Skip to content

Commit

Permalink
Remove circomlibjs dependency in favor of zk-kit (#1911)
Browse files Browse the repository at this point in the history
Minimalist approach to switching away from circomlibjs in order to
eliminate its massive poseidon_constants from our bundle size. This
drops the bundle size in my measurements from 9.4MB to 6.4MB.

Functionality remains unchanged (validated by pre-existing backward
compatibility tests), with one known exception. The EdDSA PCD now only
supports "messages" (bigint arrays) of sizes in the set {1, 2, 3, 12,
13}. Other sizes will throw an error at proving time. This is because
poseidon-lite also has a set of constants (albeit much smaller than
circomlibjs) needed for each size. There are comments in the code
explaining why each size was selected.

Two things explicitly not done, given the minimalist approach:

- This PR doesn't attempt to unify/centralize uses of
zk-kit/poseidon-lite into a single package. Instead the old EdDSA PCDs
use the appropriate zk-kit/poseidon-lite libraries directly.
- This PR also doesn't change the format (in code, or in serialization)
of EdDSA keys to match the new form used by PODs. All externally-visible
formats are kept unchanged.
  • Loading branch information
artwyman authored Oct 1, 2024
1 parent 59625ce commit ff6793c
Show file tree
Hide file tree
Showing 17 changed files with 114 additions and 122 deletions.
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}"
},
{
"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

0 comments on commit ff6793c

Please sign in to comment.