Skip to content

Commit

Permalink
Merge pull request #66 from plume-sig/22_resolveConflict_
Browse files Browse the repository at this point in the history
Naming and its align

PS Sad enough I only had most brutal of outlined ways for conflict resolutions on hand.
  • Loading branch information
skaunov authored Oct 26, 2023
2 parents d73ddde + 1b11bdd commit f5dbd6a
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 282 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ If you would like to get a grant to create PLUME applications or improve the lib

## Contributions

If you'd like to contribute, we offer $50 bounties in Eth/DAI for resolving any of the bugs in our issues! Each of them is quite small. That includes [#28](https://github.com/plume-sig/zk-nullifier-sig/issues/28), [#24](https://github.com/plume-sig/zk-nullifier-sig/issues/24), [#22](https://github.com/plume-sig/zk-nullifier-sig/issues/22), [#14](https://github.com/plume-sig/zk-nullifier-sig/issues/14),and [#13](https://github.com/plume-sig/zk-nullifier-sig/issues/13).
If you'd like to contribute, we offer $50 bounties in Eth/DAI for resolving any of the bugs in our issues! Each of them is quite small. That includes
[#28](https://github.com/plume-sig/zk-nullifier-sig/issues/28), [#24](https://github.com/plume-sig/zk-nullifier-sig/issues/24),
[#14](https://github.com/plume-sig/zk-nullifier-sig/issues/14),and [#13](https://github.com/plume-sig/zk-nullifier-sig/issues/13).

## Implementations

Expand Down
48 changes: 27 additions & 21 deletions circuits/test/vfy_nullifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { join } from 'path';
import { wasm as wasm_tester } from 'circom_tester'
import { describe, expect, test } from '@jest/globals';
import { hexToBigInt } from "../../javascript/src/utils/encoding";
import { c_v1, c_v2, gPowR, hashMPk, hashMPkPowR, nullifier, s_v1, s_v2, testMessage, testPublicKey, testPublicKeyPoint, testR, testSecretKey } from "../../javascript/test/test_consts"
import { c_v1, c_v2, rPoint, hashMPk, hashedToCurveR, nullifier, s_v1, s_v2, testMessage, testPublicKey, testPublicKeyPoint, testR, testSecretKey } from "../../javascript/test/test_consts"
import { Point } from "../../javascript/node_modules/@noble/secp256k1";
import { generate_inputs_from_array } from "secp256k1_hash_to_curve_circom/ts/generate_inputs";
import { bufToSha256PaddedBitArr } from "secp256k1_hash_to_curve_circom/ts/utils";
Expand All @@ -22,15 +22,17 @@ describe("Nullifier Circuit", () => {
hexToBigInt(hashMPk.x.toString()),
hexToBigInt(hashMPk.y.toString())
)
const hash_to_curve_inputs = utils.stringifyBigInts(generate_inputs_from_array(message_bytes.concat(public_key_bytes)));
const hash_to_curve_inputs = utils.stringifyBigInts(generate_inputs_from_array(
message_bytes.concat(public_key_bytes)
));

var sha_preimage_points: Point[] = [
Point.BASE,
testPublicKeyPoint,
hashMPkPoint,
nullifier,
gPowR,
hashMPkPowR,
rPoint,
hashedToCurveR,
]

const v1_sha256_preimage_bits = bufToSha256PaddedBitArr(Buffer.from(
Expand Down Expand Up @@ -107,12 +109,11 @@ describe("Nullifier Circuit", () => {
// Main circuit inputs
c: scalarToCircuitValue(hexToBigInt(c_v1)),
s: scalarToCircuitValue(hexToBigInt(s_v1)),
msg: message_bytes,
public_key: pointToCircuitValue(testPublicKeyPoint),
plume_message: message_bytes,
pk: pointToCircuitValue(testPublicKeyPoint),
nullifier: pointToCircuitValue(nullifier),
...htci,
sha256_preimage_bit_length: v1_sha256_preimage_bit_length,

})
await circuit.checkConstraints(w)
})
Expand All @@ -127,24 +128,29 @@ describe("Nullifier Circuit", () => {
// Main circuit inputs
c: scalarToCircuitValue(hexToBigInt(c_v2)),
s: scalarToCircuitValue(hexToBigInt(s_v2)),
msg: message_bytes,
public_key: pointToCircuitValue(testPublicKeyPoint),
plume_message: message_bytes,
pk: pointToCircuitValue(testPublicKeyPoint),
nullifier: pointToCircuitValue(nullifier),
...htci,
})
await circuit.checkConstraints(w)
// assertOut builds a huge json string containing the whole witness and fails with "Cannot create a string longer than 0x1fffffe8 characters"
// Instead we just slice into the witness, and the outputs start at 1 (where 0 always equals 1 due to a property of the underlying proof system)
expect(w.slice(1, 5)).toEqual(pointToCircuitValue(gPowR)[0])
expect(w.slice(5, 9)).toEqual(pointToCircuitValue(gPowR)[1])
expect(w.slice(9, 13)).toEqual(pointToCircuitValue(hashMPkPowR)[0])
expect(w.slice(13, 17)).toEqual(pointToCircuitValue(hashMPkPowR)[1])
/* assertOut builds a huge json string containing the whole witness and fails
with "Cannot create a string longer than 0x1fffffe8 characters" */
/* Instead we just slice into the witness, and the outputs start at 1
(where 0 always equals 1 due to a property of the underlying proof system) */
expect(w.slice(1, 5)).toEqual(pointToCircuitValue(rPoint)[0])
expect(w.slice(5, 9)).toEqual(pointToCircuitValue(rPoint)[1])
expect(w.slice(9, 13)).toEqual(pointToCircuitValue(hashedToCurveR)[0])
expect(w.slice(13, 17)).toEqual(pointToCircuitValue(hashedToCurveR)[1])

// In v2 we check the challenge point c outside the circuit
// Note, in a real application you would get the nullifier, g^r, and h^r as public outputs/inputs of the proof
expect(createHash("sha256")
.update(concatUint8Arrays([nullifier.toRawBytes(true), gPowR.toRawBytes(true), hashMPkPowR.toRawBytes(true)]))
.digest('hex')).toEqual(c_v2)
/* Note, in a real application you would get the nullifier,
g^r, and h^r as public outputs/inputs of the proof */
expect(
createHash("sha256").update(concatUint8Arrays([
nullifier.toRawBytes(true), rPoint.toRawBytes(true), hashedToCurveR.toRawBytes(true)
])).digest('hex')
).toEqual(c_v2)
})

// This tests that our circuit correctly computes g^s/(g^sk)^c = g^r, and that the first two equations are
Expand All @@ -156,7 +162,7 @@ describe("Nullifier Circuit", () => {
// Verify that gPowS/pkPowC = gPowR outside the circuit, as a sanity check
const gPowS = Point.fromPrivateKey(s_v1);
const pkPowC = testPublicKeyPoint.multiply(hexToBigInt(c_v1))
expect(gPowS.add(pkPowC.negate()).equals(gPowR)).toBe(true);
expect(gPowS.add(pkPowC.negate()).equals(rPoint)).toBe(true);

// Verify that circuit calculates g^s / pk^c = g^r
const w = await circuit.calculateWitness({
Expand All @@ -165,7 +171,7 @@ describe("Nullifier Circuit", () => {
c: scalarToCircuitValue(hexToBigInt(c_v1)),
})
await circuit.checkConstraints(w)
await circuit.assertOut(w, {out: pointToCircuitValue(gPowR)});
await circuit.assertOut(w, {out: pointToCircuitValue(rPoint)});
});

test("bigint <-> register conversion", async () => {
Expand Down
138 changes: 71 additions & 67 deletions circuits/verify_nullifier.circom
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ include "./node_modules/secp256k1_hash_to_curve_circom/circom/hash_to_curve.circ
include "./node_modules/secp256k1_hash_to_curve_circom/circom/Sha256.circom";
include "./node_modules/circomlib/circuits/bitify.circom";

// Verifies that a nullifier belongs to a specific public key
// Verifies that a nullifier belongs to a specific public key \
// This blog explains the intuition behind the construction https://blog.aayushg.com/posts/nullifier
template plume_v1(n, k, msg_length) {
template plume_v1(n, k, message_length) {
signal input c[k];
signal input s[k];
signal input msg[msg_length];
signal input public_key[2][k];
signal input plume_message[message_length];
signal input pk[2][k];
signal input nullifier[2][k];

// precomputed values for the hash_to_curve component
Expand All @@ -32,14 +32,14 @@ template plume_v1(n, k, msg_length) {
// precomputed value for the sha256 component. TODO: calculate internally in circom to simplify API
signal input sha256_preimage_bit_length;

component check_ec_equations = check_ec_equations(n, k, msg_length);
component check_ec_equations = check_ec_equations(n, k, message_length);

check_ec_equations.c <== c;
check_ec_equations.s <== s;
check_ec_equations.public_key <== public_key;
check_ec_equations.pk <== pk;
check_ec_equations.nullifier <== nullifier;

check_ec_equations.msg <== msg;
check_ec_equations.plume_message <== plume_message;

check_ec_equations.q0_gx1_sqrt <== q0_gx1_sqrt;
check_ec_equations.q0_gx2_sqrt <== q0_gx2_sqrt;
Expand All @@ -63,11 +63,11 @@ template plume_v1(n, k, msg_length) {
for (var i = 0; i < 2; i++) {
for (var j = 0; j < k; j++) {
c_sha256.coordinates[i][j] <== g[i][j];
c_sha256.coordinates[2+i][j] <== public_key[i][j];
c_sha256.coordinates[4+i][j] <== check_ec_equations.h[i][j];
c_sha256.coordinates[2+i][j] <== pk[i][j];
c_sha256.coordinates[4+i][j] <== check_ec_equations.hashed_to_curve[i][j];
c_sha256.coordinates[6+i][j] <== nullifier[i][j];
c_sha256.coordinates[8+i][j] <== check_ec_equations.g_pow_r[i][j];
c_sha256.coordinates[10+i][j] <== check_ec_equations.h_pow_r[i][j];
c_sha256.coordinates[8+i][j] <== check_ec_equations.r_point[i][j];
c_sha256.coordinates[10+i][j] <== check_ec_equations.hashed_to_curve_r[i][j];
}
}

Expand All @@ -90,17 +90,17 @@ template plume_v1(n, k, msg_length) {
}

// v2 is the same as v1, except that the sha256 check is done outside the circuit.
// We output g_pow_r and h_pow_r as public values so that the verifier can calculate the hash themselves.
// We output `r_point` ($g^r$) and `hashed_to_curve_r` ($hash^r$) as public values so that the verifier can calculate the hash themselves.
// The change is explained here https://www.notion.so/PLUME-Discussion-6f4b7e7cf63e4e33976f6e697bf349ff
template plume_v2(n, k, msg_length) {
template plume_v2(n, k, message_length) {
signal input c[k];
signal input s[k];
signal input msg[msg_length];
signal input public_key[2][k];
signal input plume_message[message_length];
signal input pk[2][k];
signal input nullifier[2][k];

signal output g_pow_r[2][k];
signal output h_pow_r[2][k];
signal output r_point[2][k];
signal output hashed_to_curve_r[2][k];

// precomputed values for the hash_to_curve component
signal input q0_gx1_sqrt[4];
Expand All @@ -115,14 +115,14 @@ template plume_v2(n, k, msg_length) {
signal input q1_x_mapped[4];
signal input q1_y_mapped[4];

component check_ec_equations = check_ec_equations(n, k, msg_length);
component check_ec_equations = check_ec_equations(n, k, message_length);

check_ec_equations.c <== c;
check_ec_equations.s <== s;
check_ec_equations.public_key <== public_key;
check_ec_equations.pk <== pk;
check_ec_equations.nullifier <== nullifier;

check_ec_equations.msg <== msg;
check_ec_equations.plume_message <== plume_message;

check_ec_equations.q0_gx1_sqrt <== q0_gx1_sqrt;
check_ec_equations.q0_gx2_sqrt <== q0_gx2_sqrt;
Expand All @@ -136,20 +136,20 @@ template plume_v2(n, k, msg_length) {
check_ec_equations.q1_x_mapped <== q1_x_mapped;
check_ec_equations.q1_y_mapped <== q1_y_mapped;

h_pow_r <== check_ec_equations.h_pow_r;
g_pow_r <== check_ec_equations.g_pow_r;
hashed_to_curve_r <== check_ec_equations.hashed_to_curve_r;
r_point <== check_ec_equations.r_point;
}

template check_ec_equations(n, k, msg_length) {
template check_ec_equations(n, k, message_length) {
signal input c[k];
signal input s[k];
signal input msg[msg_length];
signal input public_key[2][k];
signal input plume_message[message_length];
signal input pk[2][k];
signal input nullifier[2][k];

signal output g_pow_r[2][k];
signal output h_pow_r[2][k];
signal output h[2][k];
signal output r_point[2][k];
signal output hashed_to_curve_r[2][k];
signal output hashed_to_curve[2][k];

// precomputed values for the hash_to_curve component
signal input q0_gx1_sqrt[4];
Expand All @@ -170,58 +170,58 @@ template check_ec_equations(n, k, msg_length) {

// Calculates g^s. Note, turning a private key to a public key is the same operation as
// raising the generator g to some power, and we are *not* dealing with private keys in this circuit.
component g_pow_s = ECDSAPrivToPub(n, k);
g_pow_s.privkey <== s;
component s_point = ECDSAPrivToPub(n, k);
s_point.privkey <== s;

component g_pow_r_comp = a_div_b_pow_c(n, k);
g_pow_r_comp.a <== g_pow_s.pubkey;
g_pow_r_comp.b <== public_key;
g_pow_r_comp.c <== c;
component r_point_comp = a_div_b_pow_c(n, k);
r_point_comp.a <== s_point.pubkey;
r_point_comp.b <== pk;
r_point_comp.c <== c;

// Calculate hash[m, pk]^r
// hash[m, pk]^r = hash[m, pk]^s / (hash[m, pk]^sk)^c
// Note this implicitly checks the second equation in the blog

// Calculate hash[m, pk]^r
component h_comp = HashToCurve(msg_length + 33);
for (var i = 0; i < msg_length; i++) {
h_comp.msg[i] <== msg[i];
component hash_to_curve = HashToCurve(message_length + 33);
for (var i = 0; i < message_length; i++) {
hash_to_curve.msg[i] <== plume_message[i];
}

component pk_compressor = compress_ec_point(n, k);

pk_compressor.uncompressed <== public_key;
pk_compressor.uncompressed <== pk;

for (var i = 0; i < 33; i++) {
h_comp.msg[msg_length + i] <== pk_compressor.compressed[i];
hash_to_curve.msg[message_length + i] <== pk_compressor.compressed[i];
}

// Input precalculated values into HashToCurve
h_comp.q0_gx1_sqrt <== q0_gx1_sqrt;
h_comp.q0_gx2_sqrt <== q0_gx2_sqrt;
h_comp.q0_y_pos <== q0_y_pos;
h_comp.q0_x_mapped <== q0_x_mapped;
h_comp.q0_y_mapped <== q0_y_mapped;
h_comp.q1_gx1_sqrt <== q1_gx1_sqrt;
h_comp.q1_gx2_sqrt <== q1_gx2_sqrt;
h_comp.q1_y_pos <== q1_y_pos;
h_comp.q1_x_mapped <== q1_x_mapped;
h_comp.q1_y_mapped <== q1_y_mapped;
hash_to_curve.q0_gx1_sqrt <== q0_gx1_sqrt;
hash_to_curve.q0_gx2_sqrt <== q0_gx2_sqrt;
hash_to_curve.q0_y_pos <== q0_y_pos;
hash_to_curve.q0_x_mapped <== q0_x_mapped;
hash_to_curve.q0_y_mapped <== q0_y_mapped;
hash_to_curve.q1_gx1_sqrt <== q1_gx1_sqrt;
hash_to_curve.q1_gx2_sqrt <== q1_gx2_sqrt;
hash_to_curve.q1_y_pos <== q1_y_pos;
hash_to_curve.q1_x_mapped <== q1_x_mapped;
hash_to_curve.q1_y_mapped <== q1_y_mapped;

component h_pow_s = Secp256k1ScalarMult(n, k);
h_pow_s.scalar <== s;
h_pow_s.point <== h_comp.out;
h_pow_s.point <== hash_to_curve.out;

component h_pow_r_comp = a_div_b_pow_c(n, k);
h_pow_r_comp.a <== h_pow_s.out;
h_pow_r_comp.b <== nullifier;
h_pow_r_comp.c <== c;
component hashed_to_curve_r_comp = a_div_b_pow_c(n, k);
hashed_to_curve_r_comp.a <== h_pow_s.out;
hashed_to_curve_r_comp.b <== nullifier;
hashed_to_curve_r_comp.c <== c;

h <== h_comp.out;
hashed_to_curve <== hash_to_curve.out;

h_pow_r <== h_pow_r_comp.out;
hashed_to_curve_r <== hashed_to_curve_r_comp.out;

g_pow_r <== g_pow_r_comp.out;
r_point <== r_point_comp.out;
}

template a_div_b_pow_c(n, k) {
Expand All @@ -232,7 +232,7 @@ template a_div_b_pow_c(n, k) {

// Calculates b^c. Note that the spec uses multiplicative notation to preserve intuitions about
// discrete log, and these comments follow the spec to make comparison simpler. But the circom-ecdsa library uses
// additive notation. This is why we appear to calculate an expnentiation using a multiplication component.
// additive notation. This is why we appear to calculate an exponentiation using a multiplication component.
component b_pow_c = Secp256k1ScalarMult(n, k);
b_pow_c.scalar <== c;
b_pow_c.point <== b;
Expand Down Expand Up @@ -337,20 +337,23 @@ template compress_ec_point(n, k) {
verify.compressed <== compressed;
}

// We have a separate internal compression verification template for testing purposes. An adversarial prover
// can set any compressed values, so it's useful to be able to test adversarial inputs.
// We have a separate internal compression verification template for testing
// purposes. An adversarial prover can set any compressed values, so it's
// useful to be able to test adversarial inputs.
template verify_ec_compression(n, k) {
signal input uncompressed[2][k];
signal input compressed[33];

// Get the bit string of the smallest register
// Make sure the least significant bit's evenness matches the evenness specified by the first byte in the compressed version
// Get the bit string of the smallest register \
// Make sure the least significant bit's evenness matches the evenness
// specified by the first byte in the compressed version
component num2bits = Num2Bits(n);
num2bits.in <== uncompressed[1][0]; // Note, circom-ecdsa uses little endian, so we check the 0th register of the y value
compressed[0] === num2bits.out[0] + 2;

// Make sure the compressed and uncompressed x coordinates represent the same number
// l_bytes is an algebraic expression for the bytes of each register
// Make sure the compressed and uncompressed x coordinates represent
// the same number \
// `l_bytes` is an algebraic expression for the bytes of each register
var l_bytes[k];
for (var i = 1; i < 33; i++) {
var j = i - 1; // ignores the first byte specifying the compressed y coordinate
Expand All @@ -360,9 +363,10 @@ template verify_ec_compression(n, k) {
uncompressed[0] === l_bytes;
}

// Equivalent to get_gx and get_gy in circom-ecdsa, except we also have values for n = 64, k = 4.
// This is necessary because hash_to_curve is only implemented for n = 64, k = 4 but circom-ecdsa
// only g's coordinates for n = 86, k = 3
// Equivalent to get_gx and get_gy in circom-ecdsa, except we also have values
// for n = 64, k = 4. \
// This is necessary because hash_to_curve is only implemented for n = 64,
// k = 4 but circom-ecdsa only g's coordinates for n = 86, k = 3 \
// TODO: merge this upstream into circom-ecdsa
function get_genx(n, k) {
assert((n == 86 && k == 3) || (n == 64 && k == 4));
Expand Down
3 changes: 3 additions & 0 deletions javascript/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
# node.js
/node_modules
/dist

# https://github.com/plume-sig/zk-nullifier-sig/pull/66/#issuecomment-1780924701
package-lock.json
1 change: 0 additions & 1 deletion javascript/README.md

This file was deleted.

Loading

0 comments on commit f5dbd6a

Please sign in to comment.