Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove circomlibjs dependency in favor of zk-kit #1911

Merged
merged 9 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@
"port": 4321,
"restart": true,
"cwd": "${workspaceRoot}"
},
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Borrowed this from #1790.
It doesn't work for all utests (it depends on the path depth between the test file and package.json) but it works on many of them, and it's only useful if it's in the repo, so I think it's worth merging.

"args": [
"-r",
"ts-node/register",
"--config",
"${workspaceFolder}/.mocharc.js",
"--no-timeouts",
"--exit",
"${file}"
],
"cwd": "${fileDirname}",
"internalConsoleOptions": "openOnSessionStart",
"name": "TS Mocha Test (Current File)",
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
"request": "launch",
"type": "node"
}
]
}
2 changes: 1 addition & 1 deletion apps/consumer-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@semaphore-protocol/identity": "^3.15.2",
"@simplewebauthn/browser": "^7.2.0",
"@simplewebauthn/server": "^7.2.0",
"@zk-kit/eddsa-poseidon": "1.0.2",
"@zk-kit/eddsa-poseidon": "1.0.3",
"dotenv": "^16.0.3",
"ethers": "^5.7.2",
"json-bigint": "^1.0.0",
Expand Down
1 change: 0 additions & 1 deletion apps/passport-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"@sendgrid/mail": "^7.7.0",
"@types/lodash": "^4.14.191",
"@types/pg": "^8.6.6",
"@zk-kit/eddsa-poseidon": "1.0.2",
"async-lock": "^1.4.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
Expand Down
1 change: 0 additions & 1 deletion apps/zupoll-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@pcd/eslint-config-custom": "0.11.4",
"@pcd/tsconfig": "0.11.4",
"@types/circomlibjs": "^0.1.5",
"@types/expect": "^24.3.0",
"@types/fuzzy-search": "^2.1.5",
"@types/json-stable-stringify": "^1.0.34",
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"postinstall": "patch-package"
},
"devDependencies": {
"@types/circomlibjs": "0.1.6",
"@types/node": "^20.11.28",
"plop": "^4.0.1",
"prettier": "^3.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/gpc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"eslint": "^8.57.0",
"fix-esm-import-path": "^1.10.0",
"mocha": "^10.2.0",
"poseidon-lite": "^0.2.1",
"poseidon-lite": "^0.3.0",
"ts-mocha": "^10.0.0",
"typescript": "^5.3.3"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/gpcircuits/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"fix-esm-import-path": "^1.10.0",
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"poseidon-lite": "^0.2.1",
"poseidon-lite": "^0.3.0",
"ts-mocha": "^10.0.0",
"typescript": "^5.3.3"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/lib/pod/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@
"@zk-kit/utils": "1.2.1",
"js-sha256": "^0.10.1",
"json-bigint": "^1.0.0",
"poseidon-lite": "^0.2.1"
"poseidon-lite": "^0.3.0"
},
"devDependencies": {
"@pcd/eddsa-pcd": "0.6.5",
"@pcd/eslint-config-custom": "0.11.4",
"@pcd/pcd-types": "0.11.4",
"@pcd/tsconfig": "0.11.4",
"@semaphore-protocol/identity": "^3.15.2",
"@types/circomlibjs": "^0.1.6",
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"circomlibjs": "^0.1.7",
Expand Down
3 changes: 2 additions & 1 deletion packages/pcd/eddsa-pcd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"dependencies": {
"@pcd/pcd-types": "0.11.4",
"@pcd/util": "0.5.4",
"circomlibjs": "^0.1.7",
"@zk-kit/eddsa-poseidon": "1.0.3",
"poseidon-lite": "^0.3.0",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand Down
91 changes: 48 additions & 43 deletions packages/pcd/eddsa-pcd/src/EDDSAPCDPackage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { DisplayOptions, PCDPackage, SerializedPCD } from "@pcd/pcd-types";
import { fromHexString, requireDefinedParameter, toHexString } from "@pcd/util";
import { Eddsa, Point, buildEddsa } from "circomlibjs";
import {
derivePublicKey,
packSignature,
signMessage,
unpackSignature,
verifySignature
} from "@zk-kit/eddsa-poseidon";
import { poseidon1 } from "poseidon-lite/poseidon1";
import { poseidon12 } from "poseidon-lite/poseidon12";
import { poseidon13 } from "poseidon-lite/poseidon13";
import { poseidon2 } from "poseidon-lite/poseidon2";
import { poseidon3 } from "poseidon-lite/poseidon3";
import { v4 as uuid } from "uuid";
import {
EdDSAInitArgs,
Expand All @@ -12,31 +23,11 @@ import {
EdDSAPublicKey
} from "./EdDSAPCD";

let initializedPromise: Promise<void> | undefined;
let eddsa: Eddsa;

/**
* A promise designed to make sure that the EdDSA algorithm
* of the `circomlibjs` package has been properly initialized.
* It only initializes them once.
*/
async function ensureInitialized(): Promise<void> {
if (!initializedPromise) {
initializedPromise = (async (): Promise<void> => {
eddsa = await buildEddsa();
})();
}

await initializedPromise;
}

/**
* Creates a new {@link EdDSAPCD} by generating an {@link EdDSAPCDProof}
* and deriving an {@link EdDSAPCDClaim} from the given {@link EdDSAPCDArgs}.
*/
export async function prove(args: EdDSAPCDArgs): Promise<EdDSAPCD> {
await ensureInitialized();

let message;

if (!args.privateKey.value) throw new Error("No private key value provided");
Expand All @@ -60,16 +51,12 @@ export async function prove(args: EdDSAPCDArgs): Promise<EdDSAPCD> {
const id = typeof args.id.value === "string" ? args.id.value : uuid();
const prvKey = fromHexString(args.privateKey.value);

const hashedMessage = eddsa.poseidon(message);
const hashedMessage = poseidonHashMessage(message);
const publicKey = await getEdDSAPublicKey(prvKey);

// Make the signature on the message.
// Note: packSignature converts the R8 coordinates from Mongtomery form to
// standard form for use outside of circomlibjs.
// This is a reference to Montgomery form of numbers for modular
// multiplication, NOT Montgomery form of eliptic curves. See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_form
const signature = toHexString(
eddsa.packSignature(eddsa.signPoseidon(prvKey, hashedMessage))
packSignature(signMessage(prvKey, hashedMessage))
);

return new EdDSAPCD(id, { message, publicKey }, { signature });
Expand All @@ -81,22 +68,20 @@ export async function prove(args: EdDSAPCDArgs): Promise<EdDSAPCD> {
*/
export async function verify(pcd: EdDSAPCD): Promise<boolean> {
try {
await ensureInitialized();

const signature = eddsa.unpackSignature(fromHexString(pcd.proof.signature));
const signature = unpackSignature(fromHexString(pcd.proof.signature));

// Note: `F.fromObject` converts a coordinate from standard format to
// Montgomery form, which is expected by circomlibjs. unpackSignature above
// does the same for its R8 point.
// This is a reference to Montgomery form of numbers for modular
// multiplication, NOT Montgomery form of eliptic curves. See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_form
const pubKey = pcd.claim.publicKey.map((p) =>
eddsa.F.fromObject(p)
) as Point;
const pubKey = pcd.claim.publicKey.map((coordinateString: string) =>
BigInt("0x" + coordinateString)
) as [bigint, bigint];

const hashedMessage = eddsa.poseidon(pcd.claim.message);
const hashedMessage = poseidonHashMessage(pcd.claim.message);

return eddsa.verifyPoseidon(hashedMessage, signature, pubKey);
return verifySignature(hashedMessage, signature, pubKey);
} catch {
return false;
}
Expand Down Expand Up @@ -205,17 +190,37 @@ export const EdDSAPCDPackage: PCDPackage<
export async function getEdDSAPublicKey(
privateKey: string | Uint8Array
): Promise<EdDSAPublicKey> {
await ensureInitialized();

if (typeof privateKey === "string") {
privateKey = fromHexString(privateKey);
}

return eddsa.prv2pub(privateKey).map((p) =>
// Note: `F.toObject` converts a point from the Montgomery format used by
// circomlibjs to standard form.
// This is a reference to Montgomery form of numbers for modular
// multiplication, NOT Montgomery form of eliptic curves. See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_form
eddsa.F.toObject(p).toString(16).padStart(64, "0")
return derivePublicKey(privateKey).map((coordinate: bigint) =>
coordinate.toString(16).padStart(64, "0")
) as EdDSAPublicKey;
}

function poseidonHashMessage(message: bigint[]): bigint {
switch (message.length) {
case 1:
// Used by PODs for value hashing, so no extra bundle size impact.
return poseidon1(message);
case 2:
// Used by PODs for Merkle tree hasing, so no extra bundle size impact.
return poseidon2(message);
case 3:
// Used by unit tests, including backward-compatibility with fixed values.
// Used by GPCs for tuple hasing, so no extra bundle size impact.
return poseidon3(message);
case 12:
// Tailored to the size of EdDSATicketPCD.
return poseidon12(message);
case 13:
// Tailored to the size of EdDSAFrogPCD.
return poseidon13(message);
default:
break;
}
throw new Error(
`Unsupported EdDSAMessagePCD message size ${message.length}.`
);
}
2 changes: 1 addition & 1 deletion packages/pcd/semaphore-identity-pcd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"json-bigint": "^1.0.0",
"@types/json-bigint": "^1.0.3",
"@zk-kit/eddsa-poseidon": "1.0.3",
"@zk-kit/utils": "^1.2.0",
"@zk-kit/utils": "^1.2.1",
"js-sha256": "^0.11.0",
"poseidon-lite": "^0.3.0",
"uuid": "^9.0.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/pcd/zk-eddsa-event-ticket-pcd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"@pcd/snarkjs": "0.7.7",
"@pcd/util": "0.5.4",
"@semaphore-protocol/identity": "^3.15.2",
"circomlibjs": "^0.1.7",
"@zk-kit/eddsa-poseidon": "1.0.3",
"json-bigint": "^1.0.0",
"snarkjs": "^0.7.4",
"uuid": "^9.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
requireDefinedParameter,
uuidToBigInt
} from "@pcd/util";
import { Eddsa, buildEddsa } from "circomlibjs";
import { unpackSignature } from "@zk-kit/eddsa-poseidon";
import JSONBig from "json-bigint";
import { v4 as uuid } from "uuid";
import vkey from "../artifacts/circuit.json";
Expand All @@ -52,8 +52,6 @@ export const STATIC_TICKET_PCD_NULLIFIER = generateSnarkMessageHash(
"dummy-nullifier-for-eddsa-event-ticket-pcds"
);

let depsInitializedPromise: Promise<void> | undefined;
let eddsa: Eddsa;
let savedInitArgs: ZKEdDSAEventTicketPCDInitArgs | undefined = undefined;

/**
Expand All @@ -63,31 +61,13 @@ export async function init(args: ZKEdDSAEventTicketPCDInitArgs): Promise<void> {
savedInitArgs = args;
}

async function ensureDepsInitialized(): Promise<void> {
if (!depsInitializedPromise) {
depsInitializedPromise = (async (): Promise<void> => {
// TODO: This object is expensive to build, and duplicates some work,
// including buiding curves which aren't cached and thus have to be
// re-built by groth16. We need this object only for eddsa.F.toObject
// and eddsa.unpackSignature. To improve performance, we could tweak
// circomlibjs and/or zk-kit/groth16 either to expose those functions in a
// more limited way, or to cache all the expensive parts which will be
// needed later.
eddsa = await buildEddsa();
})();
}

await depsInitializedPromise;
}

async function ensureInitialized(): Promise<ZKEdDSAEventTicketPCDInitArgs> {
if (!savedInitArgs) {
throw new Error(
"Cannot initialize ZKEdDSAEventTicketPCDPackage: init has not been called yet"
);
}

await ensureDepsInitialized();
return savedInitArgs;
}

Expand Down Expand Up @@ -180,11 +160,7 @@ function snarkInputForProof(
const ticketAsBigIntArray = ticketDataToBigInts(ticketPCD.claim.ticket);
const pubKey = ticketPCD.proof.eddsaPCD.claim.publicKey;

// Note: unpackSignature leaves the R8 point's coordinates in Montgomery
// form, which is then reversed by toObject below.
// This is a reference to Montgomery form of numbers for modular
// multiplication, NOT Montgomery form of eliptic curves. See https://en.wikipedia.org/wiki/Montgomery_modular_multiplication#Montgomery_form
const rawSig = eddsa.unpackSignature(
const rawSig = unpackSignature(
fromHexString(ticketPCD.proof.eddsaPCD.proof.signature)
);

Expand Down Expand Up @@ -235,8 +211,8 @@ function snarkInputForProof(
// Ticket signature fields
ticketSignerPubkeyAx: hexToBigInt(pubKey[0]).toString(),
ticketSignerPubkeyAy: hexToBigInt(pubKey[1]).toString(),
ticketSignatureR8x: eddsa.F.toObject(rawSig.R8[0]).toString(),
ticketSignatureR8y: eddsa.F.toObject(rawSig.R8[1]).toString(),
ticketSignatureR8x: rawSig.R8[0].toString(),
ticketSignatureR8y: rawSig.R8[1].toString(),
ticketSignatureS: rawSig.S.toString(),

// Attendee identity secret
Expand Down
2 changes: 1 addition & 1 deletion packages/pcd/zk-eddsa-frog-pcd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@pcd/semaphore-signature-pcd": "0.11.6",
"@pcd/util": "0.5.4",
"@semaphore-protocol/identity": "^3.15.2",
"circomlibjs": "^0.1.7",
"@zk-kit/eddsa-poseidon": "1.0.3",
"json-bigint": "^1.0.0",
"snarkjs": "^0.7.4",
"uuid": "^9.0.0"
Expand Down
Loading
Loading