@crisp-e3/sdk 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ import { LeanIMT } from "@zk-kit/lean-imt";
7
7
 
8
8
  // src/constants.ts
9
9
  import { hashMessage } from "viem";
10
+ var MERKLE_TREE_MAX_DEPTH = 20;
10
11
  var MAX_MSG_NON_ZERO_COEFFS = 100;
11
12
  var MAX_VOTE_OPTIONS = 10;
12
13
  var SIGNATURE_MESSAGE = "CRISP: Sign this message to prove ownership of your Ethereum account";
@@ -15,30 +16,41 @@ var SIGNATURE_MESSAGE_HASH = hashMessage(SIGNATURE_MESSAGE);
15
16
  // src/utils.ts
16
17
  import { publicKeyToAddress } from "viem/utils";
17
18
  import { hexToBytes, recoverPublicKey } from "viem";
19
+ var hashLeaf = (address, balance) => {
20
+ return poseidon2([address.toLowerCase(), balance]);
21
+ };
22
+ var generateMerkleTree = (leaves) => {
23
+ return new LeanIMT((a, b) => poseidon2([a, b]), leaves);
24
+ };
25
+ var generateMerkleProof = (balance, address, leaves) => {
26
+ const leaf = hashLeaf(address.toLowerCase(), balance);
27
+ const index = leaves.findIndex((l) => BigInt(l) === leaf);
28
+ if (index === -1) {
29
+ throw new Error("Leaf not found in the tree");
30
+ }
31
+ const tree = generateMerkleTree(leaves.map((l) => BigInt(l)));
32
+ const proof = tree.generateProof(index);
33
+ const paddedSiblings = [...proof.siblings, ...Array(MERKLE_TREE_MAX_DEPTH - proof.siblings.length).fill(0n)];
34
+ const indices = proof.siblings.map((_, i) => Number(BigInt(proof.index) >> BigInt(i) & 1n));
35
+ const paddedIndices = [...indices, ...Array(MERKLE_TREE_MAX_DEPTH - indices.length).fill(0)];
36
+ return {
37
+ leaf,
38
+ index,
39
+ proof: {
40
+ ...proof,
41
+ siblings: paddedSiblings
42
+ },
43
+ // Original length before padding
44
+ length: proof.siblings.length,
45
+ indices: paddedIndices
46
+ };
47
+ };
18
48
  var toBinary = (number) => {
19
49
  if (number < 0) {
20
50
  throw new Error("Value cannot be negative");
21
51
  }
22
52
  return number.toString(2);
23
53
  };
24
- var extractSignatureComponents = async (signature, messageHash = SIGNATURE_MESSAGE_HASH) => {
25
- const publicKey = await recoverPublicKey({ hash: messageHash, signature });
26
- const publicKeyBytes = hexToBytes(publicKey);
27
- const publicKeyX = publicKeyBytes.slice(1, 33);
28
- const publicKeyY = publicKeyBytes.slice(33, 65);
29
- const sigBytes = hexToBytes(signature);
30
- const r = sigBytes.slice(0, 32);
31
- const s = sigBytes.slice(32, 64);
32
- const signatureBytes = new Uint8Array(64);
33
- signatureBytes.set(r, 0);
34
- signatureBytes.set(s, 32);
35
- return {
36
- messageHash: hexToBytes(messageHash),
37
- publicKeyX,
38
- publicKeyY,
39
- signature: signatureBytes
40
- };
41
- };
42
54
  var getMaxVoteValue = (numChoices) => {
43
55
  const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices);
44
56
  return 2 ** segmentSize - 1;
@@ -101,52 +113,54 @@ var encryptVote = (vote, publicKey) => {
101
113
  };
102
114
 
103
115
  // src/circuitInputs.ts
104
- var generateCircuitInputsImpl = async (proofInputs) => {
116
+ var prepareCircuitInputsImpl = async (inputs) => {
105
117
  const zkInputsGenerator = getZkInputsGenerator();
106
- const numOptions = proofInputs.vote.length;
118
+ const numOptions = inputs.isMaskVote ? inputs.numOptions : inputs.vote.length;
107
119
  const zeroVote = getZeroVote(numOptions);
108
- const encodedVote = encodeVote(proofInputs.vote);
120
+ const vote = inputs.isMaskVote ? zeroVote : inputs.vote;
121
+ const encodedVote = encodeVote(vote);
109
122
  let circuitInputs;
110
123
  let encryptedVote;
111
- if (!proofInputs.previousCiphertext) {
124
+ if (!inputs.previousCiphertext) {
112
125
  const result = await zkInputsGenerator.generateInputs(
113
- encryptVote(zeroVote, proofInputs.publicKey),
114
- proofInputs.publicKey,
126
+ encryptVote(zeroVote, inputs.publicKey),
127
+ inputs.publicKey,
115
128
  numberArrayToBigInt64Array(encodedVote)
116
129
  );
117
130
  circuitInputs = result.inputs;
118
131
  encryptedVote = result.encryptedVote;
119
132
  } else {
120
133
  const result = await zkInputsGenerator.generateInputsForUpdate(
121
- proofInputs.previousCiphertext,
122
- proofInputs.publicKey,
134
+ inputs.previousCiphertext,
135
+ inputs.publicKey,
123
136
  numberArrayToBigInt64Array(encodedVote)
124
137
  );
125
138
  circuitInputs = result.inputs;
126
139
  encryptedVote = result.encryptedVote;
127
140
  }
128
- const signature = await extractSignatureComponents(proofInputs.signature, proofInputs.messageHash);
129
- circuitInputs.hashed_message = Array.from(signature.messageHash).map((b) => b.toString());
130
- circuitInputs.public_key_x = Array.from(signature.publicKeyX).map((b) => b.toString());
131
- circuitInputs.public_key_y = Array.from(signature.publicKeyY).map((b) => b.toString());
132
- circuitInputs.signature = Array.from(signature.signature).map((b) => b.toString());
133
- circuitInputs.slot_address = proofInputs.slotAddress.toLowerCase();
134
- circuitInputs.balance = proofInputs.balance.toString();
135
- circuitInputs.is_first_vote = !proofInputs.previousCiphertext;
136
- circuitInputs.is_mask_vote = proofInputs.isMaskVote;
137
- circuitInputs.merkle_root = proofInputs.merkleProof.proof.root.toString();
138
- circuitInputs.merkle_proof_length = proofInputs.merkleProof.length.toString();
139
- circuitInputs.merkle_proof_indices = proofInputs.merkleProof.indices.map((i) => i.toString());
140
- circuitInputs.merkle_proof_siblings = proofInputs.merkleProof.proof.siblings.map((s) => s.toString());
141
+ circuitInputs.slot_address = inputs.slotAddress.toLowerCase();
142
+ circuitInputs.is_first_vote = !inputs.previousCiphertext;
143
+ circuitInputs.is_mask_vote = inputs.isMaskVote;
141
144
  circuitInputs.num_options = numOptions.toString();
142
- return { circuitInputs, encryptedVote };
145
+ if (inputs.censusMode === "onchain") {
146
+ circuitInputs.voting_power = inputs.votingPower.toString();
147
+ } else {
148
+ const merkleProof = generateMerkleProof(inputs.balance, inputs.slotAddress, inputs.merkleLeaves);
149
+ circuitInputs.balance = inputs.balance.toString();
150
+ circuitInputs.merkle_root = merkleProof.proof.root.toString();
151
+ circuitInputs.merkle_proof_length = merkleProof.length.toString();
152
+ circuitInputs.merkle_proof_indices = merkleProof.indices.map((i) => i === 1);
153
+ circuitInputs.merkle_proof_siblings = merkleProof.proof.siblings.map((s) => s.toString());
154
+ }
155
+ const ctCommitment = `0x${BigInt(circuitInputs.ct_commitment).toString(16).padStart(64, "0")}`;
156
+ return { circuitInputs, encryptedVote, ctCommitment, censusMode: inputs.censusMode };
143
157
  };
144
158
 
145
159
  // src/workers/generateCircuitInputs.worker.ts
146
160
  self.onmessage = async (e) => {
147
161
  try {
148
- const result = await generateCircuitInputsImpl(e.data);
149
- self.postMessage({ type: "result", ...result });
162
+ const prepared = await prepareCircuitInputsImpl(e.data);
163
+ self.postMessage({ type: "result", prepared });
150
164
  } catch (err) {
151
165
  const error = err instanceof Error ? err.message : String(err);
152
166
  const stack = err instanceof Error ? err.stack : void 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/encoding.ts","../../src/utils.ts","../../src/constants.ts","../../src/circuitInputs.ts","../../src/workers/generateCircuitInputs.worker.ts"],"sourcesContent":["// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\n/**\n * Vote encoding and BFV encryption for the CRISP voting protocol.\n *\n * Encodes vote choices (numbers per option) into polynomial coefficient arrays\n * suitable for BFV homomorphic encryption. Each choice is represented as a\n * segment of binary digits within the first MAX_MSG_NON_ZERO_COEFFS coeffs, then\n * zero-padded to the BFV polynomial degree. Supports\n * encoding, encryption, decryption, and tally decoding.\n */\n\nimport { ZKInputsGenerator } from '@crisp-e3/zk-inputs'\nimport { toBinary, numberArrayToBigInt64Array, decodeBytesToBigInts, getMaxVoteValue } from './utils'\nimport { MAX_MSG_NON_ZERO_COEFFS, MAX_VOTE_OPTIONS } from './constants'\nimport { hexToBytes } from 'viem'\nimport type { Hex } from 'viem'\nimport type { TallyResult, Vote } from './types'\n\nlet _zkInputsGenerator: InstanceType<typeof ZKInputsGenerator> | null = null\n\n/**\n * Returns the singleton ZK inputs generator instance (lazily initialized).\n */\nexport const getZkInputsGenerator = () => {\n if (!_zkInputsGenerator) {\n _zkInputsGenerator = ZKInputsGenerator.withDefaults()\n }\n return _zkInputsGenerator\n}\n\n/**\n * Encodes vote choices into a polynomial coefficient array for BFV encryption.\n * Each choice occupies floor(MAX_MSG_NON_ZERO_COEFFS / n) binary coefficients;\n * remaining slots in the first MAX_MSG_NON_ZERO_COEFFS coeffs are zero; then\n * the vector is padded to the BFV degree.\n *\n * @param vote - Array of numeric values per choice (e.g. [10, 5] for 2 options)\n * @returns Array of 0s and 1s representing coefficients\n * @throws If vote has fewer than 2 choices, any value exceeds max for its segment, or degree is too small\n */\nexport const encodeVote = (vote: Vote): number[] => {\n const numChoices = vote.length\n\n if (numChoices < 2) {\n throw new Error('Vote must have at least two choices')\n }\n\n // The Noir circuit asserts num_options <= MAX_OPTIONS, so a vote beyond this can never\n // produce a valid proof. Reject it here rather than encoding an unprovable vote.\n if (numChoices > MAX_VOTE_OPTIONS) {\n throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`)\n }\n\n const bfvParams = getZkInputsGenerator().getBFVParams()\n const degree = bfvParams.degree\n if (degree < MAX_MSG_NON_ZERO_COEFFS) {\n throw new Error(`BFV degree (${degree}) must be at least MAX_MSG_NON_ZERO_COEFFS (${MAX_MSG_NON_ZERO_COEFFS})`)\n }\n\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n const maxValue = getMaxVoteValue(numChoices)\n const voteArray: number[] = []\n\n for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx += 1) {\n const value = vote[choiceIdx]\n\n if (value > maxValue) {\n throw new Error(`Vote value for choice ${choiceIdx} exceeds maximum (${maxValue})`)\n }\n\n const binary = toBinary(value).split('')\n\n for (let i = 0; i < segmentSize; i += 1) {\n const offset = segmentSize - binary.length\n voteArray.push(i < offset ? 0 : parseInt(binary[i - offset], 10))\n }\n }\n\n const msgCoeffsUsed = segmentSize * numChoices\n for (let i = msgCoeffsUsed; i < MAX_MSG_NON_ZERO_COEFFS; i += 1) {\n voteArray.push(0)\n }\n\n for (let i = 0; i < degree - MAX_MSG_NON_ZERO_COEFFS; i += 1) {\n voteArray.push(0)\n }\n\n return voteArray\n}\n\n/**\n * Encrypts an encoded vote using BFV homomorphic encryption.\n *\n * @param vote - Vote choices to encrypt\n * @param publicKey - BFV public key\n * @returns Encrypted ciphertext\n */\nexport const encryptVote = (vote: Vote, publicKey: Uint8Array): Uint8Array => {\n const encodedVote = encodeVote(vote)\n\n return getZkInputsGenerator().encryptVote(publicKey, numberArrayToBigInt64Array(encodedVote))\n}\n\n/**\n * Decodes raw tally bytes (or coefficients) into a total per choice.\n * Expects the same segment layout as used in encodeVote.\n *\n * Mirrors `crisp_utils::decode_tally` (Rust) and `CRISPProgram.decodeTally` (Solidity):\n * only the first MAX_MSG_NON_ZERO_COEFFS coefficients carry the payload, split into\n * `floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)` binary coefficients per choice, MSB first.\n *\n * @param tallyBytes - Hex string, or the polynomial coefficients from tally/decryption\n * @param numChoices - Number of vote options: an integer from 2 to MAX_VOTE_OPTIONS\n * @returns One total per choice\n * @throws If numChoices is outside 2..MAX_VOTE_OPTIONS or not an integer, or there are fewer\n * coefficients than the payload region\n */\nexport const decodeTally = (tallyBytes: string | number[] | bigint[], numChoices: number): TallyResult => {\n // `CRISPProgram.validate` rejects a round outside 2..MAX_VOTE_OPTIONS, and `encodeVote` refuses\n // to encode fewer than two choices, so no tally in that range can exist. `Number.isInteger` also\n // screens out NaN, Infinity, and fractions: a fractional count silently returns `ceil(numChoices)`\n // segments, and NaN passes both bound checks to return an empty tally.\n if (!Number.isInteger(numChoices) || numChoices < 2) {\n throw new Error(`Number of choices (${numChoices}) must be an integer of at least 2`)\n }\n\n // Rounds cannot exceed MAX_VOTE_OPTIONS (the circuit's MAX_OPTIONS), so a larger count\n // is a caller error rather than a tally to decode.\n if (numChoices > MAX_VOTE_OPTIONS) {\n throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`)\n }\n\n let coefficients: bigint[]\n if (typeof tallyBytes === 'string') {\n const hexString = tallyBytes.startsWith('0x') ? tallyBytes : `0x${tallyBytes}`\n coefficients = decodeBytesToBigInts(hexToBytes(hexString as Hex))\n } else {\n coefficients = (tallyBytes as Array<number | bigint>).map(BigInt)\n }\n\n if (coefficients.length < MAX_MSG_NON_ZERO_COEFFS) {\n throw new Error(`decoded coefficient count (${coefficients.length}) is less than MAX_MSG_NON_ZERO_COEFFS (${MAX_MSG_NON_ZERO_COEFFS})`)\n }\n\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n const results: TallyResult = []\n\n for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx++) {\n const segmentStart = choiceIdx * segmentSize\n\n let value = 0n\n for (let i = 0; i < segmentSize; i++) {\n value += coefficients[segmentStart + i] << BigInt(segmentSize - 1 - i)\n }\n\n results.push(value)\n }\n\n return results\n}\n\n/**\n * Decrypts a BFV-encrypted vote and decodes it to vote values.\n *\n * @param ciphertext - Encrypted vote\n * @param secretKey - BFV secret key\n * @param numChoices - Number of vote options\n * @returns One total per choice\n */\nexport const decryptVote = (ciphertext: Uint8Array, secretKey: Uint8Array, numChoices: number): TallyResult => {\n const decryptedVote = getZkInputsGenerator().decryptVote(secretKey, ciphertext)\n\n return decodeTally(Array.from(decryptedVote), numChoices)\n}\n\n/**\n * Generates a BFV keypair for vote encryption and decryption.\n *\n * @returns Object with secretKey and publicKey as Uint8Arrays\n */\nexport const generateBFVKeys = (): { secretKey: Uint8Array; publicKey: Uint8Array } => {\n return getZkInputsGenerator().generateKeys()\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { poseidon2 } from 'poseidon-lite'\nimport { LeanIMT } from '@zk-kit/lean-imt'\nimport type { MerkleProof } from './types'\nimport { MAX_MSG_NON_ZERO_COEFFS, MERKLE_TREE_MAX_DEPTH, SIGNATURE_MESSAGE_HASH } from './constants'\nimport { publicKeyToAddress } from 'viem/utils'\nimport { hexToBytes, recoverPublicKey } from 'viem'\n\n/**\n * Hash a leaf node for the Merkle tree\n * @param address The voter's address\n * @param balance The voter's balance\n * @returns The hashed leaf as a bigint\n */\nexport const hashLeaf = (address: string, balance: bigint): bigint => {\n return poseidon2([address.toLowerCase(), balance])\n}\n\n/**\n * Generate a new LeanIMT with the leaves provided\n * @param leaves The leaves of the Merkle tree\n * @returns the generated Merkle tree\n */\nexport const generateMerkleTree = (leaves: bigint[]): LeanIMT => {\n return new LeanIMT((a, b) => poseidon2([a, b]), leaves)\n}\n\n/**\n * Generate a Merkle proof for a given address to prove inclusion in the voters' list\n * @param balance The voter's balance\n * @param address The voter's address\n * @param leaves The leaves of the Merkle tree\n */\nexport const generateMerkleProof = (balance: bigint, address: string, leaves: bigint[] | string[]): MerkleProof => {\n const leaf = hashLeaf(address.toLowerCase(), balance)\n\n const index = leaves.findIndex((l) => BigInt(l) === leaf)\n\n if (index === -1) {\n throw new Error('Leaf not found in the tree')\n }\n\n const tree = generateMerkleTree(leaves.map((l) => BigInt(l)))\n\n const proof = tree.generateProof(index)\n\n // Pad siblings with zeros\n const paddedSiblings = [...proof.siblings, ...Array(MERKLE_TREE_MAX_DEPTH - proof.siblings.length).fill(0n)]\n // Pad indices with zeros\n const indices = proof.siblings.map((_, i) => Number((BigInt(proof.index) >> BigInt(i)) & 1n))\n const paddedIndices = [...indices, ...Array(MERKLE_TREE_MAX_DEPTH - indices.length).fill(0)]\n\n return {\n leaf,\n index,\n proof: {\n ...proof,\n siblings: paddedSiblings,\n },\n // Original length before padding\n length: proof.siblings.length,\n indices: paddedIndices,\n }\n}\n\n/**\n * Convert a number to its binary representation\n * @param number The number to convert to binary\n * @returns The binary representation of the number as a string\n */\nexport const toBinary = (number: number): string => {\n if (number < 0) {\n throw new Error('Value cannot be negative')\n }\n\n return number.toString(2)\n}\n\n/**\n * Given a signature, extract the signature components for the Noir signature verification circuit.\n * @param signature The signature to extract the components from.\n * @returns The extracted signature components.\n */\nexport const extractSignatureComponents = async (\n signature: `0x${string}`,\n messageHash: `0x${string}` = SIGNATURE_MESSAGE_HASH,\n): Promise<{\n messageHash: Uint8Array\n publicKeyX: Uint8Array\n publicKeyY: Uint8Array\n signature: Uint8Array\n}> => {\n const publicKey = await recoverPublicKey({ hash: messageHash, signature })\n const publicKeyBytes = hexToBytes(publicKey)\n const publicKeyX = publicKeyBytes.slice(1, 33)\n const publicKeyY = publicKeyBytes.slice(33, 65)\n\n // Extract r and s from signature (remove v)\n const sigBytes = hexToBytes(signature)\n const r = sigBytes.slice(0, 32) // First 32 bytes\n const s = sigBytes.slice(32, 64) // Next 32 bytes\n\n const signatureBytes = new Uint8Array(64)\n signatureBytes.set(r, 0)\n signatureBytes.set(s, 32)\n\n return {\n messageHash: hexToBytes(messageHash),\n publicKeyX: publicKeyX,\n publicKeyY: publicKeyY,\n signature: signatureBytes,\n }\n}\n\nexport const getAddressFromSignature = async (signature: `0x${string}`, messageHash?: `0x${string}`): Promise<string> => {\n const publicKey = await recoverPublicKey({ hash: messageHash || SIGNATURE_MESSAGE_HASH, signature })\n\n return publicKeyToAddress(publicKey)\n}\n\n/**\n * Get the maximum vote value for a given number of choices.\n * @param numChoices Number of choices.\n * @returns Maximum value per choice.\n */\nexport const getMaxVoteValue = (numChoices: number): number => {\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n return 2 ** segmentSize - 1\n}\n\n/**\n * Get a zero vote with the given number of choices.\n * @param numChoices Number of choices.\n * @returns A zero vote with the given number of choices.\n */\nexport const getZeroVote = (numChoices: number): number[] => {\n return Array(numChoices).fill(0)\n}\n\n/**\n * Decode bytes to a bigint array (little-endian, 8 bytes per value).\n *\n * @remarks\n * Returns `bigint` rather than `number`: a coefficient of an aggregated plaintext\n * is a sum over all ballots and can exceed `Number.MAX_SAFE_INTEGER`.\n *\n * @param data The bytes to decode (must be multiple of 8).\n * @returns Array of coefficients.\n */\nexport const decodeBytesToBigInts = (data: Uint8Array): bigint[] => {\n if (data.length % 8 !== 0) {\n throw new Error('Data length must be multiple of 8')\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n const arrayLength = data.length / 8\n const result: bigint[] = []\n\n for (let i = 0; i < arrayLength; i++) {\n result.push(view.getBigUint64(i * 8, true)) // true = little-endian\n }\n\n return result\n}\n\nexport const bigInt64ArrayToNumberArray = (bigInt64Array: BigInt64Array): number[] => {\n return Array.from(bigInt64Array).map(Number)\n}\n\nexport const numberArrayToBigInt64Array = (numberArray: number[]): BigInt64Array => {\n return BigInt64Array.from(numberArray.map(BigInt))\n}\n\n// Helper function to convert proof bytes to field elements\nexport const proofToFields = (proof: Uint8Array): string[] => {\n const fields: string[] = []\n for (let i = 0; i < proof.length; i += 32) {\n const chunk = proof.slice(i, i + 32)\n fields.push('0x' + Buffer.from(chunk).toString('hex'))\n }\n return fields\n}\n\n/**\n * Scale down the raw balance to 1 decimal precision\n * @param balance - The raw balance (with all tokens decimals)\n * @param decimals - The decimals of the token\n * @returns The balance as a .1 precision scaled value\n */\nexport const getScaledBalance = (balance: bigint, decimals: bigint): bigint => {\n const precision = decimals > 1n ? decimals - 1n : 0n\n\n return balance / 10n ** precision\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { hashMessage } from 'viem'\n\nexport const CRISP_SERVER_TOKEN_TREE_ENDPOINT = 'state/token-holders'\nexport const CRISP_SERVER_STATE_LITE_ENDPOINT = 'state/lite'\nexport const CRISP_SERVER_PREVIOUS_CIPHERTEXT_ENDPOINT = 'state/previous-ciphertext'\nexport const CRISP_SERVER_STATE_RESULT_ENDPOINT = 'state/result'\nexport const CRISP_SERVER_STATE_ALL_ENDPOINT = 'state/all'\nexport const CRISP_SERVER_ELIGIBLE_ADDRESSES_ENDPOINT = 'state/eligible-addresses'\nexport const CRISP_SERVER_VOTING_BROADCAST_ENDPOINT = 'voting/broadcast'\nexport const CRISP_SERVER_VOTING_STATUS_ENDPOINT = 'voting/status'\nexport const CRISP_SERVER_ROUNDS_CURRENT_ENDPOINT = 'rounds/current'\nexport const CRISP_SERVER_ROUNDS_PUBLIC_KEY_ENDPOINT = 'rounds/public-key'\nexport const CRISP_SERVER_ROUNDS_CIPHERTEXT_ENDPOINT = 'rounds/ciphertext'\nexport const CRISP_SERVER_ROUNDS_REQUEST_ENDPOINT = 'rounds/request'\n\nexport const MERKLE_TREE_MAX_DEPTH = 20 // static, hardcoded in the circuit.\n\n// @note Must stay aligned with CRISP circuits / threshold message layout (Rust & Noir MAX_MSG_NON_ZERO_COEFFS).\n// Vote payload uses only the first MAX_MSG_NON_ZERO_COEFFS polynomial coeffs, split evenly across options\n// (e.g. 2 options → 50 binary coeffs each within those 100).\nexport const MAX_MSG_NON_ZERO_COEFFS = 100\n// Hard limit on the maximum number of vote options supported.\nexport const MAX_VOTE_OPTIONS = 10\n\n/**\n * Message used by users to prove ownership of their Ethereum account\n * This message is signed by the user's private key to authenticate their identity\n * @notice Apps ideally want to use a different message to avoid signature reuse across different applications\n */\nexport const SIGNATURE_MESSAGE = 'CRISP: Sign this message to prove ownership of your Ethereum account'\nexport const SIGNATURE_MESSAGE_HASH = hashMessage(SIGNATURE_MESSAGE)\n\n// Placeholder signature for masking votes.\nexport const MASK_SIGNATURE =\n '0x8e7d77112641d59e9409ec3052041703bb9d9e6ed39bfcf75aefbcafe829ac6b21dd7648116ad5db0466fcb4bd468dcb28f6c069def8bc47cd9d859c85a016e31b'\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { getZkInputsGenerator, encodeVote, encryptVote } from './encoding'\nimport { extractSignatureComponents, getZeroVote, numberArrayToBigInt64Array } from './utils'\nimport type { ProofInputs } from './types'\n\n/**\n * Generate the circuit inputs for a vote proof.\n * Kept in a separate module so it can run in a worker.\n */\nexport const generateCircuitInputsImpl = async (proofInputs: ProofInputs): Promise<{ circuitInputs: any; encryptedVote: Uint8Array }> => {\n const zkInputsGenerator = getZkInputsGenerator()\n\n const numOptions = proofInputs.vote.length\n const zeroVote = getZeroVote(numOptions)\n const encodedVote = encodeVote(proofInputs.vote)\n\n let circuitInputs: any\n let encryptedVote: Uint8Array\n\n if (!proofInputs.previousCiphertext) {\n const result = await zkInputsGenerator.generateInputs(\n encryptVote(zeroVote, proofInputs.publicKey),\n proofInputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n )\n\n circuitInputs = result.inputs\n encryptedVote = result.encryptedVote\n } else {\n const result = await zkInputsGenerator.generateInputsForUpdate(\n proofInputs.previousCiphertext,\n proofInputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n )\n\n circuitInputs = result.inputs\n encryptedVote = result.encryptedVote\n }\n\n const signature = await extractSignatureComponents(proofInputs.signature, proofInputs.messageHash)\n\n circuitInputs.hashed_message = Array.from(signature.messageHash).map((b) => b.toString())\n circuitInputs.public_key_x = Array.from(signature.publicKeyX).map((b) => b.toString())\n circuitInputs.public_key_y = Array.from(signature.publicKeyY).map((b) => b.toString())\n circuitInputs.signature = Array.from(signature.signature).map((b) => b.toString())\n circuitInputs.slot_address = proofInputs.slotAddress.toLowerCase()\n circuitInputs.balance = proofInputs.balance.toString()\n circuitInputs.is_first_vote = !proofInputs.previousCiphertext\n circuitInputs.is_mask_vote = proofInputs.isMaskVote\n circuitInputs.merkle_root = proofInputs.merkleProof.proof.root.toString()\n circuitInputs.merkle_proof_length = proofInputs.merkleProof.length.toString()\n circuitInputs.merkle_proof_indices = proofInputs.merkleProof.indices.map((i) => i.toString())\n circuitInputs.merkle_proof_siblings = proofInputs.merkleProof.proof.siblings.map((s) => s.toString())\n circuitInputs.num_options = numOptions.toString()\n\n return { circuitInputs, encryptedVote }\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// Runs generateCircuitInputs in a worker to avoid blocking the main thread\n// during CPU-heavy zk-inputs WASM (BFV encryption).\n\nimport type { ProofInputs } from '../types'\nimport { generateCircuitInputsImpl } from '../circuitInputs'\n\nself.onmessage = async (e: MessageEvent<ProofInputs>) => {\n try {\n const result = await generateCircuitInputsImpl(e.data)\n self.postMessage({ type: 'result' as const, ...result })\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n const stack = err instanceof Error ? err.stack : undefined\n self.postMessage({ type: 'error' as const, error, stack })\n }\n}\n"],"mappings":"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","names":["hexToBytes"]}
1
+ {"version":3,"sources":["../../src/encoding.ts","../../src/utils.ts","../../src/constants.ts","../../src/circuitInputs.ts","../../src/workers/generateCircuitInputs.worker.ts"],"sourcesContent":["// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\n/**\n * Vote encoding and BFV encryption for the CRISP voting protocol.\n *\n * Encodes vote choices (numbers per option) into polynomial coefficient arrays\n * suitable for BFV homomorphic encryption. Each choice is represented as a\n * segment of binary digits within the first MAX_MSG_NON_ZERO_COEFFS coeffs, then\n * zero-padded to the BFV polynomial degree. Supports\n * encoding, encryption, decryption, and tally decoding.\n */\n\nimport { ZKInputsGenerator } from '@crisp-e3/zk-inputs'\nimport { toBinary, numberArrayToBigInt64Array, decodeBytesToBigInts, getMaxVoteValue } from './utils'\nimport { MAX_MSG_NON_ZERO_COEFFS, MAX_VOTE_OPTIONS } from './constants'\nimport { hexToBytes } from 'viem'\nimport type { Hex } from 'viem'\nimport type { TallyResult, Vote } from './types'\n\nlet _zkInputsGenerator: InstanceType<typeof ZKInputsGenerator> | null = null\n\n/**\n * Returns the singleton ZK inputs generator instance (lazily initialized).\n */\nexport const getZkInputsGenerator = () => {\n if (!_zkInputsGenerator) {\n _zkInputsGenerator = ZKInputsGenerator.withDefaults()\n }\n return _zkInputsGenerator\n}\n\n/**\n * Encodes vote choices into a polynomial coefficient array for BFV encryption.\n * Each choice occupies floor(MAX_MSG_NON_ZERO_COEFFS / n) binary coefficients;\n * remaining slots in the first MAX_MSG_NON_ZERO_COEFFS coeffs are zero; then\n * the vector is padded to the BFV degree.\n *\n * @param vote - Array of numeric values per choice (e.g. [10, 5] for 2 options)\n * @returns Array of 0s and 1s representing coefficients\n * @throws If vote has fewer than 2 choices, any value exceeds max for its segment, or degree is too small\n */\nexport const encodeVote = (vote: Vote): number[] => {\n const numChoices = vote.length\n\n if (numChoices < 2) {\n throw new Error('Vote must have at least two choices')\n }\n\n // The Noir circuit asserts num_options <= MAX_OPTIONS, so a vote beyond this can never\n // produce a valid proof. Reject it here rather than encoding an unprovable vote.\n if (numChoices > MAX_VOTE_OPTIONS) {\n throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`)\n }\n\n const bfvParams = getZkInputsGenerator().getBFVParams()\n const degree = bfvParams.degree\n if (degree < MAX_MSG_NON_ZERO_COEFFS) {\n throw new Error(`BFV degree (${degree}) must be at least MAX_MSG_NON_ZERO_COEFFS (${MAX_MSG_NON_ZERO_COEFFS})`)\n }\n\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n const maxValue = getMaxVoteValue(numChoices)\n const voteArray: number[] = []\n\n for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx += 1) {\n const value = vote[choiceIdx]\n\n if (value > maxValue) {\n throw new Error(`Vote value for choice ${choiceIdx} exceeds maximum (${maxValue})`)\n }\n\n const binary = toBinary(value).split('')\n\n for (let i = 0; i < segmentSize; i += 1) {\n const offset = segmentSize - binary.length\n voteArray.push(i < offset ? 0 : parseInt(binary[i - offset], 10))\n }\n }\n\n const msgCoeffsUsed = segmentSize * numChoices\n for (let i = msgCoeffsUsed; i < MAX_MSG_NON_ZERO_COEFFS; i += 1) {\n voteArray.push(0)\n }\n\n for (let i = 0; i < degree - MAX_MSG_NON_ZERO_COEFFS; i += 1) {\n voteArray.push(0)\n }\n\n return voteArray\n}\n\n/**\n * Encrypts an encoded vote using BFV homomorphic encryption.\n *\n * @param vote - Vote choices to encrypt\n * @param publicKey - BFV public key\n * @returns Encrypted ciphertext\n */\nexport const encryptVote = (vote: Vote, publicKey: Uint8Array): Uint8Array => {\n const encodedVote = encodeVote(vote)\n\n return getZkInputsGenerator().encryptVote(publicKey, numberArrayToBigInt64Array(encodedVote))\n}\n\n/**\n * Decodes raw tally bytes (or coefficients) into a total per choice.\n * Expects the same segment layout as used in encodeVote.\n *\n * Mirrors `crisp_utils::decode_tally` (Rust) and `CRISPProgram.decodeTally` (Solidity):\n * only the first MAX_MSG_NON_ZERO_COEFFS coefficients carry the payload, split into\n * `floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)` binary coefficients per choice, MSB first.\n *\n * @param tallyBytes - Hex string, or the polynomial coefficients from tally/decryption\n * @param numChoices - Number of vote options: an integer from 2 to MAX_VOTE_OPTIONS\n * @returns One total per choice\n * @throws If numChoices is outside 2..MAX_VOTE_OPTIONS or not an integer, or there are fewer\n * coefficients than the payload region\n */\nexport const decodeTally = (tallyBytes: string | number[] | bigint[], numChoices: number): TallyResult => {\n // `CRISPProgram.validate` rejects a round outside 2..MAX_VOTE_OPTIONS, and `encodeVote` refuses\n // to encode fewer than two choices, so no tally in that range can exist. `Number.isInteger` also\n // screens out NaN, Infinity, and fractions: a fractional count silently returns `ceil(numChoices)`\n // segments, and NaN passes both bound checks to return an empty tally.\n if (!Number.isInteger(numChoices) || numChoices < 2) {\n throw new Error(`Number of choices (${numChoices}) must be an integer of at least 2`)\n }\n\n // Rounds cannot exceed MAX_VOTE_OPTIONS (the circuit's MAX_OPTIONS), so a larger count\n // is a caller error rather than a tally to decode.\n if (numChoices > MAX_VOTE_OPTIONS) {\n throw new Error(`Number of choices (${numChoices}) exceeds MAX_VOTE_OPTIONS (${MAX_VOTE_OPTIONS})`)\n }\n\n let coefficients: bigint[]\n if (typeof tallyBytes === 'string') {\n const hexString = tallyBytes.startsWith('0x') ? tallyBytes : `0x${tallyBytes}`\n coefficients = decodeBytesToBigInts(hexToBytes(hexString as Hex))\n } else {\n coefficients = (tallyBytes as Array<number | bigint>).map(BigInt)\n }\n\n if (coefficients.length < MAX_MSG_NON_ZERO_COEFFS) {\n throw new Error(`decoded coefficient count (${coefficients.length}) is less than MAX_MSG_NON_ZERO_COEFFS (${MAX_MSG_NON_ZERO_COEFFS})`)\n }\n\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n const results: TallyResult = []\n\n for (let choiceIdx = 0; choiceIdx < numChoices; choiceIdx++) {\n const segmentStart = choiceIdx * segmentSize\n\n let value = 0n\n for (let i = 0; i < segmentSize; i++) {\n value += coefficients[segmentStart + i] << BigInt(segmentSize - 1 - i)\n }\n\n results.push(value)\n }\n\n return results\n}\n\n/**\n * Decrypts a BFV-encrypted vote and decodes it to vote values.\n *\n * @param ciphertext - Encrypted vote\n * @param secretKey - BFV secret key\n * @param numChoices - Number of vote options\n * @returns One total per choice\n */\nexport const decryptVote = (ciphertext: Uint8Array, secretKey: Uint8Array, numChoices: number): TallyResult => {\n const decryptedVote = getZkInputsGenerator().decryptVote(secretKey, ciphertext)\n\n return decodeTally(Array.from(decryptedVote), numChoices)\n}\n\n/**\n * Generates a BFV keypair for vote encryption and decryption.\n *\n * @returns Object with secretKey and publicKey as Uint8Arrays\n */\nexport const generateBFVKeys = (): { secretKey: Uint8Array; publicKey: Uint8Array } => {\n return getZkInputsGenerator().generateKeys()\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { poseidon2 } from 'poseidon-lite'\nimport { LeanIMT } from '@zk-kit/lean-imt'\nimport type { MerkleProof } from './types'\nimport { MAX_MSG_NON_ZERO_COEFFS, MERKLE_TREE_MAX_DEPTH, SIGNATURE_MESSAGE_HASH } from './constants'\nimport { publicKeyToAddress } from 'viem/utils'\nimport { hexToBytes, recoverPublicKey } from 'viem'\n\n/**\n * Hash a leaf node for the Merkle tree\n * @param address The voter's address\n * @param balance The voter's balance\n * @returns The hashed leaf as a bigint\n */\nexport const hashLeaf = (address: string, balance: bigint): bigint => {\n return poseidon2([address.toLowerCase(), balance])\n}\n\n/**\n * Generate a new LeanIMT with the leaves provided\n * @param leaves The leaves of the Merkle tree\n * @returns the generated Merkle tree\n */\nexport const generateMerkleTree = (leaves: bigint[]): LeanIMT => {\n return new LeanIMT((a, b) => poseidon2([a, b]), leaves)\n}\n\n/**\n * Generate a Merkle proof for a given address to prove inclusion in the voters' list\n * @param balance The voter's balance\n * @param address The voter's address\n * @param leaves The leaves of the Merkle tree\n */\nexport const generateMerkleProof = (balance: bigint, address: string, leaves: bigint[] | string[]): MerkleProof => {\n const leaf = hashLeaf(address.toLowerCase(), balance)\n\n const index = leaves.findIndex((l) => BigInt(l) === leaf)\n\n if (index === -1) {\n throw new Error('Leaf not found in the tree')\n }\n\n const tree = generateMerkleTree(leaves.map((l) => BigInt(l)))\n\n const proof = tree.generateProof(index)\n\n // Pad siblings with zeros\n const paddedSiblings = [...proof.siblings, ...Array(MERKLE_TREE_MAX_DEPTH - proof.siblings.length).fill(0n)]\n // Pad indices with zeros\n const indices = proof.siblings.map((_, i) => Number((BigInt(proof.index) >> BigInt(i)) & 1n))\n const paddedIndices = [...indices, ...Array(MERKLE_TREE_MAX_DEPTH - indices.length).fill(0)]\n\n return {\n leaf,\n index,\n proof: {\n ...proof,\n siblings: paddedSiblings,\n },\n // Original length before padding\n length: proof.siblings.length,\n indices: paddedIndices,\n }\n}\n\n/**\n * Convert a number to its binary representation\n * @param number The number to convert to binary\n * @returns The binary representation of the number as a string\n */\nexport const toBinary = (number: number): string => {\n if (number < 0) {\n throw new Error('Value cannot be negative')\n }\n\n return number.toString(2)\n}\n\n/**\n * Given a signature, extract the signature components for the Noir signature verification circuit.\n * @param signature The signature to extract the components from.\n * @returns The extracted signature components.\n */\nexport const extractSignatureComponents = async (\n signature: `0x${string}`,\n messageHash: `0x${string}` = SIGNATURE_MESSAGE_HASH,\n): Promise<{\n messageHash: Uint8Array\n publicKeyX: Uint8Array\n publicKeyY: Uint8Array\n signature: Uint8Array\n}> => {\n const publicKey = await recoverPublicKey({ hash: messageHash, signature })\n const publicKeyBytes = hexToBytes(publicKey)\n const publicKeyX = publicKeyBytes.slice(1, 33)\n const publicKeyY = publicKeyBytes.slice(33, 65)\n\n // Extract r and s from signature (remove v)\n const sigBytes = hexToBytes(signature)\n const r = sigBytes.slice(0, 32) // First 32 bytes\n const s = sigBytes.slice(32, 64) // Next 32 bytes\n\n const signatureBytes = new Uint8Array(64)\n signatureBytes.set(r, 0)\n signatureBytes.set(s, 32)\n\n return {\n messageHash: hexToBytes(messageHash),\n publicKeyX: publicKeyX,\n publicKeyY: publicKeyY,\n signature: signatureBytes,\n }\n}\n\nexport const getAddressFromSignature = async (signature: `0x${string}`, messageHash?: `0x${string}`): Promise<string> => {\n const publicKey = await recoverPublicKey({ hash: messageHash || SIGNATURE_MESSAGE_HASH, signature })\n\n return publicKeyToAddress(publicKey)\n}\n\n/**\n * Get the maximum vote value for a given number of choices.\n * @param numChoices Number of choices.\n * @returns Maximum value per choice.\n */\nexport const getMaxVoteValue = (numChoices: number): number => {\n const segmentSize = Math.floor(MAX_MSG_NON_ZERO_COEFFS / numChoices)\n return 2 ** segmentSize - 1\n}\n\n/**\n * Get a zero vote with the given number of choices.\n * @param numChoices Number of choices.\n * @returns A zero vote with the given number of choices.\n */\nexport const getZeroVote = (numChoices: number): number[] => {\n return Array(numChoices).fill(0)\n}\n\n/**\n * Decode bytes to a bigint array (little-endian, 8 bytes per value).\n *\n * @remarks\n * Returns `bigint` rather than `number`: a coefficient of an aggregated plaintext\n * is a sum over all ballots and can exceed `Number.MAX_SAFE_INTEGER`.\n *\n * @param data The bytes to decode (must be multiple of 8).\n * @returns Array of coefficients.\n */\nexport const decodeBytesToBigInts = (data: Uint8Array): bigint[] => {\n if (data.length % 8 !== 0) {\n throw new Error('Data length must be multiple of 8')\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength)\n const arrayLength = data.length / 8\n const result: bigint[] = []\n\n for (let i = 0; i < arrayLength; i++) {\n result.push(view.getBigUint64(i * 8, true)) // true = little-endian\n }\n\n return result\n}\n\nexport const bigInt64ArrayToNumberArray = (bigInt64Array: BigInt64Array): number[] => {\n return Array.from(bigInt64Array).map(Number)\n}\n\nexport const numberArrayToBigInt64Array = (numberArray: number[]): BigInt64Array => {\n return BigInt64Array.from(numberArray.map(BigInt))\n}\n\n// Helper function to convert proof bytes to field elements\nexport const proofToFields = (proof: Uint8Array): string[] => {\n const fields: string[] = []\n for (let i = 0; i < proof.length; i += 32) {\n const chunk = proof.slice(i, i + 32)\n fields.push('0x' + Buffer.from(chunk).toString('hex'))\n }\n return fields\n}\n\n/**\n * Scale down the raw balance to 1 decimal precision\n * @param balance - The raw balance (with all tokens decimals)\n * @param decimals - The decimals of the token\n * @returns The balance as a .1 precision scaled value\n */\nexport const getScaledBalance = (balance: bigint, decimals: bigint): bigint => {\n const precision = decimals > 1n ? decimals - 1n : 0n\n\n return balance / 10n ** precision\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { hashMessage } from 'viem'\n\nexport const CRISP_SERVER_TOKEN_TREE_ENDPOINT = 'state/token-holders'\nexport const CRISP_SERVER_STATE_LITE_ENDPOINT = 'state/lite'\nexport const CRISP_SERVER_PREVIOUS_CIPHERTEXT_ENDPOINT = 'state/previous-ciphertext'\nexport const CRISP_SERVER_STATE_RESULT_ENDPOINT = 'state/result'\nexport const CRISP_SERVER_STATE_ALL_ENDPOINT = 'state/all'\nexport const CRISP_SERVER_ELIGIBLE_ADDRESSES_ENDPOINT = 'state/eligible-addresses'\nexport const CRISP_SERVER_VOTING_BROADCAST_ENDPOINT = 'voting/broadcast'\nexport const CRISP_SERVER_VOTING_STATUS_ENDPOINT = 'voting/status'\nexport const CRISP_SERVER_ROUNDS_CURRENT_ENDPOINT = 'rounds/current'\nexport const CRISP_SERVER_ROUNDS_PUBLIC_KEY_ENDPOINT = 'rounds/public-key'\nexport const CRISP_SERVER_ROUNDS_CIPHERTEXT_ENDPOINT = 'rounds/ciphertext'\nexport const CRISP_SERVER_ROUNDS_REQUEST_ENDPOINT = 'rounds/request'\n\nexport const MERKLE_TREE_MAX_DEPTH = 20 // static, hardcoded in the circuit.\n\n// @note Must stay aligned with CRISP circuits / threshold message layout (Rust & Noir MAX_MSG_NON_ZERO_COEFFS).\n// Vote payload uses only the first MAX_MSG_NON_ZERO_COEFFS polynomial coeffs, split evenly across options\n// (e.g. 2 options → 50 binary coeffs each within those 100).\nexport const MAX_MSG_NON_ZERO_COEFFS = 100\n// Hard limit on the maximum number of vote options supported.\nexport const MAX_VOTE_OPTIONS = 10\n\n/**\n * Message used by users to prove ownership of their Ethereum account\n * This message is signed by the user's private key to authenticate their identity\n * @notice Apps ideally want to use a different message to avoid signature reuse across different applications\n */\nexport const SIGNATURE_MESSAGE = 'CRISP: Sign this message to prove ownership of your Ethereum account'\nexport const SIGNATURE_MESSAGE_HASH = hashMessage(SIGNATURE_MESSAGE)\n\n// Placeholder signature for masking votes.\nexport const MASK_SIGNATURE =\n '0x8e7d77112641d59e9409ec3052041703bb9d9e6ed39bfcf75aefbcafe829ac6b21dd7648116ad5db0466fcb4bd468dcb28f6c069def8bc47cd9d859c85a016e31b'\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// This file is provided WITHOUT ANY WARRANTY;\n// without even the implied warranty of MERCHANTABILITY\n// or FITNESS FOR A PARTICULAR PURPOSE.\n\nimport { getZkInputsGenerator, encodeVote, encryptVote } from './encoding'\nimport { extractSignatureComponents, generateMerkleProof, getZeroVote, numberArrayToBigInt64Array } from './utils'\nimport type { PreparedBallot, PrepareBallotInputs } from './types'\n\n/**\n * Split a 32-byte digest into the two 16-byte halves the circuit takes as public inputs.\n *\n * A Keccak digest is 256 bits and a field element holds fewer than 254, so it cannot cross the\n * circuit boundary in one piece. `crisp_lib::ecdsa::digest_from_halves` rebuilds the 32 bytes and\n * range-checks each half, and `CRISPProgram.publishInput` splits the same way.\n *\n * @param digest The 32-byte ballot digest.\n * @returns The high and low halves as hex field elements.\n */\nexport const splitDigest = (digest: `0x${string}`): { digestHi: `0x${string}`; digestLo: `0x${string}` } => {\n if (digest.length !== 66) {\n throw new Error(`Invalid digest: expected 32 bytes, got ${(digest.length - 2) / 2}`)\n }\n\n return {\n digestHi: `0x${digest.slice(2, 34)}`,\n digestLo: `0x${digest.slice(34, 66)}`,\n }\n}\n\n/**\n * Phase one of building a ballot: encrypt the vote and build every circuit input that does not\n * depend on the signature.\n *\n * Kept separate from the signature because the digest a voter signs binds the ciphertext, so the\n * ciphertext has to exist first. The returned `ctCommitment` is what `CRISPProgram.ballotDigest`\n * takes as its `ciphertextCommitment` argument.\n *\n * Kept in a separate module so it can run in a worker.\n *\n * @param inputs The ballot to prepare.\n * @returns The partial circuit inputs, the ciphertext, and its commitment.\n */\nexport const prepareCircuitInputsImpl = async (inputs: PrepareBallotInputs): Promise<PreparedBallot> => {\n const zkInputsGenerator = getZkInputsGenerator()\n\n const numOptions = inputs.isMaskVote ? inputs.numOptions : inputs.vote.length\n const zeroVote = getZeroVote(numOptions)\n const vote = inputs.isMaskVote ? zeroVote : inputs.vote\n const encodedVote = encodeVote(vote)\n\n let circuitInputs: any\n let encryptedVote: Uint8Array\n\n if (!inputs.previousCiphertext) {\n const result = await zkInputsGenerator.generateInputs(\n encryptVote(zeroVote, inputs.publicKey),\n inputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n )\n\n circuitInputs = result.inputs\n encryptedVote = result.encryptedVote\n } else {\n const result = await zkInputsGenerator.generateInputsForUpdate(\n inputs.previousCiphertext,\n inputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n )\n\n circuitInputs = result.inputs\n encryptedVote = result.encryptedVote\n }\n\n circuitInputs.slot_address = inputs.slotAddress.toLowerCase()\n circuitInputs.is_first_vote = !inputs.previousCiphertext\n circuitInputs.is_mask_vote = inputs.isMaskVote\n circuitInputs.num_options = numOptions.toString()\n\n if (inputs.censusMode === 'onchain') {\n circuitInputs.voting_power = inputs.votingPower.toString()\n } else {\n // Derived here rather than by the caller. The old API recovered the slot address from the\n // signature to build this, which is no longer possible: the signature now comes after the\n // ciphertext, and the caller states the slot address instead.\n const merkleProof = generateMerkleProof(inputs.balance, inputs.slotAddress, inputs.merkleLeaves)\n\n circuitInputs.balance = inputs.balance.toString()\n circuitInputs.merkle_root = merkleProof.proof.root.toString()\n circuitInputs.merkle_proof_length = merkleProof.length.toString()\n circuitInputs.merkle_proof_indices = merkleProof.indices.map((i) => i === 1)\n circuitInputs.merkle_proof_siblings = merkleProof.proof.siblings.map((s) => s.toString())\n }\n\n // Exported by the wasm alongside the witness. Recomputing it here would have to match\n // `compute_ciphertext_commitment` exactly, so it is carried across instead.\n const ctCommitment = `0x${BigInt(circuitInputs.ct_commitment).toString(16).padStart(64, '0')}` as `0x${string}`\n\n return { circuitInputs, encryptedVote, ctCommitment, censusMode: inputs.censusMode }\n}\n\n/**\n * Phase two: attach the signed digest to a prepared ballot.\n *\n * The digest is a public input in both branches, because `CRISPProgram.publishInput` computes it\n * for every input. A mask carries the same digest as a real vote and only skips the signature\n * check inside the circuit, which is what keeps the two indistinguishable on chain.\n *\n * @param prepared The output of `prepareCircuitInputsImpl`.\n * @param digest The digest from `CRISPProgram.ballotDigest`.\n * @param signature The signature over that digest. A mask passes the placeholder signature.\n * @returns The complete circuit inputs.\n */\nexport const attachSignatureImpl = async (prepared: PreparedBallot, digest: `0x${string}`, signature: `0x${string}`): Promise<any> => {\n const { digestHi, digestLo } = splitDigest(digest)\n const components = await extractSignatureComponents(signature, digest)\n\n const circuitInputs = prepared.circuitInputs\n circuitInputs.digest_hi = digestHi\n circuitInputs.digest_lo = digestLo\n circuitInputs.public_key_x = Array.from(components.publicKeyX).map((b) => b.toString())\n circuitInputs.public_key_y = Array.from(components.publicKeyY).map((b) => b.toString())\n circuitInputs.signature = Array.from(components.signature).map((b) => b.toString())\n\n return circuitInputs\n}\n","// SPDX-License-Identifier: LGPL-3.0-only\n//\n// Runs prepareCircuitInputs in a worker to avoid blocking the main thread\n// during CPU-heavy zk-inputs WASM (BFV encryption).\n\nimport type { PrepareBallotInputs } from '../types'\nimport { prepareCircuitInputsImpl } from '../circuitInputs'\n\nself.onmessage = async (e: MessageEvent<PrepareBallotInputs>) => {\n try {\n const prepared = await prepareCircuitInputsImpl(e.data)\n self.postMessage({ type: 'result' as const, prepared })\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err)\n const stack = err instanceof Error ? err.stack : undefined\n self.postMessage({ type: 'error' as const, error, stack })\n }\n}\n"],"mappings":";AAgBA,SAAS,yBAAyB;;;ACVlC,SAAS,iBAAiB;AAC1B,SAAS,eAAe;;;ACDxB,SAAS,mBAAmB;AAerB,IAAM,wBAAwB;AAK9B,IAAM,0BAA0B;AAEhC,IAAM,mBAAmB;AAOzB,IAAM,oBAAoB;AAC1B,IAAM,yBAAyB,YAAY,iBAAiB;;;AD1BnE,SAAS,0BAA0B;AACnC,SAAS,YAAY,wBAAwB;AAQtC,IAAM,WAAW,CAAC,SAAiB,YAA4B;AACpE,SAAO,UAAU,CAAC,QAAQ,YAAY,GAAG,OAAO,CAAC;AACnD;AAOO,IAAM,qBAAqB,CAAC,WAA8B;AAC/D,SAAO,IAAI,QAAQ,CAAC,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM;AACxD;AAQO,IAAM,sBAAsB,CAAC,SAAiB,SAAiB,WAA6C;AACjH,QAAM,OAAO,SAAS,QAAQ,YAAY,GAAG,OAAO;AAEpD,QAAM,QAAQ,OAAO,UAAU,CAAC,MAAM,OAAO,CAAC,MAAM,IAAI;AAExD,MAAI,UAAU,IAAI;AAChB,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,OAAO,mBAAmB,OAAO,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC;AAE5D,QAAM,QAAQ,KAAK,cAAc,KAAK;AAGtC,QAAM,iBAAiB,CAAC,GAAG,MAAM,UAAU,GAAG,MAAM,wBAAwB,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC;AAE3G,QAAM,UAAU,MAAM,SAAS,IAAI,CAAC,GAAG,MAAM,OAAQ,OAAO,MAAM,KAAK,KAAK,OAAO,CAAC,IAAK,EAAE,CAAC;AAC5F,QAAM,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,wBAAwB,QAAQ,MAAM,EAAE,KAAK,CAAC,CAAC;AAE3F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO;AAAA,MACL,GAAG;AAAA,MACH,UAAU;AAAA,IACZ;AAAA;AAAA,IAEA,QAAQ,MAAM,SAAS;AAAA,IACvB,SAAS;AAAA,EACX;AACF;AAOO,IAAM,WAAW,CAAC,WAA2B;AAClD,MAAI,SAAS,GAAG;AACd,UAAM,IAAI,MAAM,0BAA0B;AAAA,EAC5C;AAEA,SAAO,OAAO,SAAS,CAAC;AAC1B;AAiDO,IAAM,kBAAkB,CAAC,eAA+B;AAC7D,QAAM,cAAc,KAAK,MAAM,0BAA0B,UAAU;AACnE,SAAO,KAAK,cAAc;AAC5B;AAOO,IAAM,cAAc,CAAC,eAAiC;AAC3D,SAAO,MAAM,UAAU,EAAE,KAAK,CAAC;AACjC;AAgCO,IAAM,6BAA6B,CAAC,gBAAyC;AAClF,SAAO,cAAc,KAAK,YAAY,IAAI,MAAM,CAAC;AACnD;;;AD7JA,SAAS,cAAAA,mBAAkB;AAI3B,IAAI,qBAAoE;AAKjE,IAAM,uBAAuB,MAAM;AACxC,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,kBAAkB,aAAa;AAAA,EACtD;AACA,SAAO;AACT;AAYO,IAAM,aAAa,CAAC,SAAyB;AAClD,QAAM,aAAa,KAAK;AAExB,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAIA,MAAI,aAAa,kBAAkB;AACjC,UAAM,IAAI,MAAM,sBAAsB,UAAU,+BAA+B,gBAAgB,GAAG;AAAA,EACpG;AAEA,QAAM,YAAY,qBAAqB,EAAE,aAAa;AACtD,QAAM,SAAS,UAAU;AACzB,MAAI,SAAS,yBAAyB;AACpC,UAAM,IAAI,MAAM,eAAe,MAAM,+CAA+C,uBAAuB,GAAG;AAAA,EAChH;AAEA,QAAM,cAAc,KAAK,MAAM,0BAA0B,UAAU;AACnE,QAAM,WAAW,gBAAgB,UAAU;AAC3C,QAAM,YAAsB,CAAC;AAE7B,WAAS,YAAY,GAAG,YAAY,YAAY,aAAa,GAAG;AAC9D,UAAM,QAAQ,KAAK,SAAS;AAE5B,QAAI,QAAQ,UAAU;AACpB,YAAM,IAAI,MAAM,yBAAyB,SAAS,qBAAqB,QAAQ,GAAG;AAAA,IACpF;AAEA,UAAM,SAAS,SAAS,KAAK,EAAE,MAAM,EAAE;AAEvC,aAAS,IAAI,GAAG,IAAI,aAAa,KAAK,GAAG;AACvC,YAAM,SAAS,cAAc,OAAO;AACpC,gBAAU,KAAK,IAAI,SAAS,IAAI,SAAS,OAAO,IAAI,MAAM,GAAG,EAAE,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,QAAM,gBAAgB,cAAc;AACpC,WAAS,IAAI,eAAe,IAAI,yBAAyB,KAAK,GAAG;AAC/D,cAAU,KAAK,CAAC;AAAA,EAClB;AAEA,WAAS,IAAI,GAAG,IAAI,SAAS,yBAAyB,KAAK,GAAG;AAC5D,cAAU,KAAK,CAAC;AAAA,EAClB;AAEA,SAAO;AACT;AASO,IAAM,cAAc,CAAC,MAAY,cAAsC;AAC5E,QAAM,cAAc,WAAW,IAAI;AAEnC,SAAO,qBAAqB,EAAE,YAAY,WAAW,2BAA2B,WAAW,CAAC;AAC9F;;;AG9DO,IAAM,2BAA2B,OAAO,WAAyD;AACtG,QAAM,oBAAoB,qBAAqB;AAE/C,QAAM,aAAa,OAAO,aAAa,OAAO,aAAa,OAAO,KAAK;AACvE,QAAM,WAAW,YAAY,UAAU;AACvC,QAAM,OAAO,OAAO,aAAa,WAAW,OAAO;AACnD,QAAM,cAAc,WAAW,IAAI;AAEnC,MAAI;AACJ,MAAI;AAEJ,MAAI,CAAC,OAAO,oBAAoB;AAC9B,UAAM,SAAS,MAAM,kBAAkB;AAAA,MACrC,YAAY,UAAU,OAAO,SAAS;AAAA,MACtC,OAAO;AAAA,MACP,2BAA2B,WAAW;AAAA,IACxC;AAEA,oBAAgB,OAAO;AACvB,oBAAgB,OAAO;AAAA,EACzB,OAAO;AACL,UAAM,SAAS,MAAM,kBAAkB;AAAA,MACrC,OAAO;AAAA,MACP,OAAO;AAAA,MACP,2BAA2B,WAAW;AAAA,IACxC;AAEA,oBAAgB,OAAO;AACvB,oBAAgB,OAAO;AAAA,EACzB;AAEA,gBAAc,eAAe,OAAO,YAAY,YAAY;AAC5D,gBAAc,gBAAgB,CAAC,OAAO;AACtC,gBAAc,eAAe,OAAO;AACpC,gBAAc,cAAc,WAAW,SAAS;AAEhD,MAAI,OAAO,eAAe,WAAW;AACnC,kBAAc,eAAe,OAAO,YAAY,SAAS;AAAA,EAC3D,OAAO;AAIL,UAAM,cAAc,oBAAoB,OAAO,SAAS,OAAO,aAAa,OAAO,YAAY;AAE/F,kBAAc,UAAU,OAAO,QAAQ,SAAS;AAChD,kBAAc,cAAc,YAAY,MAAM,KAAK,SAAS;AAC5D,kBAAc,sBAAsB,YAAY,OAAO,SAAS;AAChE,kBAAc,uBAAuB,YAAY,QAAQ,IAAI,CAAC,MAAM,MAAM,CAAC;AAC3E,kBAAc,wBAAwB,YAAY,MAAM,SAAS,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC;AAAA,EAC1F;AAIA,QAAM,eAAe,KAAK,OAAO,cAAc,aAAa,EAAE,SAAS,EAAE,EAAE,SAAS,IAAI,GAAG,CAAC;AAE5F,SAAO,EAAE,eAAe,eAAe,cAAc,YAAY,OAAO,WAAW;AACrF;;;AC5FA,KAAK,YAAY,OAAO,MAAyC;AAC/D,MAAI;AACF,UAAM,WAAW,MAAM,yBAAyB,EAAE,IAAI;AACtD,SAAK,YAAY,EAAE,MAAM,UAAmB,SAAS,CAAC;AAAA,EACxD,SAAS,KAAK;AACZ,UAAM,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC7D,UAAM,QAAQ,eAAe,QAAQ,IAAI,QAAQ;AACjD,SAAK,YAAY,EAAE,MAAM,SAAkB,OAAO,MAAM,CAAC;AAAA,EAC3D;AACF;","names":["hexToBytes"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-e3/sdk",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "gnosisguild",
@@ -35,20 +35,22 @@
35
35
  "vitest": "^1.6.1"
36
36
  },
37
37
  "dependencies": {
38
- "@aztec/bb.js": "3.0.0-nightly.20260102",
39
- "@noir-lang/noir_js": "1.0.0-beta.16",
38
+ "@aztec/bb.js": "5.1.0",
39
+ "@noir-lang/noir_js": "1.0.0-beta.26",
40
40
  "@zk-kit/lean-imt": "^2.2.4",
41
41
  "poseidon-lite": "^0.3.0",
42
42
  "viem": "2.30.6",
43
- "@crisp-e3/zk-inputs": "0.15.0"
43
+ "@crisp-e3/zk-inputs": "0.17.0"
44
44
  },
45
45
  "scripts": {
46
- "compile:circuits": "pnpm compile:crisp && pnpm compile:user_data_encryption_ct0 && pnpm compile:user_data_encryption_ct1 && pnpm compile:user_data_encryption && pnpm compile:fold",
46
+ "compile:circuits": "pnpm compile:crisp && pnpm compile:crisp_onchain && pnpm compile:user_data_encryption_ct0 && pnpm compile:user_data_encryption_ct1 && pnpm compile:user_data_encryption && pnpm compile:fold && pnpm compile:fold_onchain",
47
47
  "compile:user_data_encryption_ct0": "cd ../../../../circuits/bin/threshold/user_data_encryption_ct0 && nargo compile",
48
48
  "compile:user_data_encryption_ct1": "cd ../../../../circuits/bin/threshold/user_data_encryption_ct1 && nargo compile",
49
49
  "compile:user_data_encryption": "cd ../../../../circuits/bin/threshold/user_data_encryption && nargo compile",
50
50
  "compile:crisp": "cd ../../circuits/bin/crisp && nargo compile",
51
+ "compile:crisp_onchain": "cd ../../circuits/bin/crisp_onchain && nargo compile",
51
52
  "compile:fold": "cd ../../circuits/bin/fold && nargo compile",
53
+ "compile:fold_onchain": "cd ../../circuits/bin/fold_onchain && nargo compile",
52
54
  "build:wasm": "pnpm -C ../crisp-zk-inputs build",
53
55
  "build": "pnpm build:wasm && pnpm compile:circuits && tsup",
54
56
  "test": "vitest --run"