mirror of
https://github.com/plume-sig/zk-nullifier-sig.git
synced 2026-01-10 13:28:07 -05:00
All modules naming is aligned. + refactoring&tests
[Discussion] results are incorporated. Naming groomed repository wide. \
Fun fact: I already was confused by that "hash2" naming, lol.
Note that `fn verify_signals` barely [benefit]
from renaming at all currently.
[Discussion]: 251fba6902 (commitcomment-130400727)
[benefit]: https://github.com/plume-sig/zk-nullifier-sig/issues/61
This commit is contained in:
@@ -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";
|
||||
@@ -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(
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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
|
||||
@@ -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({
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
@@ -36,10 +36,10 @@ template plume_v1(n, k, msg_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;
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
@@ -119,10 +119,10 @@ template plume_v2(n, k, msg_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;
|
||||
@@ -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];
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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));
|
||||
|
||||
@@ -16,115 +16,108 @@ export enum PlumeVersion {
|
||||
V2,
|
||||
}
|
||||
|
||||
export function computeHashMPk(
|
||||
export function computeHashToCurve(
|
||||
message: Uint8Array,
|
||||
publicKey: Uint8Array
|
||||
pk: Uint8Array
|
||||
): HashedPoint {
|
||||
// Concatenate message and publicKey
|
||||
const preimage = new Uint8Array(message.length + publicKey.length);
|
||||
const preimage = new Uint8Array(message.length + pk.length);
|
||||
preimage.set(message);
|
||||
preimage.set(publicKey, message.length);
|
||||
preimage.set(pk, message.length);
|
||||
return hashToCurve(Array.from(preimage));
|
||||
}
|
||||
|
||||
export function computeC_V2(
|
||||
nullifier: Point,
|
||||
gPowR: Point,
|
||||
hashMPkPowR: Point
|
||||
rPoint: Point,
|
||||
hashedToCurveR: Point
|
||||
) {
|
||||
const nullifierBytes = nullifier.toRawBytes(true);
|
||||
const gPowRBytes = gPowR.toRawBytes(true);
|
||||
const hashMPkPowRBytes = hashMPkPowR.toRawBytes(true);
|
||||
const preimage = concatUint8Arrays([
|
||||
nullifierBytes,
|
||||
gPowRBytes,
|
||||
hashMPkPowRBytes,
|
||||
rPoint.toRawBytes(true),
|
||||
hashedToCurveR.toRawBytes(true),
|
||||
]);
|
||||
return sha256.create().update(preimage).hex();
|
||||
}
|
||||
|
||||
export function computeC_V1(
|
||||
publicKeyBytes: Uint8Array,
|
||||
hashMPk: HashedPoint,
|
||||
pkBytes: Uint8Array,
|
||||
hashedToCurve: HashedPoint,
|
||||
nullifier: Point,
|
||||
gPowR: Point,
|
||||
hashMPkPowR: Point
|
||||
rPoint: Point,
|
||||
hashedToCurveR: Point
|
||||
) {
|
||||
const gBytes = Point.BASE.toRawBytes(true);
|
||||
const hashMPkBytes = new Point(
|
||||
hexToBigInt(hashMPk.x.toString()),
|
||||
hexToBigInt(hashMPk.y.toString())
|
||||
).toRawBytes(true);
|
||||
const nullifierBytes = nullifier.toRawBytes(true);
|
||||
const gPowRBytes = gPowR.toRawBytes(true);
|
||||
const hashMPkPowRBytes = hashMPkPowR.toRawBytes(true);
|
||||
const preimage = concatUint8Arrays([
|
||||
gBytes,
|
||||
publicKeyBytes,
|
||||
hashMPkBytes,
|
||||
Point.BASE.toRawBytes(true),
|
||||
pkBytes,
|
||||
new Point(
|
||||
hexToBigInt(hashedToCurve.x.toString()),
|
||||
hexToBigInt(hashedToCurve.y.toString())
|
||||
).toRawBytes(true),
|
||||
nullifierBytes,
|
||||
gPowRBytes,
|
||||
hashMPkPowRBytes,
|
||||
rPoint.toRawBytes(true),
|
||||
hashedToCurveR.toRawBytes(true),
|
||||
]);
|
||||
return sha256.create().update(preimage).hex();
|
||||
}
|
||||
|
||||
export function computeNullifer(hashMPk: HashedPoint, secretKey: Uint8Array) {
|
||||
return multiplyPoint(hashMPk, secretKey);
|
||||
export function computeNullifer(hashedToCurve: HashedPoint, sk: Uint8Array) {
|
||||
return multiplyPoint(hashedToCurve, sk);
|
||||
}
|
||||
|
||||
export function computeGPowR(r: Uint8Array) {
|
||||
return Point.fromPrivateKey(r);
|
||||
export function computeRPoint(rScalar: Uint8Array) {
|
||||
return Point.fromPrivateKey(rScalar);
|
||||
}
|
||||
|
||||
export function computeHashMPkPowR(hashMPk: HashedPoint, r: Uint8Array) {
|
||||
return multiplyPoint(hashMPk, r);
|
||||
export function computeHashToCurveR(hashedToCurve: HashedPoint, rScalar: Uint8Array) {
|
||||
return multiplyPoint(hashedToCurve, rScalar);
|
||||
}
|
||||
|
||||
export function computeS(r: Uint8Array, secretKey: Uint8Array, c: string) {
|
||||
const skC = (uint8ArrayToBigInt(secretKey) * hexToBigInt(c)) % CURVE.n;
|
||||
return ((skC + uint8ArrayToBigInt(r)) % CURVE.n).toString(16);
|
||||
export function computeS(rScalar: Uint8Array, sk: Uint8Array, c: string) {
|
||||
return (((uint8ArrayToBigInt(sk) * hexToBigInt(c)) % CURVE.n + uint8ArrayToBigInt(rScalar)) % CURVE.n).toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes and returns the Plume and other signals for the prover.
|
||||
* @param {string | Uint8Array} message - Message to sign, in either string or UTF-8 array format.
|
||||
* @param {string | Uint8Array} secretKey - ECDSA secret key to sign with.
|
||||
* @param {string| Uint8Array} r - Optional seed for randomness.
|
||||
* @param {string | Uint8Array} sk - ECDSA secret key to sign with.
|
||||
* @param {string| Uint8Array} rScalar - Optional seed for randomness.
|
||||
* @returns Object containing Plume and other signals - public key, s, c, gPowR, and hashMPKPowR.
|
||||
*/
|
||||
export function computeAllInputs(
|
||||
message: string | Uint8Array,
|
||||
secretKey: string | Uint8Array,
|
||||
r?: string | Uint8Array,
|
||||
sk: string | Uint8Array,
|
||||
rScalar?: string | Uint8Array,
|
||||
version: PlumeVersion = PlumeVersion.V2
|
||||
) {
|
||||
const secretKeyBytes =
|
||||
typeof secretKey === "string" ? hexToUint8Array(secretKey) : secretKey;
|
||||
const skBytes =
|
||||
typeof sk === "string" ? hexToUint8Array(sk) : sk;
|
||||
const messageBytes =
|
||||
typeof message === "string" ? messageToUint8Array(message) : message;
|
||||
const publicKeyBytes = getPublicKey(secretKeyBytes, true);
|
||||
let rBytes;
|
||||
if (r) {
|
||||
rBytes = typeof r === "string" ? hexToUint8Array(r) : r;
|
||||
const pkBytes = getPublicKey(skBytes, true);
|
||||
let rScalarBytes;
|
||||
if (rScalar) {
|
||||
rScalarBytes = typeof rScalar === "string" ? hexToUint8Array(rScalar) : rScalar;
|
||||
} else {
|
||||
rBytes = utils.randomPrivateKey();
|
||||
rScalarBytes = utils.randomPrivateKey();
|
||||
}
|
||||
const hashMPK = computeHashMPk(messageBytes, publicKeyBytes);
|
||||
const nullifier = computeNullifer(hashMPK, secretKeyBytes);
|
||||
const hashMPKPowR = computeHashMPkPowR(hashMPK, rBytes);
|
||||
const gPowR = computeGPowR(rBytes);
|
||||
const hashedToCurve = computeHashToCurve(messageBytes, pkBytes);
|
||||
const nullifier = computeNullifer(hashedToCurve, skBytes);
|
||||
const hashedToCurveR = computeHashToCurveR(hashedToCurve, rScalarBytes);
|
||||
const rPoint = computeRPoint(rScalarBytes);
|
||||
const c =
|
||||
version == PlumeVersion.V1
|
||||
? computeC_V1(publicKeyBytes, hashMPK, nullifier, gPowR, hashMPKPowR)
|
||||
: computeC_V2(nullifier, gPowR, hashMPKPowR);
|
||||
const s = computeS(rBytes, secretKeyBytes, c);
|
||||
? computeC_V1(pkBytes, hashedToCurve, nullifier, rPoint, hashedToCurveR)
|
||||
: computeC_V2(nullifier, rPoint, hashedToCurveR);
|
||||
const s = computeS(rScalarBytes, skBytes, c);
|
||||
return {
|
||||
plume: nullifier,
|
||||
s,
|
||||
publicKey: publicKeyBytes,
|
||||
pk: pkBytes,
|
||||
c,
|
||||
gPowR,
|
||||
hashMPKPowR,
|
||||
rPoint,
|
||||
hashedToCurveR,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
hashMPk,
|
||||
nullifier,
|
||||
hashMPkPowR,
|
||||
gPowR,
|
||||
hashedToCurveR,
|
||||
rPoint,
|
||||
c_v1,
|
||||
s_v1,
|
||||
c_v2,
|
||||
@@ -36,17 +36,17 @@ describe("signals", () => {
|
||||
});
|
||||
describe("Plume V1", () => {
|
||||
it("generates c and intermediate values correctly", () => {
|
||||
expect(hashMPkPowR.x.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c"
|
||||
);
|
||||
expect(hashMPkPowR.y.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed"
|
||||
);
|
||||
|
||||
expect(gPowR.x.toString(16)).toEqual(
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804"
|
||||
);
|
||||
expect(gPowR.y.toString(16)).toEqual(
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1"
|
||||
);
|
||||
|
||||
@@ -62,17 +62,17 @@ describe("signals", () => {
|
||||
});
|
||||
|
||||
it("generates all signals", () => {
|
||||
const { plume, s, publicKey, c, gPowR, hashMPKPowR } = computeAllInputs(
|
||||
const { plume, s, pk, c, rPoint, hashedToCurveR } = computeAllInputs(
|
||||
testMessage,
|
||||
testSecretKey,
|
||||
testR,
|
||||
PlumeVersion.V1,
|
||||
);
|
||||
expect(publicKey).toEqual(testPublicKey);
|
||||
expect(gPowR.x.toString(16)).toEqual(
|
||||
expect(pk).toEqual(testPublicKey);
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804"
|
||||
);
|
||||
expect(gPowR.y.toString(16)).toEqual(
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1"
|
||||
);
|
||||
expect(plume.x.toString(16)).toEqual(
|
||||
@@ -81,7 +81,7 @@ describe("signals", () => {
|
||||
expect(plume.y.toString(16)).toEqual(
|
||||
"6a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73"
|
||||
);
|
||||
expect(hashMPKPowR.x.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c"
|
||||
);
|
||||
expect(c).toEqual(
|
||||
@@ -90,7 +90,7 @@ describe("signals", () => {
|
||||
expect(s).toEqual(
|
||||
"e69f027d84cb6fe5f761e333d12e975fb190d163e8ea132d7de0bd6079ba28ca"
|
||||
);
|
||||
expect(hashMPKPowR.y.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed"
|
||||
);
|
||||
});
|
||||
@@ -98,17 +98,17 @@ describe("signals", () => {
|
||||
|
||||
describe("Plume V2", () => {
|
||||
it("generates c and intermediate values correctly", () => {
|
||||
expect(hashMPkPowR.x.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c"
|
||||
);
|
||||
expect(hashMPkPowR.y.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed"
|
||||
);
|
||||
|
||||
expect(gPowR.x.toString(16)).toEqual(
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804"
|
||||
);
|
||||
expect(gPowR.y.toString(16)).toEqual(
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1"
|
||||
);
|
||||
|
||||
@@ -124,17 +124,17 @@ describe("signals", () => {
|
||||
});
|
||||
|
||||
it("generates all signals", () => {
|
||||
const { plume, s, publicKey, c, gPowR, hashMPKPowR } = computeAllInputs(
|
||||
const { plume, s, pk, c, rPoint, hashedToCurveR } = computeAllInputs(
|
||||
testMessage,
|
||||
testSecretKey,
|
||||
testR,
|
||||
PlumeVersion.V2
|
||||
);
|
||||
expect(publicKey).toEqual(testPublicKey);
|
||||
expect(gPowR.x.toString(16)).toEqual(
|
||||
expect(pk).toEqual(testPublicKey);
|
||||
expect(rPoint.x.toString(16)).toEqual(
|
||||
"9d8ca4350e7e2ad27abc6d2a281365818076662962a28429590e2dc736fe9804"
|
||||
);
|
||||
expect(gPowR.y.toString(16)).toEqual(
|
||||
expect(rPoint.y.toString(16)).toEqual(
|
||||
"ff08c30b8afd4e854623c835d9c3aac6bcebe45112472d9b9054816a7670c5a1"
|
||||
);
|
||||
expect(plume.x.toString(16)).toEqual(
|
||||
@@ -143,7 +143,7 @@ describe("signals", () => {
|
||||
expect(plume.y.toString(16)).toEqual(
|
||||
"6a2f41488d58f33ae46edd2188e111609f9f3ae67ea38fa891d6087fe59ecb73"
|
||||
);
|
||||
expect(hashMPKPowR.x.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.x.toString(16)).toEqual(
|
||||
"6d017c6f63c59fa7a5b1e9a654e27d2869579f4d152131db270558fccd27b97c"
|
||||
);
|
||||
expect(c).toEqual(
|
||||
@@ -152,7 +152,7 @@ describe("signals", () => {
|
||||
expect(s).toEqual(
|
||||
"528e8fbb6452f82200797b1a73b2947a92524bd611085a920f1177cb8098136b"
|
||||
);
|
||||
expect(hashMPKPowR.y.toString(16)).toEqual(
|
||||
expect(hashedToCurveR.y.toString(16)).toEqual(
|
||||
"586c43fb5c99818c564a8f80a88a65f83e3f44d3c6caf5a1a4e290b777ac56ed"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@ import { CURVE, getPublicKey, Point } from "@noble/secp256k1";
|
||||
import {
|
||||
computeC_V1,
|
||||
computeC_V2,
|
||||
computeGPowR,
|
||||
computeHashMPk,
|
||||
computeHashMPkPowR,
|
||||
computeRPoint,
|
||||
computeHashToCurve,
|
||||
computeHashToCurveR,
|
||||
computeNullifer,
|
||||
computeS,
|
||||
} from "../src/signals";
|
||||
@@ -22,18 +22,18 @@ export const testR = hexToUint8Array(
|
||||
);
|
||||
export const testMessageString = "An example app message string";
|
||||
export const testMessage = messageToUint8Array(testMessageString);
|
||||
export const hashMPk = computeHashMPk(testMessage, Buffer.from(testPublicKey));
|
||||
export const hashMPk = computeHashToCurve(testMessage, Buffer.from(testPublicKey));
|
||||
export const nullifier = computeNullifer(hashMPk, testSecretKey);
|
||||
export const hashMPkPowR = computeHashMPkPowR(hashMPk, testR);
|
||||
export const gPowR = computeGPowR(testR);
|
||||
export const hashedToCurveR = computeHashToCurveR(hashMPk, testR);
|
||||
export const rPoint = computeRPoint(testR);
|
||||
export const c_v1 = computeC_V1(
|
||||
testPublicKey,
|
||||
hashMPk,
|
||||
nullifier as unknown as Point,
|
||||
gPowR,
|
||||
hashMPkPowR
|
||||
rPoint,
|
||||
hashedToCurveR
|
||||
);
|
||||
export const s_v1 = computeS(testR, testSecretKey, c_v1);
|
||||
|
||||
export const c_v2 = computeC_V2(nullifier, gPowR, hashMPkPowR);
|
||||
export const c_v2 = computeC_V2(nullifier, rPoint, hashedToCurveR);
|
||||
export const s_v2 = computeS(testR, testSecretKey, c_v2);
|
||||
|
||||
4083
javascript/yarn.lock
4083
javascript/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
mod error;
|
||||
mod hash_to_curve;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod sig {
|
||||
use crate::error::CryptoError;
|
||||
@@ -50,35 +48,35 @@ pub mod sig {
|
||||
}
|
||||
|
||||
fn compute_c_v1<P: SWModelParameters>(
|
||||
g: &GroupAffine<P>,
|
||||
g_point: &GroupAffine<P>,
|
||||
pk: &GroupAffine<P>,
|
||||
h: &GroupAffine<P>,
|
||||
nul: &GroupAffine<P>,
|
||||
g_r: &GroupAffine<P>,
|
||||
z: &GroupAffine<P>,
|
||||
hashed_to_curve: &GroupAffine<P>,
|
||||
nullifier: &GroupAffine<P>,
|
||||
r_point: &GroupAffine<P>,
|
||||
hashed_to_curve_r: &GroupAffine<P>,
|
||||
) -> Output<Sha256> {
|
||||
// Compute c = sha512([g, pk, h, nul, g^r, z])
|
||||
let g_bytes = affine_to_bytes::<P>(g);
|
||||
let pk_bytes = affine_to_bytes::<P>(pk);
|
||||
let h_bytes = affine_to_bytes::<P>(h);
|
||||
let nul_bytes = affine_to_bytes::<P>(nul);
|
||||
let g_r_bytes = affine_to_bytes::<P>(g_r);
|
||||
let z_bytes = affine_to_bytes::<P>(z);
|
||||
|
||||
let c_preimage_vec = [g_bytes, pk_bytes, h_bytes, nul_bytes, g_r_bytes, z_bytes].concat();
|
||||
let c_preimage_vec = [
|
||||
affine_to_bytes::<P>(g_point),
|
||||
affine_to_bytes::<P>(pk),
|
||||
affine_to_bytes::<P>(hashed_to_curve),
|
||||
affine_to_bytes::<P>(nullifier),
|
||||
affine_to_bytes::<P>(r_point),
|
||||
affine_to_bytes::<P>(hashed_to_curve_r)
|
||||
].concat();
|
||||
|
||||
Sha256::digest(c_preimage_vec.as_slice())
|
||||
}
|
||||
|
||||
fn compute_c_v2<P: SWModelParameters>(
|
||||
nul: &GroupAffine<P>,
|
||||
g_r: &GroupAffine<P>,
|
||||
z: &GroupAffine<P>,
|
||||
nullifier: &GroupAffine<P>,
|
||||
r_point: &GroupAffine<P>,
|
||||
hashed_to_curve_r: &GroupAffine<P>,
|
||||
) -> Output<Sha256> {
|
||||
// Compute c = sha512([nul, g^r, z])
|
||||
let nul_bytes = affine_to_bytes::<P>(nul);
|
||||
let g_r_bytes = affine_to_bytes::<P>(g_r);
|
||||
let z_bytes = affine_to_bytes::<P>(z);
|
||||
let nul_bytes = affine_to_bytes::<P>(nullifier);
|
||||
let g_r_bytes = affine_to_bytes::<P>(r_point);
|
||||
let z_bytes = affine_to_bytes::<P>(hashed_to_curve_r);
|
||||
|
||||
let c_preimage_vec = [nul_bytes, g_r_bytes, z_bytes].concat();
|
||||
|
||||
@@ -112,7 +110,7 @@ pub mod sig {
|
||||
pp: &Self::Parameters,
|
||||
keypair: (&Self::PublicKey, &Self::SecretKey),
|
||||
message: Self::Message,
|
||||
r: Self::SecretKey,
|
||||
r_scalar: Self::SecretKey,
|
||||
version: PlumeVersion,
|
||||
) -> Result<Self::Signature, CryptoError>;
|
||||
|
||||
@@ -132,7 +130,7 @@ pub mod sig {
|
||||
ark_serialize_derive::CanonicalDeserialize,
|
||||
)]
|
||||
pub struct Parameters<P: SWModelParameters> {
|
||||
pub g: GroupAffine<P>,
|
||||
pub g_point: GroupAffine<P>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
@@ -142,11 +140,11 @@ pub mod sig {
|
||||
ark_serialize_derive::CanonicalDeserialize,
|
||||
)]
|
||||
pub struct Signature<P: SWModelParameters> {
|
||||
pub z: GroupAffine<P>,
|
||||
pub g_r: GroupAffine<P>,
|
||||
pub hashed_to_curve_r: GroupAffine<P>,
|
||||
pub r_point: GroupAffine<P>,
|
||||
pub s: P::ScalarField,
|
||||
pub c: P::ScalarField,
|
||||
pub nul: GroupAffine<P>,
|
||||
pub nullifier: GroupAffine<P>,
|
||||
}
|
||||
|
||||
impl<'a, C: ProjectiveCurve, Fq: PrimeField, P: SWModelParameters>
|
||||
@@ -163,7 +161,7 @@ pub mod sig {
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::PublicKey, Self::SecretKey), CryptoError> {
|
||||
let secret_key = Self::SecretKey::rand(rng).into();
|
||||
let public_key = pp.g.mul(secret_key).into();
|
||||
let public_key = pp.g_point.mul(secret_key).into();
|
||||
Ok((public_key, secret_key))
|
||||
}
|
||||
|
||||
@@ -171,39 +169,39 @@ pub mod sig {
|
||||
pp: &Self::Parameters,
|
||||
keypair: (&Self::PublicKey, &Self::SecretKey),
|
||||
message: Self::Message,
|
||||
r: P::ScalarField,
|
||||
r_scalar: P::ScalarField,
|
||||
version: PlumeVersion,
|
||||
) -> Result<Self::Signature, CryptoError> {
|
||||
let g = pp.g;
|
||||
let g_r = g.mul(r).into_affine();
|
||||
let g_point = pp.g_point;
|
||||
let r_point = g_point.mul(r_scalar).into_affine();
|
||||
|
||||
// Compute h = htc([m, pk])
|
||||
let h = compute_h::<C, Fq, P>(&keypair.0, &message).unwrap();
|
||||
let hashed_to_curve = compute_h::<C, Fq, P>(&keypair.0, &message).unwrap();
|
||||
|
||||
// Compute z = h^r
|
||||
let z = h.mul(r).into_affine();
|
||||
let hashed_to_curve_r = hashed_to_curve.mul(r_scalar).into_affine();
|
||||
|
||||
// Compute nul = h^sk
|
||||
let nul = h.mul(*keypair.1).into_affine();
|
||||
let nullifier = hashed_to_curve.mul(*keypair.1).into_affine();
|
||||
|
||||
// Compute c = sha512([g, pk, h, nul, g^r, z])
|
||||
let c = match version {
|
||||
PlumeVersion::V1 => compute_c_v1::<P>(&g, keypair.0, &h, &nul, &g_r, &z),
|
||||
PlumeVersion::V2 => compute_c_v2(&nul, &g_r, &z),
|
||||
PlumeVersion::V1 => compute_c_v1::<P>(&g_point, keypair.0, &hashed_to_curve, &nullifier, &r_point, &hashed_to_curve_r),
|
||||
PlumeVersion::V2 => compute_c_v2(&nullifier, &r_point, &hashed_to_curve_r),
|
||||
};
|
||||
let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref());
|
||||
// Compute s = r + sk ⋅ c
|
||||
let sk_c = keypair.1.into_repr().into() * c_scalar.into_repr().into();
|
||||
let s = r.into_repr().into() + sk_c;
|
||||
let s = r_scalar.into_repr().into() + sk_c;
|
||||
|
||||
let s_scalar = P::ScalarField::from(s);
|
||||
|
||||
let signature = Signature {
|
||||
z,
|
||||
hashed_to_curve_r,
|
||||
s: s_scalar,
|
||||
g_r,
|
||||
r_point,
|
||||
c: c_scalar,
|
||||
nul,
|
||||
nullifier,
|
||||
};
|
||||
Ok(signature)
|
||||
}
|
||||
@@ -216,9 +214,9 @@ pub mod sig {
|
||||
version: PlumeVersion,
|
||||
) -> Result<Self::Signature, CryptoError> {
|
||||
// Pick a random r from Fp
|
||||
let r: P::ScalarField = Self::SecretKey::rand(rng).into();
|
||||
let r_scalar: P::ScalarField = Self::SecretKey::rand(rng).into();
|
||||
|
||||
Self::sign_with_r(pp, keypair, message, r, version)
|
||||
Self::sign_with_r(pp, keypair, message, r_scalar, version)
|
||||
}
|
||||
|
||||
fn verify_non_zk(
|
||||
@@ -229,32 +227,32 @@ pub mod sig {
|
||||
version: PlumeVersion,
|
||||
) -> Result<bool, CryptoError> {
|
||||
// Compute h = htc([m, pk])
|
||||
let h = compute_h::<C, Fq, P>(pk, message).unwrap();
|
||||
let hashed_to_curve = compute_h::<C, Fq, P>(pk, message).unwrap();
|
||||
|
||||
// TODO [replace SHA-512](https://github.com/plume-sig/zk-nullifier-sig/issues/39#issuecomment-1732497672)
|
||||
// Compute c' = sha512([g, pk, h, nul, g^r, z]) for v1
|
||||
// c' = sha512([nul, g^r, z]) for v2
|
||||
let c = match version {
|
||||
PlumeVersion::V1 => compute_c_v1::<P>(&pp.g, pk, &h, &sig.nul, &sig.g_r, &sig.z),
|
||||
PlumeVersion::V2 => compute_c_v2(&sig.nul, &sig.g_r, &sig.z),
|
||||
PlumeVersion::V1 => compute_c_v1::<P>(&pp.g_point, pk, &hashed_to_curve, &sig.nullifier, &sig.r_point, &sig.hashed_to_curve_r),
|
||||
PlumeVersion::V2 => compute_c_v2(&sig.nullifier, &sig.r_point, &sig.hashed_to_curve_r),
|
||||
};
|
||||
let c_scalar = P::ScalarField::from_be_bytes_mod_order(c.as_ref());
|
||||
|
||||
// Reject if g^s ⋅ pk^{-c} != g^r
|
||||
let g_s = pp.g.mul(sig.s);
|
||||
let g_s = pp.g_point.mul(sig.s);
|
||||
let pk_c = pk.mul(sig.c);
|
||||
let g_s_pk_c = g_s - pk_c;
|
||||
|
||||
if sig.g_r != g_s_pk_c {
|
||||
if sig.r_point != g_s_pk_c {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Reject if h^s ⋅ nul^{-c} = z
|
||||
let h_s = h.mul(sig.s);
|
||||
let nul_c = sig.nul.mul(sig.c);
|
||||
let h_s = hashed_to_curve.mul(sig.s);
|
||||
let nul_c = sig.nullifier.mul(sig.c);
|
||||
let h_s_nul_c = h_s - nul_c;
|
||||
|
||||
if sig.z != h_s_nul_c {
|
||||
if sig.hashed_to_curve_r != h_s_nul_c {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -267,3 +265,6 @@ pub mod sig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -89,7 +89,7 @@ pub fn hardcoded_msg() -> String {
|
||||
#[test]
|
||||
pub fn test_keygen() {
|
||||
let (mut rng, g) = test_template();
|
||||
let pp = Parameters{ g };
|
||||
let pp = Parameters{ g_point: g };
|
||||
|
||||
let (pk, sk) = Scheme::keygen(&pp, &mut rng).unwrap();
|
||||
|
||||
@@ -100,7 +100,7 @@ pub fn test_keygen() {
|
||||
#[test]
|
||||
pub fn test_sign_and_verify() {
|
||||
let (mut rng, g) = test_template();
|
||||
let pp = Parameters{ g };
|
||||
let pp = Parameters{ g_point: g };
|
||||
|
||||
let message = b"Message";
|
||||
let keypair = Scheme::keygen(&pp, &mut rng).unwrap();
|
||||
@@ -217,7 +217,7 @@ pub fn test_against_zk_nullifier_sig_c_and_s() {
|
||||
let message = message.as_bytes();
|
||||
let sk = hex_to_fr(&hardcoded_sk());
|
||||
let (_, g) = test_template();
|
||||
let pp = Parameters{ g };
|
||||
let pp = Parameters{ g_point: g };
|
||||
let pk_projective = g.mul(sk);
|
||||
let pk = GroupAffine::<Secp256k1Parameters>::from(pk_projective);
|
||||
|
||||
|
||||
@@ -16,4 +16,6 @@ num-bigint = "0.4.3"
|
||||
num-integer = "0.1.45"
|
||||
k256 = {version = "0.11.3", features = ["arithmetic", "hash2curve", "expose-field", "sha2"]}
|
||||
elliptic-curve = { version = "0.12.2", features = ["arithmetic"]}
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.3"
|
||||
|
||||
@@ -8,7 +8,6 @@ use elliptic_curve::bigint::ArrayEncoding;
|
||||
use elliptic_curve::hash2curve::{ExpandMsgXmd, GroupDigest};
|
||||
use elliptic_curve::ops::Reduce;
|
||||
use elliptic_curve::sec1::ToEncodedPoint;
|
||||
use hex_literal::hex;
|
||||
use k256::U256;
|
||||
use k256::{
|
||||
// ecdsa::{signature::Signer, Signature, SigningKey},
|
||||
@@ -35,23 +34,7 @@ fn print_type_of<T>(_: &T) {
|
||||
println!("{}", std::any::type_name::<T>());
|
||||
}
|
||||
|
||||
// Generates a deterministic secret key for deterministic testing. Should be replaced by random oracle in production deployments.
|
||||
fn gen_test_scalar_sk() -> Scalar {
|
||||
Scalar::from_repr(
|
||||
hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Generates a deterministic r for deterministic testing. Should be replaced by random oracle in production deployments.
|
||||
fn gen_test_scalar_r() -> Scalar {
|
||||
Scalar::from_repr(
|
||||
hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn sha256hash_vec_signal(values: &[ProjectivePoint]) -> Output<Sha256> {
|
||||
fn c_sha256_vec_signal(values: &[ProjectivePoint]) -> Output<Sha256> {
|
||||
let preimage_vec = values
|
||||
.iter()
|
||||
.map(|value| encode_pt(*value).unwrap())
|
||||
@@ -59,7 +42,7 @@ fn sha256hash_vec_signal(values: &[ProjectivePoint]) -> Output<Sha256> {
|
||||
.concat();
|
||||
let mut sha256_hasher = Sha256::new();
|
||||
sha256_hasher.update(preimage_vec.as_slice());
|
||||
sha256_hasher.finalize() //256 bit hash
|
||||
sha256_hasher.finalize()
|
||||
}
|
||||
|
||||
fn sha256hash6signals(
|
||||
@@ -89,19 +72,8 @@ fn sha256hash6signals(
|
||||
Scalar::from_repr(c_bytes).unwrap()
|
||||
}
|
||||
|
||||
// Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint
|
||||
fn hash_to_secp(s: &[u8]) -> ProjectivePoint {
|
||||
let pt: ProjectivePoint = Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
|
||||
&[s],
|
||||
//b"CURVE_XMD:SHA-256_SSWU_RO_"
|
||||
DST,
|
||||
)
|
||||
.unwrap();
|
||||
pt
|
||||
}
|
||||
|
||||
// Hashes two values to the curve
|
||||
fn hash_m_pk_to_secp(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint {
|
||||
fn hash_to_curve(m: &[u8], pk: &ProjectivePoint) -> ProjectivePoint {
|
||||
let pt: ProjectivePoint = Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
|
||||
&[[m, &encode_pt(*pk).unwrap()].concat().as_slice()],
|
||||
//b"CURVE_XMD:SHA-256_SSWU_RO_",
|
||||
@@ -121,59 +93,68 @@ enum PlumeVersion {
|
||||
// hash[m, gsk]^[r + sk * c] / (hash[m, pk]^sk)^c = hash[m, pk]^r
|
||||
// c = hash2(g, g^sk, hash[m, g^sk], hash[m, pk]^sk, gr, hash[m, pk]^r)
|
||||
fn verify_signals(
|
||||
m: &[u8],
|
||||
message: &[u8],
|
||||
pk: &ProjectivePoint,
|
||||
nullifier: &ProjectivePoint,
|
||||
c: &Output<Sha256>,
|
||||
r_sk_c: &Scalar,
|
||||
g_r_option: &Option<ProjectivePoint>,
|
||||
hash_m_pk_pow_r_option: &Option<ProjectivePoint>,
|
||||
s: &Scalar,
|
||||
r_point_optional: &Option<ProjectivePoint>,
|
||||
hashed_to_curve_optional: &Option<ProjectivePoint>,
|
||||
version: PlumeVersion,
|
||||
) -> bool {
|
||||
let mut verified: bool = true;
|
||||
let mut verified: bool = true; // looks like antipattern to @skaunov; also see #22
|
||||
|
||||
// The base point or generator of the curve.
|
||||
let g = &ProjectivePoint::GENERATOR;
|
||||
|
||||
// hash[m, pk]
|
||||
let hash_m_pk = &hash_m_pk_to_secp(m, pk);
|
||||
let hashed_to_curve_computed = &hash_to_curve(message, pk);
|
||||
|
||||
// Check whether g^r equals g^s * pk^{-c}
|
||||
let g_r: ProjectivePoint;
|
||||
let r_point_computed: ProjectivePoint;
|
||||
// TODO should we use non-zero `Scalar`?
|
||||
let c_scalar = &Scalar::from_uint_reduced(U256::from_be_byte_array(*c));
|
||||
match *g_r_option {
|
||||
match *r_point_optional {
|
||||
Some(_g_r_value) => {
|
||||
if (g * r_sk_c - pk * c_scalar) != _g_r_value {
|
||||
if (g * s - pk * c_scalar) != _g_r_value {
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
None => println!("g^r not provided, check skipped"),
|
||||
}
|
||||
g_r = g * r_sk_c - pk * c_scalar;
|
||||
r_point_computed = g * s - pk * c_scalar;
|
||||
|
||||
// Check whether h^r equals h^{r + sk * c} * nullifier^{-c}
|
||||
let hash_m_pk_pow_r: ProjectivePoint;
|
||||
match *hash_m_pk_pow_r_option {
|
||||
let hashed_to_curve_r_computed: ProjectivePoint;
|
||||
match *hashed_to_curve_optional {
|
||||
Some(_hash_m_pk_pow_r_value) => {
|
||||
if (hash_m_pk * r_sk_c - nullifier * c_scalar) != _hash_m_pk_pow_r_value {
|
||||
if (hashed_to_curve_computed * s - nullifier * c_scalar) != _hash_m_pk_pow_r_value {
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
None => println!("hash_m_pk_pow_r not provided, check skipped"),
|
||||
}
|
||||
hash_m_pk_pow_r = hash_m_pk * r_sk_c - nullifier * c_scalar;
|
||||
hashed_to_curve_r_computed = hashed_to_curve_computed * s - nullifier * c_scalar;
|
||||
|
||||
// Check if the given hash matches
|
||||
match version {
|
||||
PlumeVersion::V1 => {
|
||||
if sha256hash_vec_signal(&[*g, *pk, *hash_m_pk, *nullifier, g_r, hash_m_pk_pow_r]) != *c
|
||||
if c_sha256_vec_signal(&[
|
||||
*g,
|
||||
*pk,
|
||||
*hashed_to_curve_computed,
|
||||
*nullifier,
|
||||
r_point_computed,
|
||||
hashed_to_curve_r_computed,
|
||||
]) != *c
|
||||
{
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
PlumeVersion::V2 => {
|
||||
if sha256hash_vec_signal(&[*nullifier, g_r, hash_m_pk_pow_r]) != *c {
|
||||
if c_sha256_vec_signal(&[*nullifier, r_point_computed, hashed_to_curve_r_computed])
|
||||
!= *c
|
||||
{
|
||||
verified = false;
|
||||
}
|
||||
}
|
||||
@@ -203,10 +184,38 @@ fn byte_array_to_scalar(bytes: &[u8]) -> Scalar {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use helpers::test_gen_signals;
|
||||
|
||||
use helpers::{gen_test_scalar_sk, hash_to_secp, test_gen_signals};
|
||||
mod helpers {
|
||||
use super::*;
|
||||
use hex_literal::hex;
|
||||
|
||||
// Generates a deterministic secret key for deterministic testing. Should be replaced by random oracle in production deployments.
|
||||
pub fn gen_test_scalar_sk() -> Scalar {
|
||||
Scalar::from_repr(
|
||||
hex!("519b423d715f8b581f4fa8ee59f4771a5b44c8130b4e3eacca54a56dda72b464").into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Generates a deterministic r for deterministic testing. Should be replaced by random oracle in production deployments.
|
||||
fn gen_test_scalar_r() -> Scalar {
|
||||
Scalar::from_repr(
|
||||
hex!("93b9323b629f251b8f3fc2dd11f4672c5544e8230d493eceea98a90bda789808").into(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Calls the hash to curve function for secp256k1, and returns the result as a ProjectivePoint
|
||||
pub fn hash_to_secp(s: &[u8]) -> ProjectivePoint {
|
||||
let pt: ProjectivePoint = Secp256k1::hash_from_bytes::<ExpandMsgXmd<Sha256>>(
|
||||
&[s],
|
||||
//b"CURVE_XMD:SHA-256_SSWU_RO_"
|
||||
DST,
|
||||
)
|
||||
.unwrap();
|
||||
pt
|
||||
}
|
||||
|
||||
// These generate test signals as if it were passed from a secure enclave to wallet. Note that leaking these signals would leak pk, but not sk.
|
||||
// Outputs these 6 signals, in this order
|
||||
@@ -243,7 +252,7 @@ mod tests {
|
||||
let g_r = &g * &r;
|
||||
|
||||
// hash[m, pk]
|
||||
let hash_m_pk = hash_m_pk_to_secp(m, &pk);
|
||||
let hash_m_pk = hash_to_curve(m, &pk);
|
||||
|
||||
println!(
|
||||
"h.x: {:?}",
|
||||
@@ -283,11 +292,11 @@ mod tests {
|
||||
// The Fiat-Shamir type step.
|
||||
let c = match version {
|
||||
PlumeVersion::V1 => {
|
||||
sha256hash_vec_signal(&[g, pk, hash_m_pk, nullifier, g_r, hash_m_pk_pow_r])
|
||||
c_sha256_vec_signal(&[g, pk, hash_m_pk, nullifier, g_r, hash_m_pk_pow_r])
|
||||
}
|
||||
PlumeVersion::V2 => sha256hash_vec_signal(&[nullifier, g_r, hash_m_pk_pow_r]),
|
||||
PlumeVersion::V2 => c_sha256_vec_signal(&[nullifier, g_r, hash_m_pk_pow_r]),
|
||||
};
|
||||
|
||||
|
||||
let c_scalar = &Scalar::from_uint_reduced(U256::from_be_byte_array(c.clone()));
|
||||
// This value is part of the discrete log equivalence (DLEQ) proof.
|
||||
let r_sk_c = r + sk * c_scalar;
|
||||
|
||||
Reference in New Issue
Block a user