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
22 changes: 12 additions & 10 deletions examples/CRISP/circuits/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ecdsa::{address_to_field, derive_address, verify_signature};
mod merkle_tree;
use merkle_tree::get_merkle_root;
mod utils;
use utils::check_coefficient_values;
use utils::{check_coefficient_values, check_coefficient_zero};

fn main(
// Ciphertext Addition Section.
Expand Down Expand Up @@ -84,6 +84,7 @@ fn main(
p1is,
p2is,
);

let ct_add: CiphertextAddition<2048, 1, 54, 54, 54> = CiphertextAddition::new(
params.crypto_params(),
ct0is,
Expand All @@ -99,31 +100,32 @@ fn main(
// Verify the correct ciphertext encryption.
let is_greco_valid = greco.verify();

assert(is_greco_valid == true);

// Verify the correct ciphertext addition.
let is_ct_add_valid = ct_add.verify();

// Ensure that the ciphertext is valid
assert(is_greco_valid);

// If the voter is eligible to vote, output the ciphertext.
// Otherwise, output the sum of the ciphertexts.
if (
(is_signature_valid == true)
& (merkle_root_calculated == merkle_root)
& (slot_address == address)
) {
if (is_signature_valid == true)
& (merkle_root_calculated == merkle_root)
& (slot_address == address) {
// @todo: need to check if vote <= balance.

// Verify the correct coefficient values.
check_coefficient_values(k1, params.crypto_params().q_mod_t);

(ct0is, ct1is)
} else {
// @todo: need to check if vote == 0.
// check if vote == 0.
check_coefficient_zero(k1);

// @todo: need to check if slot is empty (no previous ciphertext).
// If so, (ct0is, ct1is) should be returned.

assert(is_ct_add_valid == true);
// as well as the sum
assert(is_ct_add_valid);

Comment thread
ctrlc03 marked this conversation as resolved.
(sum_ct0is, sum_ct1is)
}
Expand Down
55 changes: 55 additions & 0 deletions examples/CRISP/circuits/src/utils.nr
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@ pub fn check_coefficient_values<let D: u32>(k1: Polynomial<D>, q_mod_t: Field) {
}
}

// Check that the polynomial has 0 for all relevant coefficients
pub fn check_coefficient_zero<let D: u32>(k1: Polynomial<D>) {
// This value would allow to fit
// 268435456 for yes and 268435456 for no
// which would fit the supply of most tokens really (imagining one user just holds all tokens)
let HALF_LARGEST_MINIMUM_DEGREE = 28;

let HALF_D = D / 2;
let START_INDEX_Y = HALF_D - HALF_LARGEST_MINIMUM_DEGREE;
let START_INDEX_N = D - HALF_LARGEST_MINIMUM_DEGREE;

let mut sum = 0;

// Loop through all coefficients in the space where we could have a vote
// yes part
for i in START_INDEX_Y..HALF_D {
sum += k1.coefficients[i];
}

// no part
for i in START_INDEX_N..D {
sum += k1.coefficients[i];
}

assert(sum == 0);
}

#[test]
fn test_check_coefficient_values_pass() {
let pol = Polynomial {
Expand Down Expand Up @@ -58,3 +85,31 @@ fn test_check_coefficient_values_fail() {

check_coefficient_values(pol, 1);
}

#[test(should_fail)]
fn test_check_coefficient_zero_fail() {
let pol = Polynomial {
coefficients: [
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, 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, 0, 1,
],
};

check_coefficient_zero(pol);
}

#[test]
fn test_check_coefficient_zero() {
let pol = Polynomial {
coefficients: [
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, 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, 0, 0,
],
};

check_coefficient_zero(pol);
}
5 changes: 5 additions & 0 deletions examples/CRISP/packages/crisp-sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ export const MAXIMUM_VOTE_VALUE = 268435456n
* These are the parameters used for the default testing purposes only.
*/
export const DEFAULT_BFV_PARAMS = ZKInputsGenerator.withDefaults().getBFVParams() as BFVParams

/**
* Mock message for masking signature
*/
export const MESSAGE = 'Vote for round 0'
1 change: 1 addition & 0 deletions examples/CRISP/packages/crisp-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,5 @@ export interface EncryptVoteAndGenerateCRISPInputsParams {
merkleData: IMerkleProof
balance: bigint
bfvParams?: BFVParams
slotAddress: string
}
3 changes: 1 addition & 2 deletions examples/CRISP/packages/crisp-sdk/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ export const generateMerkleProof = (

// 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 indices = proof.siblings.map((_, i) => Number((BigInt(proof.index) >> BigInt(i)) & 1n))
const paddedIndices = [...indices, ...Array(maxDepth - indices.length).fill(0)]

return {
Expand Down
35 changes: 26 additions & 9 deletions examples/CRISP/packages/crisp-sdk/src/vote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs'
import { BFVParams, type CRISPCircuitInputs, type EncryptVoteAndGenerateCRISPInputsParams, type IVote, VotingMode } from './types'
import { toBinary } from './utils'
import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS } from './constants'
import { MAXIMUM_VOTE_VALUE, DEFAULT_BFV_PARAMS, MESSAGE } from './constants'
import { extractSignature } from './signature'
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 { privateKeyToAccount } from 'viem/accounts'

/**
* This utility function calculates the first valid index for vote options
Expand Down Expand Up @@ -152,6 +153,11 @@ export const validateVote = (votingMode: VotingMode, vote: IVote, votingPower: b
* @param publicKey The public key to use for encryption
* @param previousCiphertext The previous ciphertext to use for addition operation
* @param bfvParams The BFV parameters to use for encryption
* @param merkleData The merkle proof data
* @param message The message that was signed
* @param signature The signature of the message
* @param balance The voter's balance
* @param slotAddress The voter's slot address
* @returns The CRISP circuit inputs
*/
export const encryptVoteAndGenerateCRISPInputs = async ({
Expand All @@ -163,6 +169,7 @@ export const encryptVoteAndGenerateCRISPInputs = async ({
message,
signature,
balance,
slotAddress,
}: EncryptVoteAndGenerateCRISPInputsParams): Promise<CRISPCircuitInputs> => {
if (encodedVote.length !== bfvParams.degree) {
throw new RangeError(`encodedVote length ${encodedVote.length} does not match BFV degree ${bfvParams.degree}`)
Expand All @@ -186,7 +193,7 @@ export const encryptVoteAndGenerateCRISPInputs = async ({
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(),
slot_address: merkleData.proof.root.toString(), // temporary, will be replaced with the actual slot address.
slot_address: slotAddress,
balance: balance.toString(),
}
}
Expand All @@ -196,13 +203,17 @@ export const encryptVoteAndGenerateCRISPInputs = async ({
* @param voter The voter's address
* @param publicKey The voter's public key
* @param previousCiphertext The previous ciphertext
* @param bfvParams The BFV parameters
* @param merkleRoot The merkle root of the census tree
* @param slotAddress The voter's slot address
* @returns The CRISP circuit inputs for a mask vote
*/
export const generateMaskVote = async (
publicKey: Uint8Array,
previousCiphertext: Uint8Array,
bfvParams = DEFAULT_BFV_PARAMS,
merkleRoot: bigint,
slotAddress: string,
): Promise<CRISPCircuitInputs> => {
const plaintextVote: IVote = {
yes: 0n,
Expand All @@ -217,17 +228,23 @@ export const generateMaskVote = async (

const crispInputs = (await zkInputsGenerator.generateInputs(previousCiphertext, publicKey, vote)) as CRISPCircuitInputs

// hardhat default private key
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
const account = privateKeyToAccount(privateKey)
const signature = await account.signMessage({ message: MESSAGE })
const { hashed_message, pub_key_x, pub_key_y, signature: extractedSignature } = await extractSignature(MESSAGE, signature)

return {
...crispInputs,
public_key_x: Array.from({ length: 32 }, () => '0'),
public_key_y: Array.from({ length: 32 }, () => '0'),
signature: Array.from({ length: 64 }, () => '0'),
hashed_message: Array.from({ length: 32 }, () => '0'),
merkle_proof_indices: Array.from({ length: 4 }, () => '0'),
merkle_proof_siblings: Array.from({ length: 4 }, () => '0'),
hashed_message: Array.from(hashed_message).map((b) => b.toString()),
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_indices: Array.from({ length: 20 }, () => '0'),
merkle_proof_siblings: Array.from({ length: 20 }, () => '0'),
merkle_proof_length: '1',
merkle_root: merkleRoot.toString(),
slot_address: merkleRoot.toString(), // temporary, will be replaced with the actual slot address.
slot_address: slotAddress,
balance: '0',
}
}
Expand Down
3 changes: 2 additions & 1 deletion examples/CRISP/packages/crisp-sdk/tests/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ export const LEAVES = [
export const MAX_DEPTH = 20

export const votingPowerLeaf = 1000n
export const merkleProof = generateMerkleProof(0n, votingPowerLeaf, '0x1234567890123456789012345678901234567890', LEAVES, MAX_DEPTH)
export const testAddress = '0x1234567890123456789012345678901234567890'
export const merkleProof = generateMerkleProof(0n, votingPowerLeaf, testAddress, LEAVES, MAX_DEPTH)
44 changes: 35 additions & 9 deletions examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { describe, it, expect } from 'vitest'
import { ZKInputsGenerator } from '@enclave/crisp-zk-inputs'

import {
calculateValidIndicesForPlaintext,
decodeTally,
Expand All @@ -18,12 +17,10 @@ import {
verifyProof,
} from '../src/vote'
import { BFVParams, VotingMode } from '../src/types'
import { DEFAULT_BFV_PARAMS, MAXIMUM_VOTE_VALUE } from '../src'
import { UltraHonkBackend, type ProofData } from '@aztec/bb.js'
import { Noir, type CompiledCircuit } from '@noir-lang/noir_js'
import circuit from '../../../circuits/target/crisp_circuit.json'
import { DEFAULT_BFV_PARAMS, generateMerkleProof, hashLeaf, MAXIMUM_VOTE_VALUE } from '../src'

import { merkleProof, MESSAGE, SIGNATURE, VOTE, votingPowerLeaf } from './constants'
import { LEAVES, merkleProof, MESSAGE, SIGNATURE, testAddress, VOTE, votingPowerLeaf } from './constants'
import { privateKeyToAccount } from 'viem/accounts'

describe('Vote', () => {
const votingPower = 10n
Expand Down Expand Up @@ -141,6 +138,7 @@ describe('Vote', () => {
message: MESSAGE,
merkleData: merkleProof,
balance: votingPowerLeaf,
slotAddress: testAddress,
})

expect(crispInputs.prev_ct0is).toBeInstanceOf(Array)
Expand Down Expand Up @@ -171,7 +169,7 @@ describe('Vote', () => {

describe('generateMaskVote', () => {
it('should generate a mask vote and the right circuit inputs', async () => {
const crispInputs = await generateMaskVote(publicKey, previousCiphertext, DEFAULT_BFV_PARAMS, merkleProof.proof.root)
const crispInputs = await generateMaskVote(publicKey, previousCiphertext, DEFAULT_BFV_PARAMS, merkleProof.proof.root, testAddress)

expect(crispInputs.prev_ct0is).toBeInstanceOf(Array)
expect(crispInputs.prev_ct1is).toBeInstanceOf(Array)
Expand Down Expand Up @@ -200,22 +198,50 @@ describe('Vote', () => {
})

describe('generateProof/verifyProof', () => {
it('should generate a proof and verify it', { timeout: 60000 }, async () => {
it('should generate a proof for a voter and verify it', { timeout: 180000 }, async () => {
const encodedVote = encodeVote(VOTE, VotingMode.GOVERNANCE, votingPower)

// hardhat default private key
const privateKey = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
const account = privateKeyToAccount(privateKey)
const signature = await account.signMessage({ message: MESSAGE })
const leaf = hashLeaf(account.address.toLowerCase(), votingPowerLeaf.toString())
const leaves = [...LEAVES, leaf]
const merkleProof = generateMerkleProof(0n, votingPowerLeaf, account.address.toLowerCase(), leaves, 20)

const inputs = await encryptVoteAndGenerateCRISPInputs({
encodedVote,
publicKey,
previousCiphertext,
signature: SIGNATURE,
signature,
message: MESSAGE,
merkleData: merkleProof,
balance: votingPowerLeaf,
slotAddress: account.address.toLowerCase(),
})

const proof = await generateProof(inputs)
const isValid = await verifyProof(proof)

expect(isValid).toBe(true)
})

it('should generate a proof for a masking user and verify it', { timeout: 180000 }, async () => {
const encodedVote = encodeVote(VOTE, VotingMode.GOVERNANCE, votingPower)
const zkInputsGenerator: ZKInputsGenerator = new ZKInputsGenerator(
DEFAULT_BFV_PARAMS.degree,
DEFAULT_BFV_PARAMS.plaintextModulus,
DEFAULT_BFV_PARAMS.moduli,
)
const vote = BigInt64Array.from(encodedVote.map(BigInt))
const encryptedVote = zkInputsGenerator.encryptVote(publicKey, vote)

let maskVote = await generateMaskVote(publicKey, encryptedVote, DEFAULT_BFV_PARAMS, merkleProof.proof.root, testAddress)

const proof = await generateProof(maskVote)
const isValid = await verifyProof(proof)

expect(isValid).toBe(true)
})
Comment thread
ctrlc03 marked this conversation as resolved.
})
})
Loading