Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion examples/CRISP/circuits/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fn main(
signature: [u8; 64],
hashed_message: [u8; 32],
balance: Field,
merkle_root: pub Field,
merkle_proof_length: u32,
merkle_proof_indices: [u1; 20],
merkle_proof_siblings: [Field; 20],
Expand All @@ -44,14 +45,21 @@ fn main(
let address = address_to_field(derive_address(public_key_x, public_key_y));

// Then get Merkle root.
let merkle_root = get_merkle_root(
let merkle_root_calculated = get_merkle_root(
address,
balance,
merkle_proof_length,
merkle_proof_indices,
merkle_proof_siblings,
);

// compare the merkle root with the param to confirm whether it's the voter
// or a masker
let mut is_voter = false;
if (merkle_root_calculated == merkle_root) {
is_voter = true;
}
Comment thread
ctrlc03 marked this conversation as resolved.

// then verify that the vote was encrypted correctly
let circuit: Greco<2048, 1, 54, 54, 5, 5, 20, 20, 54, 16, 54> = Greco::new(
params,
Expand Down
35 changes: 35 additions & 0 deletions examples/CRISP/circuits/src/merkle_tree.nr
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,38 @@ fn test_get_merkle_root() {

assert(merkle_root == expected);
}

#[test]
fn test_get_merkle_root_ts_sdk() {
let address = 0x1234567890123456789012345678901234567890;
let balance = 1000;

let depth = 4;
let indices: [u1; 20] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let siblings: [Field; 20] = [
3427403932201889290042771220465494458496044723860771244846412090114641612933,
142173982782527023041821996079117549632814652929360547003021612347642943458,
5984039385283196232308319035002972848802042601279200765710389913462508845879,
13230920196039626330844247773673397239708271370016925050843639396057862269188,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
];

let merkle_root = get_merkle_root(address, balance, depth, indices, siblings);
let expected = 1459018155640011149834119814743866717977769794914747043488779523926892000355;
assert(merkle_root == expected);
}
3 changes: 3 additions & 0 deletions examples/CRISP/packages/crisp-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface IMerkleProof {
leaf: bigint
index: number
proof: LeanIMTMerkleProof<bigint>
length: number
indices: number[]
}

/**
Expand Down Expand Up @@ -163,6 +165,7 @@ export interface CRISPCircuitInputs {
signature: string[]
hashed_message: string[]
balance: string
merkle_root: string
merkle_proof_length: string
merkle_proof_indices: string[]
merkle_proof_siblings: string[]
Expand Down
24 changes: 22 additions & 2 deletions examples/CRISP/packages/crisp-sdk/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,15 @@ export const generateMerkleTree = (leaves: bigint[]): LeanIMT => {
* @param balance The voter's balance
* @param address The voter's address
* @param leaves The leaves of the Merkle tree
* @param maxDepth The maximum depth of the Merkle tree
*/
export const generateMerkleProof = (threshold: number, balance: number, address: string, leaves: bigint[]): IMerkleProof => {
export const generateMerkleProof = (
threshold: bigint,
balance: bigint,
address: string,
leaves: bigint[],
maxDepth: number,
): IMerkleProof => {
if (balance < threshold) {
throw new Error('Balance is below the threshold')
}
Expand All @@ -52,10 +59,23 @@ export const generateMerkleProof = (threshold: number, balance: number, address:

const proof = tree.generateProof(index)

// Pad siblings with zeros
const paddedSiblings = [...proof.siblings, ...Array(maxDepth - proof.siblings.length).fill(0n)]

// Pad indices with zeros
const indices = proof.siblings.map((_, i) => Number((BigInt(index) >> BigInt(i)) & 1n))
const paddedIndices = [...indices, ...Array(maxDepth - indices.length).fill(0)]

return {
leaf,
index,
proof,
proof: {
...proof,
siblings: paddedSiblings,
},
// Original length before padding
length: proof.siblings.length,
indices: paddedIndices,
}
}

Expand Down
14 changes: 11 additions & 3 deletions examples/CRISP/packages/crisp-sdk/src/vote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// or FITNESS FOR A PARTICULAR PURPOSE.

import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs'
import { BFVParams, type CRISPCircuitInputs, type IVote, VotingMode } from './types'
import { BFVParams, type CRISPCircuitInputs, type IMerkleProof, type IVote, VotingMode } from './types'
import { toBinary } from './utils'
import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS } from './constants'
import { extractSignature } from './signature'
Expand Down Expand Up @@ -170,12 +170,12 @@ export const encryptVoteAndGenerateCRISPInputs = async (
// the rest of the public and private inputs will need to be generated before calling the circuit to generate the CRISP proof
return {
...crispInputs,
// @todo fill the rest of the inputs needed for CRISP
public_key_x: [],
public_key_y: [],
signature: [],
hashed_message: [],
balance: '0',
merkle_root: '0',
merkle_proof_length: '0',
merkle_proof_indices: [],
merkle_proof_siblings: [],
Expand All @@ -184,16 +184,19 @@ export const encryptVoteAndGenerateCRISPInputs = async (

/**
* Generate the CRISP circuit inputs by extracting signature components and adding them to the partial inputs
* @todo Add the merkle tree inputs too
* @param partialInputs The partial CRISP circuit inputs
* @param signature The voter's signature
* @param message The signed message
* @param merkleData The voter's Merkle proof data
* @param balance The voter's balance
* @returns The complete CRISP circuit inputs
*/
export const generateCRISPInputs = async (
partialInputs: CRISPCircuitInputs,
signature: `0x${string}`,
message: string,
merkleData: IMerkleProof,
balance: bigint,
): Promise<CRISPCircuitInputs> => {
Comment thread
ctrlc03 marked this conversation as resolved.
const { hashed_message, pub_key_x, pub_key_y, signature: extractedSignature } = await extractSignature(message, signature)

Expand All @@ -203,5 +206,10 @@ export const generateCRISPInputs = async (
public_key_x: Array.from(pub_key_x).map((b) => b.toString()),
public_key_y: Array.from(pub_key_y).map((b) => b.toString()),
signature: Array.from(extractedSignature).map((b) => b.toString()),
merkle_proof_length: merkleData.length.toString(),
merkle_proof_indices: merkleData.indices.map((i) => i.toString()),
merkle_proof_siblings: merkleData.proof.siblings.map((s) => s.toString()),
merkle_root: merkleData.proof.root.toString(),
balance: balance.toString(),
}
}
15 changes: 15 additions & 0 deletions examples/CRISP/packages/crisp-sdk/tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,18 @@ export const MESSAGE = 'Vote for round 0'
export const SIGNATURE =
'0x1641431d0ed3fd86814f026da62e11434b53c6a85162fea7f99218bf3c307dec7f361c235f07b658780afd91a5c9d68d6a4b14d5eb0511f6688d3e91140eec121b'
export const VOTE = { yes: 10n, no: 0n }

export const LEAVES = [
5744770974032406598001112375731623179326875761382288642755141437508907349272n,
3427403932201889290042771220465494458496044723860771244846412090114641612933n,
2345497045557010425836789889102383730351985634595867684180656101146305513188n,
16333345332467701633145728130049893741621620444233378624735176290889202182510n,
7181270461626747418571270128374653017252322331096300145108059576488978730010n,
10652491433271864584861721641100216833021493709204436430564590071100900917428n,
20897323534773557936302566533365152086278248537724825381610164527249213072770n,
4720511075913887710172192848636076523165432993226978491435561065722130431597n,
14131255645332550266535358189863475289290770471998199141522479556687499890181n,
4935126455042678253283865781346660214959064333962120811317994370162001200675n,
]

export const MAX_DEPTH = 20
35 changes: 20 additions & 15 deletions examples/CRISP/packages/crisp-sdk/tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.

import { expect, describe, it, beforeAll } from 'vitest'
import { expect, describe, it } from 'vitest'
import { generateMerkleProof, generateMerkleTree, hashLeaf } from '../src/utils'
import { getTreeData } from '../src'
import { CRISP_SERVER_URL } from './constants'
import { LEAVES, MAX_DEPTH } from './constants'

describe('Utils', () => {
let leaves: bigint[]

beforeAll(async () => {
leaves = await getTreeData(CRISP_SERVER_URL, 0)
})

describe('hashLeaf', () => {
it('should return a bigint hash of the two values', () => {
const leaf = hashLeaf('0x1234567890123456789012345678901234567890', '1000')
Expand All @@ -25,21 +18,33 @@ describe('Utils', () => {

describe('generateMerkleTree', () => {
it('should generate a merkle tree', () => {
const tree = generateMerkleTree(leaves)
const tree = generateMerkleTree(LEAVES)
expect(tree.root).toBeDefined()
})
})

describe('generateMerkleProof', () => {
const address = '0x1234567890123456789012345678901234567890'
const balance = 1000
it('should generate a merkle proof for a leaf', () => {
const proof = generateMerkleProof(0, balance, address, leaves)
const balance = 1000n
it('should generate a valid merkle proof for a leaf', () => {
const tree = generateMerkleTree(LEAVES);

const proof = generateMerkleProof(0n, balance, address, LEAVES, MAX_DEPTH)
expect(proof.leaf).toBe(hashLeaf(address, balance.toString()))

expect(proof.length).toBe(4)
expect(proof.indices.length).toBe(MAX_DEPTH)
// Unpad the proof for verification
const unpaddedProof = {
...proof.proof,
siblings: proof.proof.siblings.slice(0, proof.length)
};

expect(tree.verifyProof(unpaddedProof)).toBe(true);
})
it('should throw if the leaf does not exist in the tree', () => {
expect(() => generateMerkleProof(0, balance, address, [])).toThrow('Leaf not found in the tree')
expect(() => generateMerkleProof(0, 999, address, leaves)).toThrow('Leaf not found in the tree')
expect(() => generateMerkleProof(0n, balance, address, [], MAX_DEPTH)).toThrow('Leaf not found in the tree')
expect(() => generateMerkleProof(0n, 999n, address, LEAVES, MAX_DEPTH)).toThrow('Leaf not found in the tree')
})
})
})
13 changes: 10 additions & 3 deletions examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import {
validateVote,
} from '../src/vote'
import { BFVParams, VotingMode } from '../src/types'
import { DEFAULT_BFV_PARAMS, MAXIMUM_VOTE_VALUE } from '../src'
import { DEFAULT_BFV_PARAMS, generateMerkleProof, MAXIMUM_VOTE_VALUE } from '../src'

import { MESSAGE, SIGNATURE, VOTE } from './constants'
import { LEAVES, MAX_DEPTH, MESSAGE, SIGNATURE, VOTE } from './constants'

describe('Vote', () => {
const votingPower = 10n
Expand Down Expand Up @@ -143,10 +143,12 @@ describe('Vote', () => {
})

describe('generateCRISPInputs', () => {
const votingPowerLeaf = 1000n
const merkleProof = generateMerkleProof(0n, votingPowerLeaf, '0x1234567890123456789012345678901234567890', LEAVES, MAX_DEPTH)
it('should add the remaining inputs to the CRISP inputs object', async () => {
Comment thread
ctrlc03 marked this conversation as resolved.
const encodedVote = encodeVote(VOTE, VotingMode.GOVERNANCE, votingPower)
const partialInputs = await encryptVoteAndGenerateCRISPInputs(encodedVote, publicKey, previousCiphertext)
const crispInputs = await generateCRISPInputs(partialInputs, SIGNATURE, MESSAGE)
const crispInputs = await generateCRISPInputs(partialInputs, SIGNATURE, MESSAGE, merkleProof, votingPowerLeaf)

expect(crispInputs.ct_add).toBeInstanceOf(Object)
expect(crispInputs.params).toBeInstanceOf(Object)
Expand All @@ -161,6 +163,11 @@ describe('Vote', () => {
expect(crispInputs.public_key_x).toBeInstanceOf(Array)
expect(crispInputs.public_key_y).toBeInstanceOf(Array)
expect(crispInputs.signature).toBeInstanceOf(Array)
expect(crispInputs.merkle_proof_indices).toBeDefined()
expect(crispInputs.merkle_proof_siblings).toBeDefined()
expect(crispInputs.merkle_proof_length).toBeDefined()
expect(crispInputs.merkle_root).toBeDefined()
expect(crispInputs.balance).toBe(votingPowerLeaf.toString())
})
})
})
Loading