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
102 changes: 73 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,6 @@ permissions:
packages: write

jobs:
crisp_rust_unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust 1.86.0
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.86.0

# We must install foundry in order to be able to test anvil
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Install solc
run: |
sudo add-apt-repository ppa:ethereum/ethereum \
&& sudo apt-get update -y \
&& sudo apt-get install -y solc

- name: Run CRISP Unit Tests
working-directory: ./examples/CRISP
run: 'cargo test'

rust_unit:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -439,6 +415,79 @@ jobs:
path: ~/.cargo/bin/enclave
retention-days: 1

# CRISP Unit Tests (Rust, JavaScript, Noir, Solidity)
crisp_unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Rust 1.86.0
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.86.0

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Install Nargo
uses: noir-lang/noirup@v0.1.4
with:
toolchain: v1.0.0-beta.15

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10.7.1
run_install: false

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
working-directory: .
run: |
pnpm --version && pnpm install --frozen-lockfile && pnpm build:ts

- name: Install solc
run: |
sudo add-apt-repository ppa:ethereum/ethereum \
&& sudo apt-get update -y \
&& sudo apt-get install -y solc

- name: Run Rust tests
working-directory: ./examples/CRISP
run: 'cargo test'

- name: Run Noir tests
working-directory: ./examples/CRISP
run: 'pnpm test:circuits'

- name: Run JavaScript tests
working-directory: ./examples/CRISP
run: 'pnpm test:sdk'

- name: Run Solidity tests
working-directory: ./examples/CRISP
run: 'pnpm build:sdk && pnpm test:contracts'

crisp_e2e:
runs-on: ubuntu-latest
needs: [build_enclave_cli]
Expand Down Expand Up @@ -536,11 +585,6 @@ jobs:
path: ./examples/CRISP/playwright-report/
retention-days: 30

- name: Test Noir circuits
working-directory: ./examples/CRISP
run: |
pnpm test:circuits

test_enclave_circuits:
runs-on: ubuntu-latest
steps:
Expand Down
5 changes: 3 additions & 2 deletions examples/CRISP/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
"ciphernode:add:self": "pnpm -C packages/crisp-contracts ciphernode:add:self",
"deploy:contracts": "pnpm -C packages/crisp-contracts deploy:contracts",
"test": "pnpm test:e2e",
"test:circuits:inputs": "node --test test/governanceCircuit.test.ts",
"test:circuits": "cd circuits && nargo test",
"compile:circuit": "bash ./scripts/compile_circuits.sh",
"compile:circuits": "bash ./scripts/compile_circuits.sh",
"test:sdk": "pnpm -C packages/crisp-sdk test",
"build:sdk": "pnpm -C packages/crisp-sdk build",
"report": "playwright show-report",
"publish:packages": "tsx scripts/publish.ts",
"setup:testnet": "bash ./scripts/setup_testnet.sh"
Expand Down
5 changes: 5 additions & 0 deletions examples/CRISP/packages/crisp-sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ export type CircuitInputs = {
is_first_vote: boolean
}

export type ExecuteCircuitResult = {
witness: Uint8Array
returnValue: [Polynomial[][], Polynomial[][]]
}

export type ProofInputs = {
vote: Vote
publicKey: Uint8Array
Expand Down
12 changes: 5 additions & 7 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 '@crisp-e3/zk-inputs'
import { type CircuitInputs, type Vote, MaskVoteProofInputs, ProofInputs, VoteProofInputs } from './types'
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 { Noir, type CompiledCircuit } from '@noir-lang/noir_js'
Expand Down Expand Up @@ -120,17 +120,15 @@ export const generateCircuitInputs = async (proofInputs: ProofInputs): Promise<C
return crispInputs
}

export const generateWitness = async (crispInputs: CircuitInputs): Promise<Uint8Array> => {
export const executeCircuit = async (crispInputs: CircuitInputs): Promise<ExecuteCircuitResult> => {
const noir = new Noir(circuit as CompiledCircuit)

const { witness } = await noir.execute(crispInputs as any)

return witness
return noir.execute(crispInputs) as Promise<ExecuteCircuitResult>
}

export const generateProof = async (crispInputs: CircuitInputs): Promise<ProofData> => {
const witness = await generateWitness(crispInputs)
const backend = new UltraHonkBackend((circuit as CompiledCircuit).bytecode, { threads: optimalThreadCount })
const { witness } = await executeCircuit(crispInputs)
const backend = new UltraHonkBackend(circuit.bytecode, { threads: optimalThreadCount })

const proof = await backend.generateProof(witness, { keccakZK: true })

Expand Down
143 changes: 72 additions & 71 deletions examples/CRISP/packages/crisp-sdk/tests/vote.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
generatePublicKey,
verifyProof,
encodeVote,
generateProof,
generateCircuitInputs,
executeCircuit,
} from '../src/vote'
import { publicKeyToAddress, signMessage } from 'viem/accounts'
import { Hex, recoverPublicKey } from 'viem'
Expand Down Expand Up @@ -141,7 +141,7 @@ describe('Vote', () => {
})

describe('generateProof', () => {
it('Should generate a proof where the output is the new ciphertext', { timeout: 100000 }, async () => {
it('Should generate a proof where the output is the new ciphertext', async () => {
// This test simulates a real vote (i.e. generateVoteProof).

// Using generateCircuitInputs directly to check the output of the circuit.
Expand All @@ -155,81 +155,82 @@ describe('Vote', () => {
merkleProof,
})

const proof = await generateProof(crispInputs)

expect(proof).toBeDefined()
expect(proof.proof).toBeDefined()
expect(proof.publicInputs).toBeDefined()
const { returnValue } = await executeCircuit(crispInputs)

const ct0is = crispInputs.ct0is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const ct1is = crispInputs.ct1is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const outputCiphertext = proof.publicInputs.slice(2).map((b) => BigInt(b))
const outputCt0is = returnValue[0]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))
const outputCt1is = returnValue[1]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))

expect([...outputCt0is, ...outputCt1is]).toEqual([...ct0is, ...ct1is])
})

it('Should generate a proof where the output is the ciphertext addition if there is a previous ciphertext and 0 vote', async () => {
// This test simulates a mask vote (i.e. generateMaskVoteProof).

// 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 },
publicKey,
previousCiphertext,
signature: MASK_SIGNATURE,
merkleProof,
balance,
slotAddress,
})

expect([...ct0is, ...ct1is]).toEqual(outputCiphertext)
const { returnValue } = await executeCircuit(crispInputs)

const sumCt0is = crispInputs.sum_ct0is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const sumCt1is = crispInputs.sum_ct1is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const outputSumCt0is = returnValue[0]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))
const outputSumCt1is = returnValue[1]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))

expect([...outputSumCt0is, ...outputSumCt1is]).toEqual([...sumCt0is, ...sumCt1is])
})

it(
'Should generate a proof where the output is the ciphertext addition if there is a previous ciphertext and 0 vote',
{ timeout: 100000 },
async () => {
// This test simulates a mask vote (i.e. generateMaskVoteProof).

// 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 },
publicKey,
previousCiphertext,
signature: MASK_SIGNATURE,
merkleProof,
balance,
slotAddress,
})

const proof = await generateProof(crispInputs)

expect(proof).toBeDefined()
expect(proof.proof).toBeDefined()
expect(proof.publicInputs).toBeDefined()

const sumCt0is = crispInputs.sum_ct0is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const sumCt1is = crispInputs.sum_ct1is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const outputCiphertext = proof.publicInputs.slice(2).map((b) => BigInt(b))

expect([...sumCt0is, ...sumCt1is]).toEqual(outputCiphertext)
},
)

it(
'Should generate a proof where the output is the ciphertext of a 0 vote if there is no previous ciphertext',
{ timeout: 100000 },
async () => {
// This test simulates a mask vote (i.e. generateMaskVoteProof).

// 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 },
publicKey,
signature: MASK_SIGNATURE,
merkleProof,
balance,
slotAddress,
})

const proof = await generateProof(crispInputs)

expect(proof).toBeDefined()
expect(proof.proof).toBeDefined()
expect(proof.publicInputs).toBeDefined()

const ct0is = crispInputs.ct0is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const ct1is = crispInputs.ct1is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const outputCiphertext = proof.publicInputs.slice(2).map((b) => BigInt(b))

expect([...ct0is, ...ct1is]).toEqual(outputCiphertext)
},
)
it('Should generate a proof where the output is the ciphertext of a 0 vote if there is no previous ciphertext', async () => {
// This test simulates a mask vote (i.e. generateMaskVoteProof).

// 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 },
publicKey,
signature: MASK_SIGNATURE,
merkleProof,
balance,
slotAddress,
})

const { returnValue } = await executeCircuit(crispInputs)

const ct0is = crispInputs.ct0is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const ct1is = crispInputs.ct1is.flatMap((p) => p.coefficients).map((b) => BigInt(b))
const outputCt0is = returnValue[0]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))
const outputCt1is = returnValue[1]
.flat()
.flatMap((p) => p.coefficients)
.map((b) => BigInt(b))

expect([...outputCt0is, ...outputCt1is]).toEqual([...ct0is, ...ct1is])
})
})

describe('generateVoteProof', () => {
Expand Down
6 changes: 3 additions & 3 deletions examples/CRISP/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ echo "SETUP..."
echo "pnpm install"
(cd ../../ && pnpm install --frozen-lockfile)
echo "sdk"
(cd packages/crisp-sdk && pnpm install && pnpm build)
(pnpm build:sdk)
echo "evm"
(cd ../../packages/enclave-contracts && pnpm compile)
(cd packages/crisp-contracts && pnpm compile)
(cd ../../packages/enclave-contracts && pnpm compile:contracts)
(pnpm compile:contracts)
echo "server"
(cd ./server && [[ ! -f .env ]] && cp .env.example .env; cargo build --locked --bin cli && cargo build --locked --bin server)
echo "client"
Expand Down
1 change: 1 addition & 0 deletions examples/CRISP/scripts/test_e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ fi

echo "TEST E2E SCRIPT STARTING..."
pnpm concurrently -krs first "./scripts/setup.sh && ./scripts/dev.sh" "wait-on tcp:3000 && sleep 20 && ${PLAYWRIGHT_CMD} && sleep 3"

Loading