Introduction to CRISP
CRISP (Coercion-Resistant Impartial Selection Protocol) is a secure protocol for digital decision-making, leveraging fully homomorphic encryption (FHE) and distributed threshold cryptography (DTC) to enable verifiable secret ballots. Built with Enclave, CRISP safeguards democratic systems and decision-making applications against coercion, manipulation, and other vulnerabilities.
This project serves as a comprehensive example of an E3 Program, demonstrating secure and impartial decision-making processes with a modern Hardhat-based architecture. It showcases a complete full-stack implementation including frontend, backend, smart contracts, and zero-knowledge components.
Why CRISP?
Open ballots are known to produce suboptimal outcomes, exposing participants to bribery and coercion. CRISP mitigates these risks through:
- Secret ballots: Votes remain encrypted throughout the process
- Receipt-free voting: No proof of vote can be generated for coercion
- Verifiable results: Zero-knowledge proofs ensure computation integrity
- Distributed trust: No single point of failure or control
Project Structure
CRISP, whose implementation is available in the Enclave repository (opens in a new tab), follows a modern structure with clear separation of concerns:
CRISP/
├── client/ # React frontend application (Vite + @crisp-e3/sdk)
├── server/ # Rust coordination server & CLI
├── program/ # FHE program for encrypted computation + RISC Zero verification
├── packages/
│ ├── crisp-contracts/ # CRISP program contract + Hardhat deployment scripts
│ └── crisp-sdk/ # TypeScript helpers to generate a ZK proof
├── crates/ # Rust libraries used by the server
├── circuits/ # Noir zero-knowledge circuits
├── scripts/ # Development scripts for running, testing, and deployment
├── enclave.config.yaml # Ciphernodes + aggregator config
└── docker-compose.yaml # Optional multi-node deploymentClient Application
The client (opens in a new tab) is a React application built with TypeScript that provides a voting interface. It uses the CRISP SDK and server endpoints to deliver the following capabilities:
- Connecting wallets via MetaMask
- Encrypting votes using FHE encryption before submission
- Generating Noir zero-knowledge proofs for vote validation
- Displaying real-time updates on voting status and results
Coordination Server
The server (opens in a new tab) is a Rust-based coordination service that manages the E3 lifecycle and drives the same SDK from a privileged wallet. It acts as a relayer and facilitator, handling blockchain interactions on behalf of clients. However, clients can bypass the server entirely and interact directly with the blockchain if they prefer.
The server's key responsibilities include:
- Monitoring blockchain events and coordinating protocol progression
- Collecting encrypted votes from smart contracts
- Triggering FHE computations after voting rounds are closed
- Publishing computation results back to the blockchain
- Providing a RESTful API for client interactions
FHE Program
The FHE program (opens in a new tab) is a Rust guest program that runs inside the RISC Zero zkVM to perform homomorphic addition on encrypted votes (BFV ciphertexts). It computes the encrypted tally without decrypting individual votes, ensuring voter privacy throughout the process.
The program decodes BFV parameters and ciphertexts, then sums all encrypted votes homomorphically to produce a single encrypted ciphertext containing the total. RISC Zero generates zero-knowledge proofs that verify the computation was executed correctly, enabling trustless verification of the tally results.
Zero-knowledge Circuits
Noir circuits (opens in a new tab) provide zero-knowledge proofs for vote validation before votes are accepted into the system. These circuits verify that votes are correctly encrypted using BFV fully homomorphic encryption under a valid public key, check that the voter's address is included in the eligibility Merkle tree, and ensure votes conform to the expected structure and values. The circuits also support mask votes, allowing anyone to submit zero votes to mask slot activity and reduce the risk of collusion and coercion.
For detailed documentation on how the circuit works, see the main circuit implementation (opens in a new tab).
Smart Contracts
The CRISP smart contracts implement the E3 program interface and handle the on-chain logic for voting rounds. The main contract is CRISPProgram.sol (opens in a new tab), which orchestrates the entire voting process.
When a new voting round is initialized, the validate() function sets up round parameters and
initializes a Merkle tree to store votes efficiently. As votes are submitted, validateInput()
processes each encrypted vote by verifying its Noir zero-knowledge proof through the CRISPVerifier
contract, ensuring votes are correctly encrypted and voters are eligible. Votes are stored in the
Merkle tree with slot addresses mapping to vote indices, allowing updates while maintaining
uniqueness.
After the voting period ends and FHE computation completes, the verify() function validates the
RISC Zero proof attesting to the correctness of the homomorphic tally computation, verifying it
corresponds to the correct input Merkle root, parameters hash, and ciphertext output. Once verified,
decodeTally() decodes the encrypted results into readable yes and no vote counts.
The CRISPVerifier.sol (opens in a new tab) contract is a Honk verifier generated from compiled Noir circuits that verifies zero-knowledge proofs demonstrating votes are correctly encrypted using BFV fully homomorphic encryption under the valid public key.
JavaScript SDK
The CRISP SDK (opens in a new tab) is a TypeScript library that abstracts away the complex cryptographic operations required for vote submission and proof generation. It fetches round details from the coordination server, queries token balances at snapshot blocks to verify eligibility, and generates Merkle proofs demonstrating voter inclusion in the eligibility tree.
When casting a vote, the SDK encrypts votes using BFV fully homomorphic encryption under the committee's public key and generates zero-knowledge proofs using compiled Noir circuits. These proofs demonstrate that votes are correctly encrypted and that voters are eligible to participate. The SDK also supports mask votes, generating proofs for zero-value votes that help reduce the risk of collusion and coercion. Before submission, proofs are verified locally to prevent failed transactions, and after voting concludes, the SDK decodes encrypted tally results into readable vote counts.
Next Steps
To get started with CRISP:
- Setup: Install dependencies and configure your environment
- Running E3: Deploy and run the complete CRISP protocol
- Study the code: Examine the implementation for your own E3 programs
CRISP demonstrates the full potential of E3 programs for privacy-preserving applications, providing a complete reference implementation for developers building with Enclave.