Skip to content

Commit

Permalink
add serialization for proofs with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewCLu committed Oct 12, 2023
1 parent a84d1bf commit 1c8885e
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 9 deletions.
20 changes: 20 additions & 0 deletions packages/circuits/instances/example_input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"s": "1556192236082850800011477753789706164136184180458744644984084897070345066570",
"root": "1799182282238172949735919814155076722550339245418717182904975644657694908682",
"Tx": "11796026433945242671642728009981778919257130899633207712788256867701213124641",
"Ty": "14123514812924309349601388555201142092835117152213858542018278815110993732603",
"Ux": "0",
"Uy": "1",
"pathIndices": [0, 1, 0, 0, 0, 0, 0, 0],
"siblings": [
"19588054228312086345868691355666543386017663516009792796758663539234820257351",
"17039564632945388764306088555981902867518200276453168439618972583980589320757",
"7423237065226347324353380772367382631490014989348495481811164164159255474657",
"11286972368698509976183087595462810875513684078608517520839298933882497716792",
"3607627140608796879659380071776844901612302623152076817094415224584923813162",
"19712377064642672829441595136074946683621277828620209496774504837737984048981",
"20775607673010627194014556968476266066927294572720319469184847051418138353016",
"3396914609616007258851405644437304192397291162432396347162513310381425243293"
],
"nullifierRandomness": "0"
}
28 changes: 28 additions & 0 deletions packages/circuits/instances/example_proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"pi_a": [
"5880605639562721042099077587951260330226387809088309339996151506399349325339",
"7096901602761248668503897659955309997393895397922447381528780747125571090245",
"1"
],
"pi_b": [
[
"18576773105084491115543591258364978342831549325867424921401856027434420822064",
"10279670701863395962518281136133097461939970707963724808289856065805928035565"
],
[
"4456673949340011695522128695389117223374330783056666386614554999699832656589",
"10775645183456742273212890664091724274969162427612538237794050697901024603323"
],
[
"1",
"0"
]
],
"pi_c": [
"15459569549714798131271839827181190193543932132060983652373983789485730666035",
"1428509297855179783720746751267718262192801713365121898583005715695231837332",
"1"
],
"protocol": "groth16",
"curve": "bn128"
}
9 changes: 9 additions & 0 deletions packages/circuits/instances/example_public.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
"17825334909698573620993222371821585663772073121519814540615199066752100895281",
"1799182282238172949735919814155076722550339245418717182904975644657694908682",
"11796026433945242671642728009981778919257130899633207712788256867701213124641",
"14123514812924309349601388555201142092835117152213858542018278815110993732603",
"0",
"1",
"0"
]
Binary file added packages/circuits/instances/example_witness.wtns
Binary file not shown.
25 changes: 25 additions & 0 deletions packages/lib/src/babyJubjub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as hash from "hash.js";
// @ts-ignore
import { ZqField } from "ffjavascript";
import { BabyJubJub } from "./types";
import { bigIntToHex, hexToBigInt } from "./utils";

// Define short Weierstrass parameters
const curveOptions = {
Expand Down Expand Up @@ -106,6 +107,18 @@ export class WeierstrassPoint implements CurvePoint {
toString(): string {
return `Weierstrass: (${this.x.toString()}, ${this.y.toString()})`;
}

serialize(): string {
return JSON.stringify({
x: bigIntToHex(this.x),
y: bigIntToHex(this.y),
});
}

static deserialize(serialized: string): WeierstrassPoint {
const { x, y } = JSON.parse(serialized);
return new WeierstrassPoint(hexToBigInt(x), hexToBigInt(y));
}
}

export class EdwardsPoint implements CurvePoint {
Expand Down Expand Up @@ -156,4 +169,16 @@ export class EdwardsPoint implements CurvePoint {
toString(): string {
return `Edwards: (${this.x.toString()}, ${this.y.toString()})`;
}

serialize(): string {
return JSON.stringify({
x: bigIntToHex(this.x),
y: bigIntToHex(this.y),
});
}

static deserialize(serialized: string): EdwardsPoint {
const { x, y } = JSON.parse(serialized);
return new EdwardsPoint(hexToBigInt(x), hexToBigInt(y));
}
}
4 changes: 2 additions & 2 deletions packages/lib/src/inputGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { hashEdwardsPublicKey, hexToBigInt } from "./utils";
*/
export const computeMerkleRoot = async (
pubKeys: EdwardsPoint[],
hashFn: any | undefined = undefined
hashFn: any = undefined
): Promise<bigint> => {
const proof = await generateMerkleProof(pubKeys, 0, hashFn);
return proof.root;
Expand All @@ -33,7 +33,7 @@ export const computeMerkleRoot = async (
export const generateMerkleProof = async (
pubKeys: EdwardsPoint[],
index: number,
hashFn: any | undefined = undefined
hashFn: any = undefined
): Promise<MerkleProof> => {
const TREE_DEPTH = 8; // We used a fixed depth merkle tree for now
// Precomputed hashes of zero for each layer of the merkle tree
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/src/prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const proveMembership = async (
msgHash: bigint,
nullifierRandomness: bigint = BigInt(0),
pathToCircuits: string | undefined = undefined,
hashFn: any | undefined = undefined
hashFn: any = undefined
): Promise<EcdsaMembershipProof> => {
console.time("Membership Proof Generation");
console.time("T and U Generation");
Expand Down Expand Up @@ -93,7 +93,7 @@ export const batchProveMembership = async (
msgHashes: bigint[],
nullifierRandomness: bigint = BigInt(0),
pathToCircuits: string | undefined = undefined,
hashFn: any | undefined = undefined
hashFn: any = undefined
): Promise<EcdsaMembershipProof[]> => {
const numProofs = sigs.length;
if (numProofs !== indexes.length || numProofs !== msgHashes.length) {
Expand Down
27 changes: 26 additions & 1 deletion packages/lib/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EdwardsPoint, WeierstrassPoint } from "./babyJubjub";
import { Signature } from "./types";
import { EcdsaMembershipProof, Signature } from "./types";
// @ts-ignore
import { buildPoseidonReference } from "circomlibjs";

Expand Down Expand Up @@ -49,6 +49,31 @@ export const hashEdwardsPublicKey = async (
return hexToBigInt(poseidon.F.toString(hash, 16));
};

export const serializeEcdsaMembershipProof = (
proof: EcdsaMembershipProof
): string => {
const R = proof.R.serialize();
const msgHash = bigIntToHex(proof.msgHash);
const T = proof.T.serialize();
const U = proof.U.serialize();
const zkp = proof.zkp;

return JSON.stringify({ R, msgHash, T, U, zkp });
};

export const deserializeEcdsaMembershipProof = (
serializedProof: string
): EcdsaMembershipProof => {
const proof = JSON.parse(serializedProof);
const R = EdwardsPoint.deserialize(proof.R);
const msgHash = hexToBigInt(proof.msgHash);
const T = EdwardsPoint.deserialize(proof.T);
const U = EdwardsPoint.deserialize(proof.U);
const zkp = proof.zkp;

return { R, msgHash, T, U, zkp };
};

export const hexToBigInt = (hex: string): bigint => {
return BigInt(`0x${hex}`);
};
Expand Down
12 changes: 8 additions & 4 deletions packages/lib/src/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import { isNode } from "./utils";
* @param pubKeys - The list of public keys comprising the anonymity set for the proof
* @param nullifierRandomness - Optional nullifier randomness used to generate unique nullifiers
* @param pathToCircuits - The path to the verification key. Only needed for server side verification
* @param hashFn - The hash function to use for the merkle tree. Defaults to Poseidon
* @returns - A boolean indicating whether or not the proof is valid
*/
export const verifyMembership = async (
proof: EcdsaMembershipProof,
pubKeys: WeierstrassPoint[],
nullifierRandomness: bigint = BigInt(0),
pathToCircuits: string | undefined = undefined
pathToCircuits: string | undefined = undefined,
hashFn: any = undefined
): Promise<boolean> => {
if (isNode() && pathToCircuits === undefined) {
throw new Error(
Expand All @@ -33,7 +35,7 @@ export const verifyMembership = async (
const publicSignals = proof.zkp.publicSignals;
const merkleRoot = BigInt(publicSignals[1]);
const edwardsPubKeys = pubKeys.map((pubKey) => pubKey.toEdwards());
const computedMerkleRoot = await computeMerkleRoot(edwardsPubKeys);
const computedMerkleRoot = await computeMerkleRoot(edwardsPubKeys, hashFn);
if (computedMerkleRoot !== merkleRoot) {
return false;
}
Expand Down Expand Up @@ -86,13 +88,15 @@ export const verifyMembership = async (
* @param pubKeys - The list of public keys comprising the anonymity set for the proof
* @param nullifierRandomness - Optional nullifier randomness used to generate unique nullifiers
* @param pathToCircuits - The path to the verification key. Only needed for server side verification
* @param hashFn - The hash function to use for the merkle tree. Defaults to Poseidon
* @returns - A boolean indicating whether or not all of the proofs are valid
*/
export const batchVerifyMembership = async (
proofs: EcdsaMembershipProof[],
pubKeys: WeierstrassPoint[],
nullifierRandomness: bigint = BigInt(0),
pathToCircuits: string | undefined = undefined
pathToCircuits: string | undefined = undefined,
hashFn: any = undefined
): Promise<boolean> => {
if (isNode() && pathToCircuits === undefined) {
throw new Error(
Expand All @@ -103,7 +107,7 @@ export const batchVerifyMembership = async (
console.time("Batch Membership Proof Verification");
console.time("Batch Merkle Root Computation");
const edwardsPubKeys = pubKeys.map((pubKey) => pubKey.toEdwards());
const computedMerkleRoot = await computeMerkleRoot(edwardsPubKeys);
const computedMerkleRoot = await computeMerkleRoot(edwardsPubKeys, hashFn);
console.timeEnd("Batch Merkle Root Computation");

console.time("Fetching Verification Key");
Expand Down
32 changes: 32 additions & 0 deletions packages/lib/test/babyJubjub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,36 @@ describe("baby jubjub curve definition", () => {
expect(edwardsPoint.toWeierstrass()).toEqual(weierstrassPoint);
expect(weierstrassPoint.toEdwards()).toEqual(edwardsPoint);
});

test("should serialize and deserialize weierstrass points correctly", () => {
const weierstrassPoint = new WeierstrassPoint(
BigInt(
"4561812309240861642917635986636818826442846353062159251237759819544681210360"
),
BigInt(
"4047434573331865975122957359703219020835673338643881982088616311845542612717"
)
);

const serialized = weierstrassPoint.serialize();
const deserialized = WeierstrassPoint.deserialize(serialized);

expect(deserialized).toEqual(weierstrassPoint);
});

test("should serialize and deserialize edwards points correctly", () => {
const edwardsPoint = new EdwardsPoint(
BigInt(
"11049791236506940775725016544774320801686704107093911375737399460678915074436"
),
BigInt(
"14122061015030538160275787174689078850141853547608413074819581224165574773574"
)
);

const serialized = edwardsPoint.serialize();
const deserialized = EdwardsPoint.deserialize(serialized);

expect(deserialized).toEqual(edwardsPoint);
});
});
28 changes: 28 additions & 0 deletions packages/lib/test/circuits/example_pubkey_membership_proof.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"pi_a": [
"5880605639562721042099077587951260330226387809088309339996151506399349325339",
"7096901602761248668503897659955309997393895397922447381528780747125571090245",
"1"
],
"pi_b": [
[
"18576773105084491115543591258364978342831549325867424921401856027434420822064",
"10279670701863395962518281136133097461939970707963724808289856065805928035565"
],
[
"4456673949340011695522128695389117223374330783056666386614554999699832656589",
"10775645183456742273212890664091724274969162427612538237794050697901024603323"
],
[
"1",
"0"
]
],
"pi_c": [
"15459569549714798131271839827181190193543932132060983652373983789485730666035",
"1428509297855179783720746751267718262192801713365121898583005715695231837332",
"1"
],
"protocol": "groth16",
"curve": "bn128"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
"17825334909698573620993222371821585663772073121519814540615199066752100895281",
"1799182282238172949735919814155076722550339245418717182904975644657694908682",
"11796026433945242671642728009981778919257130899633207712788256867701213124641",
"14123514812924309349601388555201142092835117152213858542018278815110993732603",
"0",
"1",
"0"
]
30 changes: 30 additions & 0 deletions packages/lib/test/prove.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const fs = require("fs");
import { batchProveMembership, proveMembership } from "../src/prove";
import {
batchVerifyMembership,
getNullifierFromMembershipProof,
verifyMembership,
verifyMembershipZKP,
} from "../src/verify";
import { derDecode, hexToBigInt, publicKeyFromString } from "../src/utils";
import { WeierstrassPoint } from "../src/babyJubjub";
Expand Down Expand Up @@ -213,4 +215,32 @@ describe("ECDSA membership proof generation and verification", () => {
expect(recoveredNullifier).toEqual(expectedNullifier);
});
});

describe("zero knowledge proof generation and verification", () => {
test("should verify a zero knowledge proof generated by snarkjs", async () => {
const vKey = JSON.parse(
fs.readFileSync(pathToCircuits + "pubkey_membership_vkey.json")
);
const proof = JSON.parse(
fs.readFileSync(pathToCircuits + "example_pubkey_membership_proof.json")
);
const publicSignals = JSON.parse(
fs.readFileSync(
pathToCircuits + "example_pubkey_membership_public.json"
)
);
const zkp = {
proof,
publicSignals,
};

const verified = await verifyMembershipZKP(vKey, zkp);

// @ts-ignore
await globalThis.curve_bn128.terminate();
console.timeEnd("Batch Membership Proof Verification");

expect(verified).toBe(true);
});
});
});
Loading

0 comments on commit 1c8885e

Please sign in to comment.