@crisp-e3/sdk 0.27.0-insecure.0 → 0.27.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.
- package/README.md +23 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +38 -4
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/presets/insecure-64.js +2 -2
- package/dist/presets/secure-8192.d.ts +7 -0
- package/dist/presets/secure-8192.js +20 -6
- package/dist/workers/generateCircuitInputs.worker.js.map +1 -1
- package/package.json +2 -2
|
@@ -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 * A ballot stores one integer per option: coefficient `o` of the message polynomial is the\n * weight on option `o`, and every other coefficient up to the BFV degree is zero. BFV adds\n * ballots coefficient by coefficient, so the decrypted tally holds one total per option in the\n * same positions. Supports encoding, encryption, decryption, and tally decoding.\n */\n\nimport { ZKInputsGenerator } from '@crisp-e3/zk-inputs'\nimport { registeredPreset, type CircuitPreset } from './circuits'\nimport { numberArrayToBigInt64Array, decodeBytesToBigInts } 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 * Checks every choice weight against the plaintext modulus `t`. A weight at or above `t` wraps\n * around in the plaintext ring and no longer adds as an integer.\n *\n * @throws If a weight is not a non-negative safe integer below `t`\n */\nexport const checkVoteWeights = (vote: Vote, plaintextModulus: bigint): void => {\n vote.forEach((value, choiceIdx) => {\n if (!Number.isSafeInteger(value) || value < 0 || BigInt(value) >= plaintextModulus) {\n throw new Error(`Vote value for choice ${choiceIdx} must be a non-negative integer below the plaintext modulus (${plaintextModulus})`)\n }\n })\n}\n\n/**\n * Encodes vote choices into a polynomial coefficient array for BFV encryption.\n *\n * @param vote - Weight per option, for example [10, 0] for 2 options. Each weight is a non-negative\n * safe integer below the BFV plaintext modulus.\n * @returns The coefficients, `degree` entries long\n * @throws If vote has fewer than 2 or more than MAX_VOTE_OPTIONS choices, any weight is not a\n * non-negative safe integer below the plaintext modulus, or the BFV 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 circuit asserts num_options <= MAX_OPTIONS, so a larger vote can never be proved.\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 { degree, plaintextModulus } = getZkInputsGenerator().getBFVParams()\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 checkVoteWeights(vote, plaintextModulus)\n\n return [...vote, ...new Array(degree - numChoices).fill(0)]\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 one total per choice: the first `numChoices` of the\n * MAX_MSG_NON_ZERO_COEFFS published coefficients, in the layout `encodeVote` produces. Same layout as\n * `CRISPProgram.decodeTally` (Solidity) and `crisp_utils::decode_tally` (Rust).\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, as bigint because each coefficient is a uint64 word\n * @throws If numChoices is outside 2..MAX_VOTE_OPTIONS or not an integer, or there are fewer\n * coefficients than MAX_MSG_NON_ZERO_COEFFS\n */\nexport const decodeTally = (tallyBytes: string | number[] | bigint[], numChoices: number): TallyResult => {\n // CRISPProgram.validate rejects a round outside 2..MAX_VOTE_OPTIONS. Number.isInteger also screens\n // out NaN, Infinity, and fractions.\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 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 return coefficients.slice(0, numChoices)\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-64' | '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-64 ones. Load the preset selected by the\n * round from its subpath and register it before proving:\n *\n * ```ts\n * import { setCircuits } from '@crisp-e3/sdk'\n * import { loadCircuits } from '@crisp-e3/sdk/insecure-64'\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-64\" 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 { 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/** Parse a Merkle leaf returned by the CRISP server or supplied as an integer. */\nconst parseMerkleLeaf = (leaf: bigint | string): bigint => {\n if (typeof leaf === 'bigint' || leaf.startsWith('0x')) return BigInt(leaf)\n\n // The server's persisted tree format is a fixed-width, unprefixed hexadecimal string.\n // Keep accepting decimal strings for callers that used the older SDK input shape.\n return /^[0-9a-fA-F]{64}$/.test(leaf) ? BigInt(`0x${leaf}`) : BigInt(leaf)\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 const parsedLeaves = leaves.map(parseMerkleLeaf)\n\n const index = parsedLeaves.findIndex((candidate) => candidate === leaf)\n\n if (index === -1) {\n throw new Error('Leaf not found in the tree')\n }\n\n const tree = generateMerkleTree(parsedLeaves)\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 * 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 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","// 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_AVAILABILITY_ENDPOINT = 'voting/availability'\nexport const CRISP_SERVER_VOTING_STATUS_ENDPOINT = 'voting/status'\nexport const CRISP_SERVER_VOTING_SELECTION_ENDPOINT = 'voting/selection'\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// The committee publishes this many plaintext coefficients as the tally; option `o` is coefficient `o`.\nexport const MAX_MSG_NON_ZERO_COEFFS = 50\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"]}
|
|
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 * A ballot stores one integer per option: coefficient `o` of the message polynomial is the\n * weight on option `o`, and every other coefficient up to the BFV degree is zero. BFV adds\n * ballots coefficient by coefficient, so the decrypted tally holds one total per option in the\n * same positions. Supports encoding, encryption, decryption, and tally decoding.\n */\n\nimport { ZKInputsGenerator } from '@crisp-e3/zk-inputs'\nimport { registeredPreset, type CircuitPreset } from './circuits'\nimport { numberArrayToBigInt64Array, decodeBytesToBigInts } 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 * Checks every choice weight against the plaintext modulus `t`. A weight at or above `t` wraps\n * around in the plaintext ring and no longer adds as an integer.\n *\n * @throws If a weight is not a non-negative safe integer below `t`\n */\nexport const checkVoteWeights = (vote: Vote, plaintextModulus: bigint): void => {\n vote.forEach((value, choiceIdx) => {\n if (!Number.isSafeInteger(value) || value < 0 || BigInt(value) >= plaintextModulus) {\n throw new Error(`Vote value for choice ${choiceIdx} must be a non-negative integer below the plaintext modulus (${plaintextModulus})`)\n }\n })\n}\n\n/**\n * Encodes vote choices into a polynomial coefficient array for BFV encryption.\n *\n * @param vote - Weight per option, for example [10, 0] for 2 options. Each weight is a non-negative\n * safe integer below the BFV plaintext modulus.\n * @returns The coefficients, `degree` entries long\n * @throws If vote has fewer than 2 or more than MAX_VOTE_OPTIONS choices, any weight is not a\n * non-negative safe integer below the plaintext modulus, or the BFV 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 circuit asserts num_options <= MAX_OPTIONS, so a larger vote can never be proved.\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 { degree, plaintextModulus } = getZkInputsGenerator().getBFVParams()\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 checkVoteWeights(vote, plaintextModulus)\n\n return [...vote, ...new Array(degree - numChoices).fill(0)]\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 one total per choice: the first `numChoices` of the\n * MAX_MSG_NON_ZERO_COEFFS published coefficients, in the layout `encodeVote` produces. Same layout as\n * `CRISPProgram.decodeTally` (Solidity) and `crisp_utils::decode_tally` (Rust).\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, as bigint because each coefficient is a uint64 word\n * @throws If numChoices is outside 2..MAX_VOTE_OPTIONS or not an integer, or there are fewer\n * coefficients than MAX_MSG_NON_ZERO_COEFFS\n */\nexport const decodeTally = (tallyBytes: string | number[] | bigint[], numChoices: number): TallyResult => {\n // CRISPProgram.validate rejects a round outside 2..MAX_VOTE_OPTIONS. Number.isInteger also screens\n // out NaN, Infinity, and fractions.\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 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 return coefficients.slice(0, numChoices)\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-64' | '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-64 ones. Load the preset selected by the\n * round from its subpath and register it before proving:\n *\n * ```ts\n * import { setCircuits } from '@crisp-e3/sdk'\n * import { loadCircuits } from '@crisp-e3/sdk/insecure-64'\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-64\" 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 { 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/** Parse a Merkle leaf returned by the CRISP server or supplied as an integer. */\nconst parseMerkleLeaf = (leaf: bigint | string): bigint => {\n if (typeof leaf === 'bigint' || leaf.startsWith('0x')) return BigInt(leaf)\n\n // The server's persisted tree format is a fixed-width, unprefixed hexadecimal string.\n // Keep accepting decimal strings for callers that used the older SDK input shape.\n return /^[0-9a-fA-F]{64}$/.test(leaf) ? BigInt(`0x${leaf}`) : BigInt(leaf)\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 const parsedLeaves = leaves.map(parseMerkleLeaf)\n\n const index = parsedLeaves.findIndex((candidate) => candidate === leaf)\n\n if (index === -1) {\n throw new Error('Leaf not found in the tree')\n }\n\n const tree = generateMerkleTree(parsedLeaves)\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 * 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 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","// 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'\nimport type { ProofStage } from './types'\n\n/** The proofs `finishBallotProof` produces, in order. See {@link ProofStage}. */\nexport const PROOF_STAGES: readonly ProofStage[] = ['encryption_ct0', 'encryption_ct1', 'ballot', 'encryption', 'fold']\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_AVAILABILITY_ENDPOINT = 'voting/availability'\nexport const CRISP_SERVER_VOTING_STATUS_ENDPOINT = 'voting/status'\nexport const CRISP_SERVER_VOTING_SELECTION_ENDPOINT = 'voting/selection'\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// The committee publishes this many plaintext coefficients as the tally; option `o` is coefficient `o`.\nexport const MAX_MSG_NON_ZERO_COEFFS = 50\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.27.0
|
|
3
|
+
"version": "0.27.0",
|
|
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.27.0
|
|
54
|
+
"@crisp-e3/zk-inputs": "0.27.0"
|
|
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",
|