From 4dba8825c045fee021cf85e3d41f9a8f0a1e0811 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 13:10:16 +0100 Subject: [PATCH 01/14] test: add real encoded tally --- .../CRISP/packages/crisp-sdk/src/constants.ts | 2 +- .../CRISP/packages/crisp-sdk/src/types.ts | 4 +- examples/CRISP/packages/crisp-sdk/src/vote.ts | 72 +++++++--- .../packages/crisp-sdk/tests/vote.test.ts | 129 ++---------------- 4 files changed, 72 insertions(+), 135 deletions(-) diff --git a/examples/CRISP/packages/crisp-sdk/src/constants.ts b/examples/CRISP/packages/crisp-sdk/src/constants.ts index ef83a32cbc..6ae052f1f5 100644 --- a/examples/CRISP/packages/crisp-sdk/src/constants.ts +++ b/examples/CRISP/packages/crisp-sdk/src/constants.ts @@ -21,7 +21,7 @@ export const HALF_LARGEST_MINIMUM_DEGREE = 50 * This is the maximum value for a vote (Yes or No). This is 2^50 - 1 * The minimum degree that BFV should use is 100 (to accommodate both Yes and No votes) */ -export const MAXIMUM_VOTE_VALUE = BigInt(Math.pow(2, HALF_LARGEST_MINIMUM_DEGREE) - 1) +export const MAXIMUM_VOTE_VALUE = Math.pow(2, HALF_LARGEST_MINIMUM_DEGREE) - 1 /** * Message used by users to prove ownership of their Ethereum account diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index 5a63ddd2af..dc0c34c40c 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -71,11 +71,11 @@ export type Vote = { /** * The voting power for 'yes' votes */ - yes: bigint + yes: number /** * The voting power for 'no' votes */ - no: bigint + no: number } /** diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 5e9c984a9d..199cc5a7e4 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -11,7 +11,7 @@ import { MAXIMUM_VOTE_VALUE, HALF_LARGEST_MINIMUM_DEGREE, MASK_SIGNATURE } from import { Noir, type CompiledCircuit } from '@noir-lang/noir_js' import { UltraHonkBackend, type ProofData } from '@aztec/bb.js' import circuit from '../../../circuits/target/crisp_circuit.json' -import { bytesToHex, encodeAbiParameters, parseAbiParameters, numberToHex, getAddress } from 'viem/utils' +import { bytesToHex, encodeAbiParameters, parseAbiParameters, numberToHex, getAddress, hexToBytes } from 'viem/utils' import { Hex } from 'viem' // Initialize the ZKInputsGenerator. @@ -28,8 +28,8 @@ export const encodeVote = (vote: Vote): BigInt64Array => { const voteArray = [] const length = bfvParams.degree const halfLength = length / 2 - const yesBinary = toBinary(vote.yes).split('') - const noBinary = toBinary(vote.no).split('') + const yesBinary = toBinary(BigInt(vote.yes)).split('') + const noBinary = toBinary(BigInt(vote.no)).split('') // Fill first half with 'yes' binary representation (pad with leading 0s if needed) for (let i = 0; i < halfLength; i++) { @@ -46,33 +46,69 @@ export const encodeVote = (vote: Vote): BigInt64Array => { return BigInt64Array.from(voteArray.map(BigInt)) } +/** + * Decode bytes to numbers array (little-endian, 8 bytes per value). + * @param data The bytes to decode (must be multiple of 8). + * @returns Array of numbers. + */ +const decodeBytesToNumbers = (data: Uint8Array): number[] => { + if (data.length % 8 !== 0) { + throw new Error('Data length must be multiple of 8') + } + + const arrayLength = data.length / 8 + const result: number[] = [] + + for (let i = 0; i < arrayLength; i++) { + const offset = i * 8 + let value = 0 + + // Read 8 bytes in little-endian order + for (let j = 0; j < 8; j++) { + const byteValue = data[offset + j] + value |= byteValue << (j * 8) + } + + result.push(value) + } + + return result +} + /** * Decode an encoded tally into its decimal representation. - * @param tally The encoded tally to decode. + * @param tallyBytes The encoded tally as a hex string (bytes). * @returns The decoded tally as an IVote. */ -export const decodeTally = (tally: string[]): Vote => { - const HALF_D = tally.length / 2 +export const decodeTally = (tallyBytes: string): Vote => { + // Convert hex string to bytes, handling both with and without 0x prefix + const hexString = tallyBytes.startsWith('0x') ? tallyBytes : `0x${tallyBytes}` + const bytes = hexToBytes(hexString as Hex) + + // Decode bytes to numbers array + const numbers = decodeBytesToNumbers(bytes) + + const HALF_D = numbers.length / 2 const START_INDEX_Y = HALF_D - HALF_LARGEST_MINIMUM_DEGREE - const START_INDEX_N = tally.length - HALF_LARGEST_MINIMUM_DEGREE + const START_INDEX_N = numbers.length - HALF_LARGEST_MINIMUM_DEGREE // Extract only the relevant parts of the tally - const yesBinary = tally.slice(START_INDEX_Y, HALF_D) - const noBinary = tally.slice(START_INDEX_N, tally.length) + const yesBinary = numbers.slice(START_INDEX_Y, HALF_D) + const noBinary = numbers.slice(START_INDEX_N, numbers.length) - let yes = 0n - let no = 0n + let yes = 0 + let no = 0 // Convert yes votes (from START_INDEX_Y to HALF_D) for (let i = 0; i < yesBinary.length; i += 1) { - const weight = 2n ** BigInt(yesBinary.length - 1 - i) - yes += BigInt(yesBinary[i]) * weight + const weight = 2 ** (yesBinary.length - 1 - i) + yes += yesBinary[i] * weight } // Convert no votes (from START_INDEX_N to D) for (let i = 0; i < noBinary.length; i += 1) { - const weight = 2n ** BigInt(noBinary.length - 1 - i) - no += BigInt(noBinary[i]) * weight + const weight = 2 ** (noBinary.length - 1 - i) + no += noBinary[i] * weight } return { @@ -98,7 +134,7 @@ export const generateCircuitInputs = async (proofInputs: ProofInputs): Promise { throw new Error('Invalid vote: vote exceeds maximum allowed value') } - if (voteProofInputs.vote.yes < 0n || voteProofInputs.vote.no < 0n) { + if (voteProofInputs.vote.yes < 0 || voteProofInputs.vote.no < 0) { throw new Error('Invalid vote: vote is negative') } @@ -173,7 +209,7 @@ export const generateMaskVoteProof = async (maskVoteProofInputs: MaskVoteProofIn const crispInputs = await generateCircuitInputs({ ...maskVoteProofInputs, signature: MASK_SIGNATURE, - vote: { yes: 0n, no: 0n }, + vote: { yes: 0, no: 0 }, merkleProof, }) diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 2cfd5d5a33..0739328e2b 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -34,7 +34,7 @@ describe('Vote', () => { // Setup the test environment. beforeAll(async () => { - vote = { yes: 10n, no: 0n } + vote = { yes: 10, no: 0 } signature = await signMessage({ message: SIGNATURE_MESSAGE, privateKey: ECDSA_PRIVATE_KEY }) balance = 100n address = publicKeyToAddress(await recoverPublicKey({ hash: SIGNATURE_MESSAGE_HASH, signature })) @@ -46,139 +46,40 @@ describe('Vote', () => { describe('decodeTally', () => { it('Should decode an encoded tally into its decimal representation', () => { - const tally = [ - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '5', - '0', - '2', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '0', - '1', - ] + const tally = + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' const decoded = decodeTally(tally) - expect(decoded.yes).toBe(22n) - expect(decoded.no).toBe(1n) + expect(decoded.yes).toBe(2) + expect(decoded.no).toBe(4) }) }) describe('encodeVote', () => { - const decodeHalf = (encoded: BigInt64Array, isFirstHalf: boolean): bigint => { + const decodeHalf = (encoded: BigInt64Array, isFirstHalf: boolean): number => { const halfLength = encoded.length / 2 const half = Array.from(isFirstHalf ? encoded.slice(0, halfLength) : encoded.slice(halfLength)) const binaryString = half.map((b) => b.toString()).join('') const trimmedBinary = binaryString.replace(/^0+/, '') || '0' - return BigInt('0b' + trimmedBinary) + return parseInt(trimmedBinary, 2) } it('Should encode yes vote correctly in the first half', () => { - const encoded = encodeVote({ yes: 10n, no: 0n }) + const encoded = encodeVote({ yes: 10, no: 2 }) - expect(decodeHalf(encoded, true)).toBe(10n) + expect(decodeHalf(encoded, true)).toBe(10) + expect(decodeHalf(encoded, false)).toBe(2) }) it('Should encode no vote correctly in the second half', () => { - const encoded = encodeVote({ yes: 0n, no: 5n }) + const encoded = encodeVote({ yes: 0, no: 5 }) - expect(decodeHalf(encoded, false)).toBe(5n) + expect(decodeHalf(encoded, false)).toBe(5) }) it('Should only contain binary digits (0 or 1)', () => { - const encoded = encodeVote({ yes: 255n, no: 128n }) + const encoded = encodeVote({ yes: 255, no: 128 }) expect(Array.from(encoded).every((b) => b >= 0n && b <= 1n)).toBe(true) }) @@ -221,7 +122,7 @@ describe('Vote', () => { // Using generateCircuitInputs directly to check the output of the circuit. const merkleProof = generateMerkleProof(balance, slotAddress, LEAVES) const crispInputs = await generateCircuitInputs({ - vote: { yes: 0n, no: 0n }, + vote: { yes: 0, no: 0 }, publicKey, previousCiphertext, signature: MASK_SIGNATURE, @@ -252,7 +153,7 @@ describe('Vote', () => { // Using generateCircuitInputs directly to check the output of the circuit. const merkleProof = generateMerkleProof(balance, slotAddress, LEAVES) const crispInputs = await generateCircuitInputs({ - vote: { yes: 0n, no: 0n }, + vote: { yes: 0, no: 0 }, publicKey, signature: MASK_SIGNATURE, merkleProof, From d514714f279cf6ebe0ef47eb9dce7a28800d64a5 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 16:26:39 +0100 Subject: [PATCH 02/14] test: update test to use real FHE computation output --- examples/CRISP/packages/crisp-sdk/tests/vote.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 0739328e2b..cef0a84584 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -46,13 +46,14 @@ describe('Vote', () => { describe('decodeTally', () => { it('Should decode an encoded tally into its decimal representation', () => { + // Output of a real FHE computation. const tally = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' const decoded = decodeTally(tally) - expect(decoded.yes).toBe(2) - expect(decoded.no).toBe(4) + expect(decoded.yes).toBe(10000000000) + expect(decoded.no).toBe(30000000000) }) }) From 08b253aa0bec9ba078636dabd13831bfeec058c9 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 16:52:12 +0100 Subject: [PATCH 03/14] refactor: update decodeTally to use entire halves --- examples/CRISP/packages/crisp-sdk/src/vote.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 199cc5a7e4..874f22ae23 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -7,7 +7,7 @@ import { ZKInputsGenerator } from '@crisp-e3/zk-inputs' import { type CircuitInputs, type Vote, ExecuteCircuitResult, MaskVoteProofInputs, ProofInputs, VoteProofInputs } from './types' import { generateMerkleProof, toBinary, extractSignatureComponents, getAddressFromSignature, getOptimalThreadCount } from './utils' -import { MAXIMUM_VOTE_VALUE, HALF_LARGEST_MINIMUM_DEGREE, MASK_SIGNATURE } from './constants' +import { MAXIMUM_VOTE_VALUE, MASK_SIGNATURE } from './constants' import { Noir, type CompiledCircuit } from '@noir-lang/noir_js' import { UltraHonkBackend, type ProofData } from '@aztec/bb.js' import circuit from '../../../circuits/target/crisp_circuit.json' @@ -89,23 +89,22 @@ export const decodeTally = (tallyBytes: string): Vote => { const numbers = decodeBytesToNumbers(bytes) const HALF_D = numbers.length / 2 - const START_INDEX_Y = HALF_D - HALF_LARGEST_MINIMUM_DEGREE - const START_INDEX_N = numbers.length - HALF_LARGEST_MINIMUM_DEGREE - // Extract only the relevant parts of the tally - const yesBinary = numbers.slice(START_INDEX_Y, HALF_D) - const noBinary = numbers.slice(START_INDEX_N, numbers.length) + // Extract the first half for yes votes and second half for no votes + // Votes are right-aligned with leading zeros, so we can use the entire halves + const yesBinary = numbers.slice(0, HALF_D) + const noBinary = numbers.slice(HALF_D, numbers.length) let yes = 0 let no = 0 - // Convert yes votes (from START_INDEX_Y to HALF_D) + // Convert yes votes (entire first half) for (let i = 0; i < yesBinary.length; i += 1) { const weight = 2 ** (yesBinary.length - 1 - i) yes += yesBinary[i] * weight } - // Convert no votes (from START_INDEX_N to D) + // Convert no votes (entire second half) for (let i = 0; i < noBinary.length; i += 1) { const weight = 2 ** (noBinary.length - 1 - i) no += noBinary[i] * weight From fe99f60be06e5edf895520efc4e95911fd09cadb Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 17:10:27 +0100 Subject: [PATCH 04/14] fix: add correct decoding function for rust --- examples/CRISP/Cargo.lock | 10 +++ examples/CRISP/Cargo.toml | 4 +- examples/CRISP/crates/crisp-utils/Cargo.toml | 13 ++++ examples/CRISP/crates/crisp-utils/src/lib.rs | 72 +++++++++++++++++++ .../packages/crisp-sdk/tests/vote.test.ts | 4 +- examples/CRISP/server/Cargo.toml | 1 + examples/CRISP/server/src/server/indexer.rs | 29 +++----- 7 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 examples/CRISP/crates/crisp-utils/Cargo.toml create mode 100644 examples/CRISP/crates/crisp-utils/src/lib.rs diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 082a9e465d..35a5c85074 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2031,6 +2031,7 @@ dependencies = [ "clap", "config", "crisp-constants", + "crisp-utils", "derivative", "dialoguer", "dotenvy", @@ -2063,6 +2064,15 @@ dependencies = [ "e3-sdk", ] +[[package]] +name = "crisp-utils" +version = "0.1.0" +dependencies = [ + "e3-sdk", + "eyre", + "hex", +] + [[package]] name = "crisp-zk-inputs" version = "0.1.0" diff --git a/examples/CRISP/Cargo.toml b/examples/CRISP/Cargo.toml index 82acc35d76..b75fa344e6 100644 --- a/examples/CRISP/Cargo.toml +++ b/examples/CRISP/Cargo.toml @@ -6,7 +6,8 @@ members = [ "crates/zk-inputs", "crates/zk-inputs-wasm", "crates/evm_helpers", - "crates/crisp-constants" + "crates/crisp-constants", + "crates/crisp-utils" ] resolver = "3" @@ -20,6 +21,7 @@ repository = "https://github.com/gnosisguild/enclave" [workspace.dependencies] e3-user-program = { path = "./program" } crisp-constants = { path = "./crates/crisp-constants" } +crisp-utils = { path = "./crates/crisp-utils" } alloy = { version = "=1.0.41", features = ["full", "rpc-types-eth"] } alloy-primitives = { version = "=1.3.0", default-features = false, features = [ diff --git a/examples/CRISP/crates/crisp-utils/Cargo.toml b/examples/CRISP/crates/crisp-utils/Cargo.toml new file mode 100644 index 0000000000..366fc8a83a --- /dev/null +++ b/examples/CRISP/crates/crisp-utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "crisp-utils" +version.workspace = true +edition.workspace = true +license.workspace = true +description.workspace = true +repository.workspace = true + +[dependencies] +e3-sdk = { workspace = true, default-features = false, features=["bfv"] } +eyre = { workspace = true } +hex = { workspace = true } + diff --git a/examples/CRISP/crates/crisp-utils/src/lib.rs b/examples/CRISP/crates/crisp-utils/src/lib.rs new file mode 100644 index 0000000000..ec902a9f80 --- /dev/null +++ b/examples/CRISP/crates/crisp-utils/src/lib.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: LGPL-3.0-only +// +// This file is provided WITHOUT ANY WARRANTY; +// without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. + +use e3_sdk::bfv_helpers::decode_bytes_to_vec_u64; +use eyre::Result; + +/// Represents decoded vote counts from a tally +#[derive(Debug, Clone, Copy)] +pub struct VoteCounts { + pub yes: u64, + pub no: u64, +} + +/// Decode an encoded tally into its decimal representation. +/// +/// The plaintext output from FHE computation contains the result as bytes. +/// Votes are encoded with yes votes in the first half and no votes in the second half, +/// right-aligned with leading zeros. +/// +/// # Arguments +/// +/// * `tally_bytes` - The encoded tally as bytes (little-endian format of u64s) +/// +/// # Returns +/// +/// A `VoteCounts` struct containing the decoded yes and no vote counts +pub fn decode_tally(tally_bytes: &[u8]) -> Result { + // Decode bytes to numbers array (little-endian, 8 bytes per value) + let decoded = decode_bytes_to_vec_u64(tally_bytes)?; + + // Votes are right-aligned with leading zeros, so we can use the entire halves + let half_d = decoded.len() / 2; + let yes_binary = &decoded[0..half_d]; + let no_binary = &decoded[half_d..decoded.len()]; + + // Convert yes votes (entire first half) + let mut yes = 0u64; + for (i, &value) in yes_binary.iter().enumerate() { + let weight = 2u64.pow((yes_binary.len() - 1 - i) as u32); + yes += value * weight; + } + + // Convert no votes (entire second half) + let mut no = 0u64; + for (i, &value) in no_binary.iter().enumerate() { + let weight = 2u64.pow((no_binary.len() - 1 - i) as u32); + no += value * weight; + } + + Ok(VoteCounts { yes, no }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_decode_tally_real_fhe_output() { + // Real FHE computation output + // Expected: yes = 10000000000, no = 30000000000 + let tally_hex = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + + let bytes = hex::decode(tally_hex.strip_prefix("0x").unwrap_or(tally_hex)).unwrap(); + let result = decode_tally(&bytes).unwrap(); + + assert_eq!(result.yes, 10000000000); + assert_eq!(result.no, 30000000000); + } +} diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index cef0a84584..2f2f53cc40 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -45,10 +45,10 @@ describe('Vote', () => { }) describe('decodeTally', () => { - it('Should decode an encoded tally into its decimal representation', () => { + it.only('Should decode an encoded tally into its decimal representation', () => { // Output of a real FHE computation. const tally = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' const decoded = decodeTally(tally) diff --git a/examples/CRISP/server/Cargo.toml b/examples/CRISP/server/Cargo.toml index 88eda16571..7a06c4dfb3 100644 --- a/examples/CRISP/server/Cargo.toml +++ b/examples/CRISP/server/Cargo.toml @@ -46,6 +46,7 @@ e3-compute-provider.workspace = true e3-sdk = { workspace = true, default-features = false, features=["full"] } evm-helpers = { path = "../crates/evm_helpers" } crisp-constants.workspace = true +crisp-utils.workspace = true # CLI and user interaction dialoguer = { version = "=0.11.0", features = ["fuzzy-select"] } diff --git a/examples/CRISP/server/src/server/indexer.rs b/examples/CRISP/server/src/server/indexer.rs index 0706bc350a..34fae668a6 100644 --- a/examples/CRISP/server/src/server/indexer.rs +++ b/examples/CRISP/server/src/server/indexer.rs @@ -15,9 +15,9 @@ use crate::server::{ use alloy::providers::{Provider, ProviderBuilder}; use alloy::sol_types::{sol_data, SolType}; use alloy_primitives::{Address, U256}; +use crisp_utils::decode_tally; use e3_sdk::indexer::IndexerContext; use e3_sdk::{ - bfv_helpers::decode_bytes_to_vec_u64, evm_helpers::{ contracts::{EnclaveRead, EnclaveWrite, ReadWrite}, events::{ @@ -290,26 +290,13 @@ pub async fn register_plaintext_output_published( info!("[e3_id={}] Handling PlaintextOutputPublished", e3_id); // The plaintextOutput from the event contains the result of the FHE computation. - // The computation sums the encrypted votes: '0' for Option 1, '1' for Option 2. - // Thus, the decrypted sum directly represents the number of votes for Option 2. - // The output is expected to be a Vec in little endian format of u64s. - let decoded = decode_bytes_to_vec_u64(&event.plaintextOutput)?; - - // decoded[0] is the sum of all encrypted votes (0s and 1s). - // Since Option 1 votes are encrypted as '0' and Option 2 votes as '1', - // this sum is equivalent to the count of votes for Option 2. - let option_2 = decoded[0]; - - // Retrieve the total number of votes that were cast and recorded for this round. - let total_votes = repo.get_vote_count().await?; - - // The number of votes for Option 1 can be derived by subtracting - // the Option 2 votes (the sum from the FHE output) from the total votes. - let option_1 = total_votes - option_2; - - info!("[e3_id={}] Vote Count: {:?}", e3_id, total_votes); - info!("[e3_id={}] Votes Option 1: {:?}", e3_id, option_1); - info!("[e3_id={}] Votes Option 2: {:?}", e3_id, option_2); + // Decode the tally using the utility function. + let vote_counts = decode_tally(&event.plaintextOutput)?; + let option_1 = vote_counts.yes; + let option_2 = vote_counts.no; + + info!("[e3_id={}] Votes Option 1 (Yes): {:?}", e3_id, option_1); + info!("[e3_id={}] Votes Option 2 (No): {:?}", e3_id, option_2); repo.set_votes(option_1, option_2).await?; repo.update_status("Finished").await?; From 9247184bdc89ca50e9003e8ebe06025950dfdf36 Mon Sep 17 00:00:00 2001 From: ctrlc03 <93448202+ctrlc03@users.noreply.github.com> Date: Wed, 17 Dec 2025 16:38:07 +0000 Subject: [PATCH 05/14] chore: add new decoding to contract --- .../contracts/CRISPProgram.sol | 48 ++++++++++++------- .../contracts/Mocks/MockEnclave.sol | 4 +- .../tests/crisp.contracts.test.ts | 33 ++++--------- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol index 786f7ccf5e..ecefe50fe7 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol @@ -29,8 +29,6 @@ contract CRISPProgram is IE3Program, Ownable { bytes32 public constant ENCRYPTION_SCHEME_ID = keccak256("fhe.rs:BFV"); /// @notice The depth of the input Merkle tree. uint8 public constant TREE_DEPTH = 20; - /// @notice Half of the largest minimum degree used to fit votes inside the plaintext polynomial. - uint256 public constant HALF_LARGEST_MINIMUM_DEGREE = 28; // static, hardcoded in the circuit. // State variables IEnclave public enclave; @@ -159,31 +157,21 @@ contract CRISPProgram is IE3Program, Ownable { // fetch from enclave E3 memory e3 = enclave.getE3(e3Id); - // abi decode it into an array of uint256 - uint256[] memory tally = abi.decode(e3.plaintextOutput, (uint256[])); + // decode it into an array of uint64 + uint64[] memory tally = _decodeBytesToUint64Array(e3.plaintextOutput); - /// @notice We want to completely ignore anything outside of the coefficients - /// we agreed to store out votes on. uint256 halfD = tally.length / 2; - uint256 START_INDEX_Y = halfD - HALF_LARGEST_MINIMUM_DEGREE; - uint256 START_INDEX_N = tally.length - HALF_LARGEST_MINIMUM_DEGREE; - - // first weight (we are converting back from bits to integer) - uint256 weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); // Convert yes votes - for (uint256 i = START_INDEX_Y; i < halfD; i++) { + for (uint256 i = 0; i < halfD; i++) { + uint256 weight = 2 ** (halfD - 1 - i); yes += tally[i] * weight; - weight /= 2; // Right shift equivalent } - // Reset weight for no votes - weight = 2 ** (HALF_LARGEST_MINIMUM_DEGREE - 1); - // Convert no votes - for (uint256 i = START_INDEX_N; i < tally.length; i++) { + for (uint256 i = halfD; i < tally.length; i++) { + uint256 weight = 2 ** (tally.length - 1 - i); no += tally[i] * weight; - weight /= 2; } return (yes, no); @@ -236,4 +224,28 @@ contract CRISPProgram is IE3Program, Ownable { journal[startIndex + i * 4] = hashVal[i]; } } + + /// @notice Decode bytes to uint256 array + /// @param data The bytes to decode (must be multiple of 32) + /// @return result Array of uint256 values + function _decodeBytesToUint64Array(bytes memory data) internal pure returns (uint64[] memory result) { + require(data.length % 8 == 0, "Data length must be multiple of 8"); + + uint256 arrayLength = data.length / 8; + result = new uint64[](arrayLength); + + for (uint256 i = 0; i < arrayLength; i++) { + uint256 offset = i * 8; + uint64 value = 0; + + // Read 8 bytes in little-endian order + for (uint64 j = 0; j < 8; j++) { + value |= uint64(uint8(data[offset + j])) << (j * 8); + } + + result[i] = value; + } + + return result; + } } diff --git a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol index 58cd6e65a6..605a756e85 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/Mocks/MockEnclave.sol @@ -12,8 +12,8 @@ import { IDecryptionVerifier } from "@enclave-e3/contracts/contracts/interfaces/ contract MockEnclave { bytes public plaintextOutput; - function setPlaintextOutput(uint256[] memory plaintext) external { - plaintextOutput = abi.encode(plaintext); + function setPlaintextOutput(bytes memory plaintext) external { + plaintextOutput = plaintext; } function getE3(uint256 e3Id) external view returns (E3 memory) { diff --git a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts index 67e5a9827d..bddd588348 100644 --- a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts +++ b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts @@ -22,36 +22,19 @@ let publicKey = generatePublicKey() describe('CRISP Contracts', function () { describe('decode tally', () => { - it('should decode different tallies correctly', async () => { + it('should decode a tally correctly', async () => { const mockEnclave = await deployMockEnclave() const crispProgram = await deployCRISPProgram({ mockEnclave }) - // 2 * 2 + 1 * 1 = 5 Y - // 2 * 1 + 0 * 1 = 2 N - const tally1 = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, - ] + const tally = + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - await mockEnclave.setPlaintextOutput(tally1) + await mockEnclave.setPlaintextOutput(tally) const decodedTally1 = await crispProgram.decodeTally(0) - expect(decodedTally1[0]).to.equal(5n) - expect(decodedTally1[1]).to.equal(2n) - - // 1 * 1 + 2 * 2 + 5 * 16 + 8 * 1024 = 8277 - // 2 * 1 + 3 * 64 + 1024 = - const tally2 = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 5, 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0, - ] - await mockEnclave.setPlaintextOutput(tally2) - - const decodedTally2 = await crispProgram.decodeTally(0) - - expect(decodedTally2[0]).to.equal(8277n) - expect(decodedTally2[1]).to.equal(1218n) + expect(decodedTally1[0]).to.equal(10000000000n) + expect(decodedTally1[1]).to.equal(30000000000n) }) }) @@ -63,7 +46,7 @@ describe('CRISP Contracts', function () { const honkVerifier = await deployHonkVerifier() const [signer] = await ethers.getSigners() - const vote = { yes: 10n, no: 0n } + const vote = { yes: 10, no: 0 } const balance = 100n const signature = (await signer.signMessage(SIGNATURE_MESSAGE)) as `0x${string}` const address = await getAddressFromSignature(signature, SIGNATURE_MESSAGE_HASH) @@ -92,7 +75,7 @@ describe('CRISP Contracts', function () { const e3Id = 1n - const vote = { yes: 10n, no: 0n } + const vote = { yes: 10, no: 0 } const balance = 100n const signature = (await signer.signMessage(SIGNATURE_MESSAGE)) as `0x${string}` const address = await getAddressFromSignature(signature, SIGNATURE_MESSAGE_HASH) From 722d710be0640d89944d3756b198cf8070a6332e Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 17:38:29 +0100 Subject: [PATCH 06/14] refactor: update types and decoding function --- examples/CRISP/Cargo.lock | 1 + examples/CRISP/crates/crisp-utils/Cargo.toml | 1 + examples/CRISP/crates/crisp-utils/src/lib.rs | 26 +++++++++---------- .../CRISP/packages/crisp-sdk/src/types.ts | 4 +-- examples/CRISP/packages/crisp-sdk/src/vote.ts | 25 +++++++++--------- .../packages/crisp-sdk/tests/vote.test.ts | 19 +++++++------- examples/CRISP/server/src/server/indexer.rs | 14 ++++++---- examples/CRISP/server/src/server/models.rs | 14 +++++----- examples/CRISP/server/src/server/repo.rs | 12 ++++----- 9 files changed, 60 insertions(+), 56 deletions(-) diff --git a/examples/CRISP/Cargo.lock b/examples/CRISP/Cargo.lock index 35a5c85074..64ba384aa2 100644 --- a/examples/CRISP/Cargo.lock +++ b/examples/CRISP/Cargo.lock @@ -2071,6 +2071,7 @@ dependencies = [ "e3-sdk", "eyre", "hex", + "num-bigint", ] [[package]] diff --git a/examples/CRISP/crates/crisp-utils/Cargo.toml b/examples/CRISP/crates/crisp-utils/Cargo.toml index 366fc8a83a..158925e92f 100644 --- a/examples/CRISP/crates/crisp-utils/Cargo.toml +++ b/examples/CRISP/crates/crisp-utils/Cargo.toml @@ -10,4 +10,5 @@ repository.workspace = true e3-sdk = { workspace = true, default-features = false, features=["bfv"] } eyre = { workspace = true } hex = { workspace = true } +num-bigint = "=0.4.6" diff --git a/examples/CRISP/crates/crisp-utils/src/lib.rs b/examples/CRISP/crates/crisp-utils/src/lib.rs index ec902a9f80..0979e47d91 100644 --- a/examples/CRISP/crates/crisp-utils/src/lib.rs +++ b/examples/CRISP/crates/crisp-utils/src/lib.rs @@ -6,12 +6,13 @@ use e3_sdk::bfv_helpers::decode_bytes_to_vec_u64; use eyre::Result; +use num_bigint::BigUint; /// Represents decoded vote counts from a tally -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct VoteCounts { - pub yes: u64, - pub no: u64, + pub yes: BigUint, + pub no: BigUint, } /// Decode an encoded tally into its decimal representation. @@ -37,17 +38,17 @@ pub fn decode_tally(tally_bytes: &[u8]) -> Result { let no_binary = &decoded[half_d..decoded.len()]; // Convert yes votes (entire first half) - let mut yes = 0u64; + let mut yes = BigUint::from(0u64); for (i, &value) in yes_binary.iter().enumerate() { - let weight = 2u64.pow((yes_binary.len() - 1 - i) as u32); - yes += value * weight; + let weight = BigUint::from(2u64).pow((yes_binary.len() - 1 - i) as u32); + yes += BigUint::from(value) * weight; } // Convert no votes (entire second half) - let mut no = 0u64; + let mut no = BigUint::from(0u64); for (i, &value) in no_binary.iter().enumerate() { - let weight = 2u64.pow((no_binary.len() - 1 - i) as u32); - no += value * weight; + let weight = BigUint::from(2u64).pow((no_binary.len() - 1 - i) as u32); + no += BigUint::from(value) * weight; } Ok(VoteCounts { yes, no }) @@ -58,15 +59,14 @@ mod tests { use super::*; #[test] - fn test_decode_tally_real_fhe_output() { - // Real FHE computation output + fn test_decode_tally_fhe_output() { // Expected: yes = 10000000000, no = 30000000000 let tally_hex = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; let bytes = hex::decode(tally_hex.strip_prefix("0x").unwrap_or(tally_hex)).unwrap(); let result = decode_tally(&bytes).unwrap(); - assert_eq!(result.yes, 10000000000); - assert_eq!(result.no, 30000000000); + assert_eq!(result.yes, BigUint::from(10000000000u64)); + assert_eq!(result.no, BigUint::from(30000000000u64)); } } diff --git a/examples/CRISP/packages/crisp-sdk/src/types.ts b/examples/CRISP/packages/crisp-sdk/src/types.ts index dc0c34c40c..5a63ddd2af 100644 --- a/examples/CRISP/packages/crisp-sdk/src/types.ts +++ b/examples/CRISP/packages/crisp-sdk/src/types.ts @@ -71,11 +71,11 @@ export type Vote = { /** * The voting power for 'yes' votes */ - yes: number + yes: bigint /** * The voting power for 'no' votes */ - no: number + no: bigint } /** diff --git a/examples/CRISP/packages/crisp-sdk/src/vote.ts b/examples/CRISP/packages/crisp-sdk/src/vote.ts index 874f22ae23..dd0a474eb8 100644 --- a/examples/CRISP/packages/crisp-sdk/src/vote.ts +++ b/examples/CRISP/packages/crisp-sdk/src/vote.ts @@ -28,8 +28,8 @@ export const encodeVote = (vote: Vote): BigInt64Array => { const voteArray = [] const length = bfvParams.degree const halfLength = length / 2 - const yesBinary = toBinary(BigInt(vote.yes)).split('') - const noBinary = toBinary(BigInt(vote.no)).split('') + const yesBinary = toBinary(vote.yes).split('') + const noBinary = toBinary(vote.no).split('') // Fill first half with 'yes' binary representation (pad with leading 0s if needed) for (let i = 0; i < halfLength; i++) { @@ -95,19 +95,19 @@ export const decodeTally = (tallyBytes: string): Vote => { const yesBinary = numbers.slice(0, HALF_D) const noBinary = numbers.slice(HALF_D, numbers.length) - let yes = 0 - let no = 0 + let yes = 0n + let no = 0n // Convert yes votes (entire first half) for (let i = 0; i < yesBinary.length; i += 1) { - const weight = 2 ** (yesBinary.length - 1 - i) - yes += yesBinary[i] * weight + const weight = 2n ** BigInt(yesBinary.length - 1 - i) + yes += BigInt(yesBinary[i]) * weight } // Convert no votes (entire second half) for (let i = 0; i < noBinary.length; i += 1) { - const weight = 2 ** (noBinary.length - 1 - i) - no += noBinary[i] * weight + const weight = 2n ** BigInt(noBinary.length - 1 - i) + no += BigInt(noBinary[i]) * weight } return { @@ -133,7 +133,7 @@ export const generateCircuitInputs = async (proofInputs: ProofInputs): Promise { throw new Error('Invalid vote: vote exceeds balance') } - if (voteProofInputs.vote.yes > MAXIMUM_VOTE_VALUE || voteProofInputs.vote.no > MAXIMUM_VOTE_VALUE) { + const maxVoteValue = BigInt(MAXIMUM_VOTE_VALUE) + if (voteProofInputs.vote.yes > maxVoteValue || voteProofInputs.vote.no > maxVoteValue) { throw new Error('Invalid vote: vote exceeds maximum allowed value') } - if (voteProofInputs.vote.yes < 0 || voteProofInputs.vote.no < 0) { + if (voteProofInputs.vote.yes < 0n || voteProofInputs.vote.no < 0n) { throw new Error('Invalid vote: vote is negative') } @@ -208,7 +209,7 @@ export const generateMaskVoteProof = async (maskVoteProofInputs: MaskVoteProofIn const crispInputs = await generateCircuitInputs({ ...maskVoteProofInputs, signature: MASK_SIGNATURE, - vote: { yes: 0, no: 0 }, + vote: { yes: 0n, no: 0n }, merkleProof, }) diff --git a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts index 2f2f53cc40..69b3efdc2f 100644 --- a/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts +++ b/examples/CRISP/packages/crisp-sdk/tests/vote.test.ts @@ -34,7 +34,7 @@ describe('Vote', () => { // Setup the test environment. beforeAll(async () => { - vote = { yes: 10, no: 0 } + vote = { yes: 10n, no: 0n } signature = await signMessage({ message: SIGNATURE_MESSAGE, privateKey: ECDSA_PRIVATE_KEY }) balance = 100n address = publicKeyToAddress(await recoverPublicKey({ hash: SIGNATURE_MESSAGE_HASH, signature })) @@ -45,15 +45,14 @@ describe('Vote', () => { }) describe('decodeTally', () => { - it.only('Should decode an encoded tally into its decimal representation', () => { - // Output of a real FHE computation. + it('Should decode an encoded tally into its decimal representation', () => { const tally = '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' const decoded = decodeTally(tally) - expect(decoded.yes).toBe(10000000000) - expect(decoded.no).toBe(30000000000) + expect(decoded.yes).toBe(10000000000n) + expect(decoded.no).toBe(30000000000n) }) }) @@ -67,20 +66,20 @@ describe('Vote', () => { } it('Should encode yes vote correctly in the first half', () => { - const encoded = encodeVote({ yes: 10, no: 2 }) + const encoded = encodeVote({ yes: 10n, no: 2n }) expect(decodeHalf(encoded, true)).toBe(10) expect(decodeHalf(encoded, false)).toBe(2) }) it('Should encode no vote correctly in the second half', () => { - const encoded = encodeVote({ yes: 0, no: 5 }) + const encoded = encodeVote({ yes: 0n, no: 5n }) expect(decodeHalf(encoded, false)).toBe(5) }) it('Should only contain binary digits (0 or 1)', () => { - const encoded = encodeVote({ yes: 255, no: 128 }) + const encoded = encodeVote({ yes: 255n, no: 128n }) expect(Array.from(encoded).every((b) => b >= 0n && b <= 1n)).toBe(true) }) @@ -123,7 +122,7 @@ describe('Vote', () => { // Using generateCircuitInputs directly to check the output of the circuit. const merkleProof = generateMerkleProof(balance, slotAddress, LEAVES) const crispInputs = await generateCircuitInputs({ - vote: { yes: 0, no: 0 }, + vote: { yes: 0n, no: 0n }, publicKey, previousCiphertext, signature: MASK_SIGNATURE, @@ -154,7 +153,7 @@ describe('Vote', () => { // Using generateCircuitInputs directly to check the output of the circuit. const merkleProof = generateMerkleProof(balance, slotAddress, LEAVES) const crispInputs = await generateCircuitInputs({ - vote: { yes: 0, no: 0 }, + vote: { yes: 0n, no: 0n }, publicKey, signature: MASK_SIGNATURE, merkleProof, diff --git a/examples/CRISP/server/src/server/indexer.rs b/examples/CRISP/server/src/server/indexer.rs index 34fae668a6..ab1aaaf668 100644 --- a/examples/CRISP/server/src/server/indexer.rs +++ b/examples/CRISP/server/src/server/indexer.rs @@ -292,13 +292,17 @@ pub async fn register_plaintext_output_published( // The plaintextOutput from the event contains the result of the FHE computation. // Decode the tally using the utility function. let vote_counts = decode_tally(&event.plaintextOutput)?; - let option_1 = vote_counts.yes; - let option_2 = vote_counts.no; - info!("[e3_id={}] Votes Option 1 (Yes): {:?}", e3_id, option_1); - info!("[e3_id={}] Votes Option 2 (No): {:?}", e3_id, option_2); + info!( + "[e3_id={}] Votes Option 1 (Yes): {:?}", + e3_id, vote_counts.yes + ); + info!( + "[e3_id={}] Votes Option 2 (No): {:?}", + e3_id, vote_counts.no + ); - repo.set_votes(option_1, option_2).await?; + repo.set_votes(vote_counts.yes, vote_counts.no).await?; repo.update_status("Finished").await?; Ok(()) } diff --git a/examples/CRISP/server/src/server/models.rs b/examples/CRISP/server/src/server/models.rs index 9cc3fc8f0c..4ed3d965ab 100644 --- a/examples/CRISP/server/src/server/models.rs +++ b/examples/CRISP/server/src/server/models.rs @@ -128,9 +128,8 @@ pub struct RoundRequest { #[derive(Debug, Deserialize, Serialize)] pub struct WebResultRequest { pub round_id: u64, - pub option_1_tally: u64, - pub option_2_tally: u64, - pub total_votes: u64, + pub option_1_tally: String, + pub option_2_tally: String, pub option_1_emoji: String, pub option_2_emoji: String, pub end_time: u64, @@ -168,8 +167,8 @@ pub struct E3 { pub status: String, pub has_voted: Vec, pub vote_count: u64, - pub votes_option_1: u64, - pub votes_option_2: u64, + pub votes_option_1: String, + pub votes_option_2: String, // Timing-related pub start_time: u64, @@ -198,8 +197,8 @@ pub struct E3Crisp { pub has_voted: Vec, pub start_time: u64, pub status: String, - pub votes_option_1: u64, - pub votes_option_2: u64, + pub votes_option_1: String, + pub votes_option_2: String, pub token_holder_hashes: Vec, pub token_address: String, pub balance_threshold: String, @@ -212,7 +211,6 @@ impl From for WebResultRequest { round_id: e3.id, option_1_tally: e3.votes_option_1, option_2_tally: e3.votes_option_2, - total_votes: e3.votes_option_1 + e3.votes_option_2, option_1_emoji: e3.emojis[0].clone(), option_2_emoji: e3.emojis[1].clone(), end_time: e3.expiration, diff --git a/examples/CRISP/server/src/server/repo.rs b/examples/CRISP/server/src/server/repo.rs index 264b7a296d..4eac405ca0 100644 --- a/examples/CRISP/server/src/server/repo.rs +++ b/examples/CRISP/server/src/server/repo.rs @@ -11,6 +11,7 @@ use super::{ use e3_sdk::indexer::{models::E3 as EnclaveE3, DataStore, E3Repository, SharedStore}; use eyre::Result; use log::info; +use num_bigint::BigUint; pub struct CurrentRoundRepository { store: SharedStore, @@ -125,8 +126,8 @@ impl CrispE3Repository { has_voted: vec![], start_time: 0u64, status: "Requested".to_string(), - votes_option_1: 0, - votes_option_2: 0, + votes_option_1: "0".to_string(), + votes_option_2: "0".to_string(), emojis: generate_emoji(), token_holder_hashes: vec![], token_address, @@ -165,14 +166,14 @@ impl CrispE3Repository { Ok(()) } - pub async fn set_votes(&mut self, option_1: u64, option_2: u64) -> Result<()> { + pub async fn set_votes(&mut self, option_1: BigUint, option_2: BigUint) -> Result<()> { info!("set_votes(option_1:{} option_2:{})", option_1, option_2); let key = self.crisp_key(); self.store .modify(&key, |e3_obj: Option| { e3_obj.map(|mut e| { - e.votes_option_1 = option_1; - e.votes_option_2 = option_2; + e.votes_option_1 = option_1.to_string(); + e.votes_option_2 = option_2.to_string(); e }) }) @@ -198,7 +199,6 @@ impl CrispE3Repository { round_id: e3.id, option_1_tally: e3_crisp.votes_option_1, option_2_tally: e3_crisp.votes_option_2, - total_votes: e3_crisp.votes_option_1 + e3_crisp.votes_option_2, option_1_emoji: e3_crisp.emojis[0].clone(), option_2_emoji: e3_crisp.emojis[1].clone(), end_time: e3.expiration, From acbb022fd30a6ce06064458824dc2801883280fa Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 17:58:43 +0100 Subject: [PATCH 07/14] refactor: add error for invalid tally length --- .../packages/crisp-contracts/contracts/CRISPProgram.sol | 6 +++++- .../packages/crisp-contracts/tests/crisp.contracts.test.ts | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol index ecefe50fe7..419199e7f2 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol @@ -51,7 +51,7 @@ contract CRISPProgram is IE3Program, Ownable { error InvalidNoirProof(); error InvalidMerkleRoot(); error MerkleRootAlreadySet(); - + error InvalidTallyLength(); // Events event InputPublished(uint256 indexed e3Id, bytes vote, uint256 index); @@ -160,6 +160,10 @@ contract CRISPProgram is IE3Program, Ownable { // decode it into an array of uint64 uint64[] memory tally = _decodeBytesToUint64Array(e3.plaintextOutput); + if (tally.length % 2 != 0) { + revert InvalidTallyLength(); + } + uint256 halfD = tally.length / 2; // Convert yes votes diff --git a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts index bddd588348..b0b2a990c0 100644 --- a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts +++ b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts @@ -22,12 +22,12 @@ let publicKey = generatePublicKey() describe('CRISP Contracts', function () { describe('decode tally', () => { - it('should decode a tally correctly', async () => { + it.only('should decode a tally correctly', async () => { const mockEnclave = await deployMockEnclave() const crispProgram = await deployCRISPProgram({ mockEnclave }) const tally = - '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000001000000000000000000000000000000010000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000100000000000000010000000000000001000000000000000100000000000000010000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000300000000000000000000000000000003000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000030000000000000003000000000000000300000000000000030000000000000003000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' await mockEnclave.setPlaintextOutput(tally) From f8dba93f1662519dd4fbda7e3046169c5ead7edf Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 18:01:43 +0100 Subject: [PATCH 08/14] refactor: add error for invalid tally length --- .../crisp-contracts/contracts/CRISPProgram.sol | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol index 419199e7f2..3c2a65f78f 100644 --- a/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol +++ b/examples/CRISP/packages/crisp-contracts/contracts/CRISPProgram.sol @@ -160,10 +160,6 @@ contract CRISPProgram is IE3Program, Ownable { // decode it into an array of uint64 uint64[] memory tally = _decodeBytesToUint64Array(e3.plaintextOutput); - if (tally.length % 2 != 0) { - revert InvalidTallyLength(); - } - uint256 halfD = tally.length / 2; // Convert yes votes @@ -229,11 +225,13 @@ contract CRISPProgram is IE3Program, Ownable { } } - /// @notice Decode bytes to uint256 array - /// @param data The bytes to decode (must be multiple of 32) - /// @return result Array of uint256 values + /// @notice Decode bytes to uint64 array + /// @param data The bytes to decode (must be multiple of 8) + /// @return result Array of uint64 values function _decodeBytesToUint64Array(bytes memory data) internal pure returns (uint64[] memory result) { - require(data.length % 8 == 0, "Data length must be multiple of 8"); + if (data.length % 8 != 0) { + revert InvalidTallyLength(); + } uint256 arrayLength = data.length / 8; result = new uint64[](arrayLength); From d733ed72f68b91ac83309c985c59e7d6398fa3b1 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 18:04:52 +0100 Subject: [PATCH 09/14] refactor: update vote type to bigint --- .../packages/crisp-contracts/tests/crisp.contracts.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts index b0b2a990c0..3686f7b33e 100644 --- a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts +++ b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts @@ -46,7 +46,7 @@ describe('CRISP Contracts', function () { const honkVerifier = await deployHonkVerifier() const [signer] = await ethers.getSigners() - const vote = { yes: 10, no: 0 } + const vote = { yes: 10n, no: 0n } const balance = 100n const signature = (await signer.signMessage(SIGNATURE_MESSAGE)) as `0x${string}` const address = await getAddressFromSignature(signature, SIGNATURE_MESSAGE_HASH) @@ -75,7 +75,7 @@ describe('CRISP Contracts', function () { const e3Id = 1n - const vote = { yes: 10, no: 0 } + const vote = { yes: 10n, no: 0n } const balance = 100n const signature = (await signer.signMessage(SIGNATURE_MESSAGE)) as `0x${string}` const address = await getAddressFromSignature(signature, SIGNATURE_MESSAGE_HASH) From b30e9e6dbce29f1f00da465838a5f46d82e4d597 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 18:16:29 +0100 Subject: [PATCH 10/14] test: remove only from decode tally test --- .../packages/crisp-contracts/tests/crisp.contracts.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts index 3686f7b33e..40a7597d51 100644 --- a/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts +++ b/examples/CRISP/packages/crisp-contracts/tests/crisp.contracts.test.ts @@ -22,7 +22,7 @@ let publicKey = generatePublicKey() describe('CRISP Contracts', function () { describe('decode tally', () => { - it.only('should decode a tally correctly', async () => { + it('should decode a tally correctly', async () => { const mockEnclave = await deployMockEnclave() const crispProgram = await deployCRISPProgram({ mockEnclave }) From fdb94302df8f0ed94a10cacad9db66ce0fe0fb00 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Wed, 17 Dec 2025 18:44:37 +0100 Subject: [PATCH 11/14] refactor: add total votes to web result request --- examples/CRISP/server/src/server/models.rs | 2 ++ examples/CRISP/server/src/server/repo.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/CRISP/server/src/server/models.rs b/examples/CRISP/server/src/server/models.rs index 4ed3d965ab..66cc8266d2 100644 --- a/examples/CRISP/server/src/server/models.rs +++ b/examples/CRISP/server/src/server/models.rs @@ -132,6 +132,7 @@ pub struct WebResultRequest { pub option_2_tally: String, pub option_1_emoji: String, pub option_2_emoji: String, + pub total_votes: u64, pub end_time: u64, } @@ -213,6 +214,7 @@ impl From for WebResultRequest { option_2_tally: e3.votes_option_2, option_1_emoji: e3.emojis[0].clone(), option_2_emoji: e3.emojis[1].clone(), + total_votes: e3.vote_count, end_time: e3.expiration, } } diff --git a/examples/CRISP/server/src/server/repo.rs b/examples/CRISP/server/src/server/repo.rs index 4eac405ca0..944326979a 100644 --- a/examples/CRISP/server/src/server/repo.rs +++ b/examples/CRISP/server/src/server/repo.rs @@ -202,6 +202,7 @@ impl CrispE3Repository { option_1_emoji: e3_crisp.emojis[0].clone(), option_2_emoji: e3_crisp.emojis[1].clone(), end_time: e3.expiration, + total_votes: self.get_vote_count().await?, }) } From 6073bf683725d341807d653417a6d04e806fc39c Mon Sep 17 00:00:00 2001 From: Cedoor Date: Thu, 18 Dec 2025 10:20:56 +0100 Subject: [PATCH 12/14] ci: set correct cargo lock path for crisp e2e --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f17d2a886..c4b0dc42d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -488,6 +488,9 @@ jobs: - name: Cache Rust dependencies uses: ./.github/actions/cache-dependencies + with: + cargo-lock-path: examples/CRISP/Cargo.lock + rust-target-path: examples/CRISP/target/ - name: Prepare test environment run: | From 8dc9255569c342d9f5352aebb9688d579c1ced86 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Thu, 18 Dec 2025 10:57:21 +0100 Subject: [PATCH 13/14] chore: choose shorter wait time for e3 activation --- examples/CRISP/server/.env.example | 5 ++++- examples/CRISP/test/crisp.spec.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/CRISP/server/.env.example b/examples/CRISP/server/.env.example index 78ff8b0759..ef360227e0 100644 --- a/examples/CRISP/server/.env.example +++ b/examples/CRISP/server/.env.example @@ -19,10 +19,13 @@ E3_PROGRAM_ADDRESS="0x67d269191c92Caf3cD7723F116c85e6E9bf55933" # CRISPProgram C FEE_TOKEN_ADDRESS="0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" # E3 Config +# Defines the time window during which an e3 can be activated E3_WINDOW_SIZE=40 +# Defines the time interval during which users can submit their inputs +# After this interval, the computation phase starts automatically +E3_DURATION=60 E3_THRESHOLD_MIN=2 E3_THRESHOLD_MAX=5 -E3_DURATION=160 # E3 Compute Provider Config E3_COMPUTE_PROVIDER_NAME="RISC0" diff --git a/examples/CRISP/test/crisp.spec.ts b/examples/CRISP/test/crisp.spec.ts index 601c278787..8ccf23b471 100644 --- a/examples/CRISP/test/crisp.spec.ts +++ b/examples/CRISP/test/crisp.spec.ts @@ -44,7 +44,7 @@ async function checkE3Activated(e3id: number): Promise { } } -async function waitForE3Activation(e3id: number, maxWaitMs: number = 300000): Promise { +async function waitForE3Activation(e3id: number, maxWaitMs: number = 60000): Promise { const startTime = Date.now() while (Date.now() - startTime < maxWaitMs) { const isActivated = await checkE3Activated(e3id) @@ -110,7 +110,7 @@ test('CRISP smoke test', async ({ context, page, metamaskPage, extensionId }) => await page.locator('button:has-text("Cast Vote")').click() log(`confirming MetaMask signature request...`) await metamask.confirmSignature() - const WAIT = 310_000 + const WAIT = 100_000 log(`waiting for ${WAIT}ms...`) await page.waitForTimeout(WAIT) log(`clicking historic polls button...`) From 217cc6e2caa2a1592b81726feccf61f73396dad3 Mon Sep 17 00:00:00 2001 From: Cedoor Date: Thu, 18 Dec 2025 11:17:21 +0100 Subject: [PATCH 14/14] chore: choose longer wait time after casting vote --- examples/CRISP/test/crisp.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/CRISP/test/crisp.spec.ts b/examples/CRISP/test/crisp.spec.ts index 8ccf23b471..69409c410d 100644 --- a/examples/CRISP/test/crisp.spec.ts +++ b/examples/CRISP/test/crisp.spec.ts @@ -110,7 +110,7 @@ test('CRISP smoke test', async ({ context, page, metamaskPage, extensionId }) => await page.locator('button:has-text("Cast Vote")').click() log(`confirming MetaMask signature request...`) await metamask.confirmSignature() - const WAIT = 100_000 + const WAIT = 150_000 log(`waiting for ${WAIT}ms...`) await page.waitForTimeout(WAIT) log(`clicking historic polls button...`)