@crisp-e3/sdk 0.19.0 → 0.19.1

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.
@@ -167,7 +167,7 @@ var prepareCircuitInputsImpl = async (inputs) => {
167
167
  // src/workers/generateCircuitInputs.worker.ts
168
168
  self.onmessage = async (e) => {
169
169
  try {
170
- if (e.data.preset) setZkInputsGeneratorPreset(e.data.preset);
170
+ setZkInputsGeneratorPreset(e.data.preset);
171
171
  const prepared = await prepareCircuitInputsImpl(e.data.inputs);
172
172
  self.postMessage({ type: "result", prepared });
173
173
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/encoding.ts","../../src/circuits.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 { registeredPreset, type CircuitPreset } from './circuits'\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\nlet _zkInputsGeneratorPreset: CircuitPreset | 'default' | null = null\nlet _zkInputsGeneratorPresetOverride: CircuitPreset | null = null\n\n/** Force a BFV preset for contexts that do not share the registered circuit bundle. */\nexport const setZkInputsGeneratorPreset = (preset: CircuitPreset): void => {\n if (_zkInputsGeneratorPresetOverride !== preset) {\n _zkInputsGenerator = null\n _zkInputsGeneratorPreset = null\n _zkInputsGeneratorPresetOverride = preset\n }\n}\n\n/**\n * Returns the singleton ZK inputs generator instance for the registered BFV preset.\n */\nexport const getZkInputsGenerator = () => {\n const preset = _zkInputsGeneratorPresetOverride ?? registeredPreset()\n const targetPreset = preset ?? 'default'\n\n if (!_zkInputsGenerator || _zkInputsGeneratorPreset !== targetPreset) {\n _zkInputsGenerator = preset ? ZKInputsGenerator.fromPreset(preset) : ZKInputsGenerator.withDefaults()\n _zkInputsGeneratorPreset = targetPreset\n }\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(\n Array.from(decryptedVote, (value) => BigInt(value)),\n numChoices,\n )\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 type { CompiledCircuit } from '@noir-lang/noir_js'\n\n/** BFV parameter sets the circuits can be compiled against. */\nexport type CircuitPreset = 'insecure-512' | 'secure-8192'\n\n/**\n * The circuits whose ABI is shaped by the BFV degree, and which therefore exist once per preset.\n *\n * The aggregation circuits — `crisp_fold`, `crisp_onchain_fold` and `user_data_encryption` — are\n * deliberately absent. Their parameters are proof and verification-key shaped (410/115 fields), not\n * polynomial shaped, so one compiled artifact serves both presets; the fold circuits assert\n * `chain_key_hash` against the insecure *or* the secure constant for exactly that reason. They ship\n * in the main entry point, which is why `verifyProof` works without a preset loaded.\n */\nexport type CircuitBundle = {\n readonly preset: CircuitPreset\n readonly crisp: CompiledCircuit\n readonly crispOnchain: CompiledCircuit\n readonly userDataEncryptionCt0: CompiledCircuit\n readonly userDataEncryptionCt1: CompiledCircuit\n}\n\nlet registered: CircuitBundle | null = null\n\n/**\n * Install the preset-bound circuits used by `generateProof`.\n *\n * The bundle is not bundled into the main entry point, because the secure-8192 artifacts are more\n * than an order of magnitude larger than the insecure-512 ones and no consumer needs both. Load the\n * one you want from its subpath and register it once at start-up:\n *\n * ```ts\n * import { setCircuits } from '@crisp-e3/sdk'\n * import { loadCircuits } from '@crisp-e3/sdk/insecure-512'\n *\n * setCircuits(await loadCircuits())\n * ```\n */\nexport const setCircuits = (bundle: CircuitBundle): void => {\n registered = bundle\n}\n\n/** The registered bundle, or `null` when none has been installed yet. */\nexport const getRegisteredCircuits = (): CircuitBundle | null => registered\n\n/** The preset currently installed, or `null` when none has been installed yet. */\nexport const registeredPreset = (): CircuitPreset | null => registered?.preset ?? null\n\n/**\n * The registered bundle, throwing a directed error when nothing has been installed.\n *\n * Proving cannot fall back to a default preset: a ballot proved against the wrong parameters fails\n * on chain rather than locally, so guessing here would move the failure somewhere much harder to\n * read.\n */\nexport const requireCircuits = (): CircuitBundle => {\n if (!registered) {\n throw new Error(\n 'No circuit preset registered. Import `loadCircuits` from \"@crisp-e3/sdk/insecure-512\" or ' +\n '\"@crisp-e3/sdk/secure-8192\" and pass the result to `setCircuits()` before proving.',\n )\n }\n\n return registered\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\n// Chain access. These let a client read the contracts CRISP already watches without holding a\n// hosted-provider key of its own — see the `/chain/*` routes on the server.\nexport const CRISP_SERVER_CHAIN_RPC_ENDPOINT = 'chain/rpc'\nexport const CRISP_SERVER_CHAIN_HEAD_ENDPOINT = 'chain/head'\nexport const CRISP_SERVER_CHAIN_READ_ENDPOINT = 'chain/read'\nexport const CRISP_SERVER_CHAIN_LOGS_ENDPOINT = 'chain/logs'\nexport const CRISP_SERVER_CHAIN_BLOCK_AT_TIMESTAMP_ENDPOINT = 'chain/block-at-timestamp'\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 } 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 * One path for all three operations. A first vote, a re-vote, and a mask reach the same generator\n * with the same arguments, differ only in `isMaskVote` — which stays private to the proof — and\n * produce the same shape of submission. Branching here would make the three tellable apart by\n * anything watching the client, which is what masks exist to prevent.\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 vote = inputs.isMaskVote ? getZeroVote(numOptions) : inputs.vote\n const encodedVote = encodeVote(vote)\n\n // Only a mask adds to what the slot already holds. A vote replaces it, so a voter cannot have\n // their old ballot counted alongside the new one. The circuit derives the same choice from\n // `is_mask_vote` and rejects any witness built the other way.\n const keepPrevious = inputs.isMaskVote && !!inputs.previousCiphertext\n\n const { inputs: circuitInputs, encryptedVote } = await zkInputsGenerator.generateInputs(\n inputs.previousCiphertext,\n inputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n keepPrevious,\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 // The commitment to `encryptedVote`, which is the ciphertext this ballot publishes: the ballot\n // itself for a vote or a re-vote, the slot plus the zero ballot for a mask over an occupied slot.\n // The circuit returns the same value as `final_ct_commitment`, `CRISPProgram` stores it, and\n // `CRISPProgram.ballotDigest` is built over it — so it is what a voter has to sign, and it has to\n // be known before proving because the digest is itself a circuit input.\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.sum_ct_commitment).toString(16).padStart(64, '0')}` as `0x${string}`\n\n // Zero when there is nothing to extend, which is what the contract reads as `is_first_vote`.\n //\n // Checked at runtime as well as in the type, because a caller reaching this through plain\n // JavaScript or a widened object gets no type error. Defaulting a missing index to zero would\n // name the slot's first entry as the parent, and the proof would be built against one commitment\n // while the contract supplied another — visible only as a rejected proof.\n if (inputs.previousCiphertext !== undefined) {\n const index = inputs.previousIndex\n // Non-negative and safe, not merely an integer. `-1` would come back out as zero, which the\n // contract reads as \"extends nothing\" — a re-vote silently published as a first vote against a\n // slot that already holds one. Anything at or above `MAX_SAFE_INTEGER` cannot represent\n // `index + 1` exactly, so the parent it names is not the parent it meant.\n if (!Number.isSafeInteger(index) || (index as number) < 0 || (index as number) + 1 > Number.MAX_SAFE_INTEGER) {\n throw new Error(\n `previousCiphertext needs a non-negative safe integer previousIndex; got ${String(index)}. Pass the slot head as a pair.`,\n )\n }\n }\n\n const parentIndexPlusOne = inputs.previousCiphertext !== undefined ? (inputs.previousIndex as number) + 1 : 0\n\n return { circuitInputs, encryptedVote, ctCommitment, parentIndexPlusOne, 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 type { CircuitPreset } from '../circuits'\nimport { prepareCircuitInputsImpl } from '../circuitInputs'\nimport { setZkInputsGeneratorPreset } from '../encoding'\n\ntype GenerateCircuitInputsRequest = {\n inputs: PrepareBallotInputs\n preset: CircuitPreset | null\n}\n\nself.onmessage = async (e: MessageEvent<GenerateCircuitInputsRequest>) => {\n try {\n if (e.data.preset) setZkInputsGeneratorPreset(e.data.preset)\n const prepared = await prepareCircuitInputsImpl(e.data.inputs)\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":"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","names":["hexToBytes"]}
1
+ {"version":3,"sources":["../../src/encoding.ts","../../src/circuits.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 { registeredPreset, type CircuitPreset } from './circuits'\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\nlet _zkInputsGeneratorPreset: CircuitPreset | 'default' | null = null\nlet _zkInputsGeneratorPresetOverride: CircuitPreset | null = null\n\n/** Set or clear the BFV preset override for contexts that do not share the registered bundle. */\nexport const setZkInputsGeneratorPreset = (preset: CircuitPreset | null): void => {\n if (_zkInputsGeneratorPresetOverride !== preset) {\n _zkInputsGenerator = null\n _zkInputsGeneratorPreset = null\n _zkInputsGeneratorPresetOverride = preset\n }\n}\n\n/**\n * Returns the singleton ZK inputs generator instance for the registered BFV preset.\n */\nexport const getZkInputsGenerator = () => {\n const preset = _zkInputsGeneratorPresetOverride ?? registeredPreset()\n const targetPreset = preset ?? 'default'\n\n if (!_zkInputsGenerator || _zkInputsGeneratorPreset !== targetPreset) {\n _zkInputsGenerator = preset ? ZKInputsGenerator.fromPreset(preset) : ZKInputsGenerator.withDefaults()\n _zkInputsGeneratorPreset = targetPreset\n }\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(\n Array.from(decryptedVote, (value) => BigInt(value)),\n numChoices,\n )\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 type { CompiledCircuit } from '@noir-lang/noir_js'\n\n/** BFV parameter sets the circuits can be compiled against. */\nexport type CircuitPreset = 'insecure-512' | 'secure-8192'\n\n/**\n * The circuits whose ABI is shaped by the BFV degree, and which therefore exist once per preset.\n *\n * The aggregation circuits — `crisp_fold`, `crisp_onchain_fold` and `user_data_encryption` — are\n * deliberately absent. Their parameters are proof and verification-key shaped (410/115 fields), not\n * polynomial shaped, so one compiled artifact serves both presets; the fold circuits assert\n * `chain_key_hash` against the insecure *or* the secure constant for exactly that reason. They ship\n * in the main entry point, which is why `verifyProof` works without a preset loaded.\n */\nexport type CircuitBundle = {\n readonly preset: CircuitPreset\n readonly crisp: CompiledCircuit\n readonly crispOnchain: CompiledCircuit\n readonly userDataEncryptionCt0: CompiledCircuit\n readonly userDataEncryptionCt1: CompiledCircuit\n}\n\nlet registered: CircuitBundle | null = null\n\n/**\n * Install the preset-bound circuits used by `generateProof`.\n *\n * The bundle is not bundled into the main entry point, because the secure-8192 artifacts are more\n * than an order of magnitude larger than the insecure-512 ones and no consumer needs both. Load the\n * one you want from its subpath and register it once at start-up:\n *\n * ```ts\n * import { setCircuits } from '@crisp-e3/sdk'\n * import { loadCircuits } from '@crisp-e3/sdk/insecure-512'\n *\n * setCircuits(await loadCircuits())\n * ```\n */\nexport const setCircuits = (bundle: CircuitBundle): void => {\n registered = bundle\n}\n\n/** The registered bundle, or `null` when none has been installed yet. */\nexport const getRegisteredCircuits = (): CircuitBundle | null => registered\n\n/** The preset currently installed, or `null` when none has been installed yet. */\nexport const registeredPreset = (): CircuitPreset | null => registered?.preset ?? null\n\n/**\n * The registered bundle, throwing a directed error when nothing has been installed.\n *\n * Proving cannot fall back to a default preset: a ballot proved against the wrong parameters fails\n * on chain rather than locally, so guessing here would move the failure somewhere much harder to\n * read.\n */\nexport const requireCircuits = (): CircuitBundle => {\n if (!registered) {\n throw new Error(\n 'No circuit preset registered. Import `loadCircuits` from \"@crisp-e3/sdk/insecure-512\" or ' +\n '\"@crisp-e3/sdk/secure-8192\" and pass the result to `setCircuits()` before proving.',\n )\n }\n\n return registered\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\n// Chain access. These let a client read the contracts CRISP already watches without holding a\n// hosted-provider key of its own — see the `/chain/*` routes on the server.\nexport const CRISP_SERVER_CHAIN_RPC_ENDPOINT = 'chain/rpc'\nexport const CRISP_SERVER_CHAIN_HEAD_ENDPOINT = 'chain/head'\nexport const CRISP_SERVER_CHAIN_READ_ENDPOINT = 'chain/read'\nexport const CRISP_SERVER_CHAIN_LOGS_ENDPOINT = 'chain/logs'\nexport const CRISP_SERVER_CHAIN_BLOCK_AT_TIMESTAMP_ENDPOINT = 'chain/block-at-timestamp'\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 } 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 * One path for all three operations. A first vote, a re-vote, and a mask reach the same generator\n * with the same arguments, differ only in `isMaskVote` — which stays private to the proof — and\n * produce the same shape of submission. Branching here would make the three tellable apart by\n * anything watching the client, which is what masks exist to prevent.\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 vote = inputs.isMaskVote ? getZeroVote(numOptions) : inputs.vote\n const encodedVote = encodeVote(vote)\n\n // Only a mask adds to what the slot already holds. A vote replaces it, so a voter cannot have\n // their old ballot counted alongside the new one. The circuit derives the same choice from\n // `is_mask_vote` and rejects any witness built the other way.\n const keepPrevious = inputs.isMaskVote && !!inputs.previousCiphertext\n\n const { inputs: circuitInputs, encryptedVote } = await zkInputsGenerator.generateInputs(\n inputs.previousCiphertext,\n inputs.publicKey,\n numberArrayToBigInt64Array(encodedVote),\n keepPrevious,\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 // The commitment to `encryptedVote`, which is the ciphertext this ballot publishes: the ballot\n // itself for a vote or a re-vote, the slot plus the zero ballot for a mask over an occupied slot.\n // The circuit returns the same value as `final_ct_commitment`, `CRISPProgram` stores it, and\n // `CRISPProgram.ballotDigest` is built over it — so it is what a voter has to sign, and it has to\n // be known before proving because the digest is itself a circuit input.\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.sum_ct_commitment).toString(16).padStart(64, '0')}` as `0x${string}`\n\n // Zero when there is nothing to extend, which is what the contract reads as `is_first_vote`.\n //\n // Checked at runtime as well as in the type, because a caller reaching this through plain\n // JavaScript or a widened object gets no type error. Defaulting a missing index to zero would\n // name the slot's first entry as the parent, and the proof would be built against one commitment\n // while the contract supplied another — visible only as a rejected proof.\n if (inputs.previousCiphertext !== undefined) {\n const index = inputs.previousIndex\n // Non-negative and safe, not merely an integer. `-1` would come back out as zero, which the\n // contract reads as \"extends nothing\" — a re-vote silently published as a first vote against a\n // slot that already holds one. Anything at or above `MAX_SAFE_INTEGER` cannot represent\n // `index + 1` exactly, so the parent it names is not the parent it meant.\n if (!Number.isSafeInteger(index) || (index as number) < 0 || (index as number) + 1 > Number.MAX_SAFE_INTEGER) {\n throw new Error(\n `previousCiphertext needs a non-negative safe integer previousIndex; got ${String(index)}. Pass the slot head as a pair.`,\n )\n }\n }\n\n const parentIndexPlusOne = inputs.previousCiphertext !== undefined ? (inputs.previousIndex as number) + 1 : 0\n\n return { circuitInputs, encryptedVote, ctCommitment, parentIndexPlusOne, 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 type { CircuitPreset } from '../circuits'\nimport { prepareCircuitInputsImpl } from '../circuitInputs'\nimport { setZkInputsGeneratorPreset } from '../encoding'\n\ntype GenerateCircuitInputsRequest = {\n inputs: PrepareBallotInputs\n preset: CircuitPreset | null\n}\n\nself.onmessage = async (e: MessageEvent<GenerateCircuitInputsRequest>) => {\n try {\n setZkInputsGeneratorPreset(e.data.preset)\n const prepared = await prepareCircuitInputsImpl(e.data.inputs)\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":"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","names":["hexToBytes"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-e3/sdk",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "gnosisguild",
@@ -51,7 +51,7 @@
51
51
  "@zk-kit/lean-imt": "^2.2.4",
52
52
  "poseidon-lite": "^0.3.0",
53
53
  "viem": "2.30.6",
54
- "@crisp-e3/zk-inputs": "0.19.0"
54
+ "@crisp-e3/zk-inputs": "0.19.1"
55
55
  },
56
56
  "scripts": {
57
57
  "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",
@@ -70,7 +70,8 @@
70
70
  "stage:preset": "node ../../scripts/stage-preset-artifacts.mjs",
71
71
  "build:presets": "node ../../scripts/build-presets.mjs",
72
72
  "check:presets": "node ../../scripts/check-presets.mjs",
73
- "build:testing": "pnpm build:wasm && CRISP_PRESET=insecure-512 tsup",
74
- "build:prod": "pnpm build:wasm && CRISP_PRESET=secure-8192 tsup"
73
+ "build:testing": "pnpm build:wasm && pnpm check:staged insecure-512 && CRISP_PRESET=insecure-512 tsup",
74
+ "build:prod": "pnpm build:wasm && pnpm check:staged secure-8192 && CRISP_PRESET=secure-8192 tsup",
75
+ "check:staged": "node ../../scripts/check-staged-preset.mjs"
75
76
  }
76
77
  }