Zero-Knowledge Proof Multiplier on Solana
This project implements a zk-SNARK (Zero-Knowledge Succinct Non-Interactive Argument of Knowledge) system on Solana to verify multiplications without revealing input factors. It demonstrates the integration of Circom circuits, the Groth16 proving system, and Solana’s cryptographic primitives.
Technical Stack
- Circuit Language: Circom
- Proving System: Groth16
- Blockchain: Solana (targeting v1.17+ for full compatibility)
- Libraries: snarkjs, ffjavascript
- Development Environment: Rust, TypeScript
Key Components
1. Circom Circuit
template Multiplier() {
signal private input a;
signal private input b;
signal output c;
c <== a * b;
}
component main = Multiplier();
This circuit defines the computation to be proved: c = a * b
, where a
and b
are private inputs.
2. Trusted Setup
Utilizes Powers of Tau ceremony for the initial setup:
- Initialize parameters:
powersoftau new bn128 12 pot12_0000.ptau
- Contribute randomness:
powersoftau contribute pot12_0000.ptau pot12_0001.ptau
- Apply random beacon:
powersoftau beacon pot12_0001.ptau pot12_beacon.ptau
- Prepare for phase 2:
powersoftau prepare phase2 pot12_beacon.ptau pot12_final.ptau
3. Circuit Compilation
Compiles the Circom circuit to generate R1CS, WASM, and symbolic files:
circom Multiplier.circom --r1cs --wasm --sym
4. Solana Verifier Contract
Exports the verification key and implements Groth16 verification:
pub const VERIFYINGKEY: Groth16Verifyingkey = Groth16Verifyingkey {
// ... (elliptic curve points)
};
pub fn verify(&mut self) -> Result<bool, Groth16Error> {
self.prepare_inputs()?;
// ... (pairing checks implementation)
}
Utilizes Solana’s alt_bn128
syscalls for efficient pairing operations.
5. Proof Generation and Verification
TypeScript code for generating and formatting proofs:
let { proof, publicSignals } = await snarkjs.groth16.fullProve(input, wasmPath, zkeyPath);
let curve = await buildBn128();
let proofProc = unstringifyBigInts(proof);
publicSignals = unstringifyBigInts(publicSignals);
let pi_a = g1Uncompressed(curve, proofProc.pi_a);
let pi_b = g2Uncompressed(curve, proofProc.pi_b);
let pi_c = g1Uncompressed(curve, proofProc.pi_c);
6. On-chain Verification
Rust test function for on-chain proof verification:
#[test]
fn proof_verification_should_succeed() {
let mut verifier = Groth16Verifier::new(&proof_a, &proof_b, &proof_c, &PUBLIC_INPUTS, &VERIFYING_KEY).unwrap();
verifier.verify().unwrap();
}
Technical Challenges and Solutions
-
Endianness Conversion: Implemented custom functions to handle big-endian to little-endian conversion for Solana compatibility.
-
Proof Formatting: Developed utility functions (
g1Uncompressed
,g2Uncompressed
) to format elliptic curve points for the Solana program. -
Compute Budget: Implemented compute budget instructions to allocate sufficient resources for on-chain verification:
transaction.add(web3.ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 })); transaction.add(web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 2 }));
-
Solana Versioning: Project targets Solana v1.17+ due to the requirement for efficient pairing operations via new syscalls.
Performance Considerations
- Proof generation is performed off-chain to minimize on-chain computation.
- Verification requires approximately 200,000 compute units on Solana.
- The
alt_bn128
curve is used for its efficiency in pairing operations.
Security Notes
- The security of the system relies on the integrity of the trusted setup phase.
- The verifier contract must be correctly deployed and not tampered with.
- Input validation is crucial to prevent invalid proof submissions.
Future Optimizations
- Implement recursive SNARKs for improved scalability.
- Explore Plonk or other proving systems for potentially reduced proof sizes.
- Implement batched verification for improved throughput.