Skip to content

Commit

Permalink
cached zeros in merkle proof, switch to poseidon reference, make path…
Browse files Browse the repository at this point in the history
… to circuits an input parameter
  • Loading branch information
AndrewCLu committed Oct 12, 2023
1 parent 91fa52f commit 8f434e5
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 47 deletions.
33 changes: 22 additions & 11 deletions packages/lib/src/inputGen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const BN = require("bn.js");
import { buildPoseidon } from "circomlibjs";
import { buildPoseidonReference } from "circomlibjs";
import { EdwardsPoint, WeierstrassPoint, babyjubjub } from "./babyJubjub";
import { Signature, MerkleProof } from "./types";
import {
Expand All @@ -13,8 +13,19 @@ export const generateMerkleProof = async (
index: number
): Promise<MerkleProof> => {
const TREE_DEPTH = 10;
const DEFAULT_VALUE = BigInt(0);
const poseidon = await buildPoseidon();
const ZEROS = [
"0",
"14744269619966411208579211824598458697587494354926760081771325075741142829156",
"7423237065226347324353380772367382631490014989348495481811164164159255474657",
"11286972368698509976183087595462810875513684078608517520839298933882497716792",
"3607627140608796879659380071776844901612302623152076817094415224584923813162",
"19712377064642672829441595136074946683621277828620209496774504837737984048981",
"20775607673010627194014556968476266066927294572720319469184847051418138353016",
"3396914609616007258851405644437304192397291162432396347162513310381425243293",
"21551820661461729022865262380882070649935529853313286572328683688269863701601",
"6573136701248752079028194407151022595060682063033565181951145966236778420039",
];
const poseidon = await buildPoseidonReference();

const leaves = await Promise.all(
pubKeys.map(async (pubKey) => {
Expand All @@ -24,11 +35,6 @@ export const generateMerkleProof = async (
})
);

// Todo: Cache zero values
for (let i = pubKeys.length; i < 2 ** TREE_DEPTH; i++) {
leaves.push(DEFAULT_VALUE);
}

let prevLayer: bigint[] = leaves;
let nextLayer: bigint[] = [];
let pathIndices: number[] = [];
Expand All @@ -37,18 +43,23 @@ export const generateMerkleProof = async (
for (let i = 0; i < TREE_DEPTH; i++) {
pathIndices.push(index % 2);
const siblingIndex = index % 2 === 0 ? index + 1 : index - 1;
siblings.push(prevLayer[siblingIndex]);
const sibling =
siblingIndex === prevLayer.length
? BigInt(ZEROS[i])
: prevLayer[siblingIndex];
siblings.push(sibling);
index = Math.floor(index / 2);

for (let j = 0; j < prevLayer.length; j += 2) {
const nextNode = poseidon([prevLayer[j], prevLayer[j + 1]]);
const secondNode =
j + 1 === prevLayer.length ? BigInt(ZEROS[i]) : prevLayer[j + 1];
const nextNode = poseidon([prevLayer[j], secondNode]);
nextLayer.push(hexToBigInt(poseidon.F.toString(nextNode, 16)));
}

prevLayer = nextLayer;
nextLayer = [];
}

const root = prevLayer[0];

return { root, pathIndices, siblings: siblings };
Expand Down
54 changes: 29 additions & 25 deletions packages/lib/src/proving.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,25 @@ export const proveMembership = async (
sig: Signature,
pubKeys: string[],
index: number,
msgHash: bigint
msgHash: bigint,
pathToCircuits: string | undefined = undefined
): Promise<ZKP> => {
console.time("T and U Generation");
if (isNode() && pathToCircuits === undefined) {
throw new Error(
"Path to circuits must be provided for server side proving!"
);
}

console.time("T and U Generation");
const pubKey = publicKeyFromString(pubKeys[index]);

const { T, U } = getPublicInputsFromSignature(sig, msgHash, pubKey);

console.timeEnd("T and U Generation");

console.time("Merkle Proof Generation");

const merkleProof = await generateMerkleProof(pubKeys, index);

console.timeEnd("Merkle Proof Generation");

console.time("Proving");
const proofInputs: MembershipProofInputs = {
s: sig.s,
Tx: T.x,
Expand All @@ -35,46 +38,50 @@ export const proveMembership = async (
siblings: merkleProof.siblings,
};

console.time("Proving");

const circuitsPath = getPathToCircuits();
const wasmPath = isNode()
? circuitsPath + "pubkey_membership.wasm"
? pathToCircuits + "pubkey_membership.wasm"
: "https://storage.googleapis.com/jubmoji-circuits/pubkey_membership.wasm";
const zkeyPath = isNode()
? circuitsPath + "pubkey_membership.zkey"
? pathToCircuits + "pubkey_membership.zkey"
: "https://storage.googleapis.com/jubmoji-circuits/pubkey_membership.zkey";

const proof = await snarkjs.groth16.fullProve(
proofInputs,
wasmPath,
zkeyPath
);

console.timeEnd("Proving");

return proof;
};

export const verifyMembership = async (zkProof: ZKP): Promise<boolean> => {
console.time("Verification");

const { proof, publicSignals } = zkProof;
export const verifyMembership = async (
{ proof, publicSignals }: ZKP,
pathToCircuits: string | undefined = undefined
): Promise<boolean> => {
if (isNode() && pathToCircuits === undefined) {
throw new Error(
"Path to circuits must be provided for server side verification!"
);
}

console.time("Verification");
const vKey = isNode()
? await getVerificationKeyFromFile()
? await getVerificationKeyFromFile(pathToCircuits!)
: await getVerificationKeyFromUrl();

const verified = await snarkjs.groth16.verify(vKey, publicSignals, proof);

console.timeEnd("Verification");

return verified;
};

const getVerificationKeyFromFile = async (): Promise<any> => {
const getVerificationKeyFromFile = async (
pathToCircuits: string
): Promise<any> => {
const vKey = JSON.parse(
fs.readFileSync(getPathToCircuits() + "pubkey_membership_vkey.json")
fs.readFileSync(pathToCircuits + "pubkey_membership_vkey.json")
);

return vKey;
};

Expand All @@ -83,11 +90,8 @@ const getVerificationKeyFromUrl = async (): Promise<any> => {
"https://storage.googleapis.com/jubmoji-circuits/pubkey_membership_vkey.json"
);
const vKey = await response.json();
return vKey;
};

export const getPathToCircuits = (): string => {
return isNode() ? __dirname + "/circuits/" : "";
return vKey;
};

export const isNode = (): boolean => {
Expand Down
6 changes: 3 additions & 3 deletions packages/lib/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EdwardsPoint, WeierstrassPoint } from "./babyJubjub";
import { Signature } from "./types";
import { buildPoseidon } from "circomlibjs";
import { buildPoseidonReference } from "circomlibjs";

export const derDecode = (encodedSig: string): Signature => {
const r_length = parseInt(encodedSig.slice(6, 8), 16) * 2; // Multiply by 2 to get length in hex characters
Expand All @@ -26,7 +26,7 @@ export const publicKeyFromString = (pubKey: string): WeierstrassPoint => {

export const hashPublicKey = async (pubKey: string): Promise<bigint> => {
const pubKeyPoint = publicKeyFromString(pubKey);
const poseidon = await buildPoseidon();
const poseidon = await buildPoseidonReference();
const hash = poseidon([
bigIntToBytes(pubKeyPoint.x),
bigIntToBytes(pubKeyPoint.y),
Expand All @@ -38,7 +38,7 @@ export const hashPublicKey = async (pubKey: string): Promise<bigint> => {
export const hashEdwardsPublicKey = async (
pubKey: EdwardsPoint
): Promise<bigint> => {
const poseidon = await buildPoseidon();
const poseidon = await buildPoseidonReference();
const hash = poseidon([pubKey.x, pubKey.y]);
return hexToBigInt(poseidon.F.toString(hash, 16));
};
Expand Down
4 changes: 2 additions & 2 deletions packages/lib/test/inputGen.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildPoseidon } from "circomlibjs";
import { buildPoseidonReference } from "circomlibjs";
import {
generateMerkleProof,
getPublicInputsFromSignature,
Expand All @@ -20,7 +20,7 @@ describe("merkle tree", () => {
"044d9d03f3266f24777ac488f04ec579e1c4bea984398c9b98d99a9e31bc75ef0f13a19471a7297a6f2bf0126ed93d4c55b6e98ec286203e3d761c61922e3a4cda",
];
const index = 2;
const poseidon = await buildPoseidon();
const poseidon = await buildPoseidonReference();

const merkleProof = await generateMerkleProof(pubKeys, index);

Expand Down
14 changes: 11 additions & 3 deletions packages/lib/test/proving.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { proveMembership, verifyMembership } from "../src/proving";
import { hexToBigInt } from "../src/utils";

describe("zero knowledge proof generation and verification", () => {
const pathToCircuits = process.cwd() + "/src/circuits/";

test("should generate and verify a membership proof", async () => {
const pubKeys = [
"041941f5abe4f903af965d707182b688bd1fa725fd2cbc648fc435feb42a3794593275a2e9b4ad4bc0d2f3ecc8d23e3cf89da889d7aa35ce33f132d87b5bb5c393",
Expand All @@ -20,16 +22,22 @@ describe("zero knowledge proof generation and verification", () => {
),
};

const zkProof = await proveMembership(sig, pubKeys, 2, msgHash);
const verified = await verifyMembership(zkProof);
const zkProof = await proveMembership(
sig,
pubKeys,
2,
msgHash,
pathToCircuits
);
const verified = await verifyMembership(zkProof, pathToCircuits);

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

test("should verify a proof", async () => {
const proof = JSON.parse(fs.readFileSync("./test/example_proof.json"));

const verified = await verifyMembership(proof);
const verified = await verifyMembership(proof, pathToCircuits);

expect(verified).toBe(true);
});
Expand Down
6 changes: 3 additions & 3 deletions packages/lib/test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { hashEdwardsPublicKey, publicKeyFromString } from "../src/utils";
import { EdwardsPoint, WeierstrassPoint } from "../src/babyJubjub";
import { buildPoseidon } from "circomlibjs";
import { buildPoseidonReference } from "circomlibjs";

describe("signature and key parsing utilities", () => {
test("should parse an encoded public key", () => {
Expand Down Expand Up @@ -46,7 +46,7 @@ describe("signature and key parsing utilities", () => {
const a = 1;
const b = 2;

const poseidon = await buildPoseidon();
const poseidon = await buildPoseidonReference();
const hash = poseidon.F.toString(poseidon([a, b]), 16);

expect(hash).toEqual(expected);
Expand All @@ -60,7 +60,7 @@ describe("signature and key parsing utilities", () => {
const b =
"15702184800053625297652133943476286357553803483146409610785811576616213183541";

const poseidon = await buildPoseidon();
const poseidon = await buildPoseidonReference();
const hash = poseidon.F.toString(poseidon([a, b]), 10);

expect(hash).toEqual(expected);
Expand Down

0 comments on commit 8f434e5

Please sign in to comment.