Circom circuits for browser-side ZK proof generation in TSN shielded transactions.
These circuits mirror the Rust arkworks circuits exactly, enabling:
- Browser proving: Generate proofs client-side using snarkjs
- Server verification: Verify proofs on the Rust backend
- Cross-platform compatibility: Same constraints, same verification
Proves valid consumption of a note:
- Note commitment was correctly computed
- Note exists in the commitment tree (Merkle proof)
- Nullifier was correctly derived
Proves valid creation of a note:
- Note commitment was correctly computed
- Node.js >= 16
- circom >= 2.1.0
- snarkjs
npm installnpm run compile:allDownload powers of tau ceremony file:
wget https://hermez.s3-eu-west-1.amazonaws.com/powersOfTau28_hez_final_20.ptauGenerate circuit-specific keys:
npm run setup:spend
npm run setup:outputContribute randomness (optional but recommended):
snarkjs zkey contribute build/spend_0000.zkey build/spend_final.zkey
snarkjs zkey contribute build/output_0000.zkey build/output_final.zkeyExport verification keys:
npm run export:spend-vkey
npm run export:output-vkeyThese MUST match the Rust implementation:
DOMAIN_NOTE_COMMITMENT = 1DOMAIN_VALUE_COMMITMENT_HASH = 2DOMAIN_NULLIFIER = 3DOMAIN_MERKLE_EMPTY = 4DOMAIN_MERKLE_NODE = 5
{
"merkleRoot": "field_element_as_decimal_string",
"nullifier": "field_element_as_decimal_string",
"valueCommitmentHash": "field_element_as_decimal_string",
"value": 1000,
"recipientPkHash": "field_element_as_decimal_string",
"noteRandomness": "field_element_as_decimal_string",
"nullifierKey": "field_element_as_decimal_string",
"pathElements": ["field_element", ...], // 32 elements
"pathIndices": [0, 1, 0, ...], // 32 bits
"position": 5
}{
"noteCommitment": "field_element_as_decimal_string",
"valueCommitmentHash": "field_element_as_decimal_string",
"value": 1000,
"recipientPkHash": "field_element_as_decimal_string",
"noteRandomness": "field_element_as_decimal_string"
}After setup, distribute these files with your wallet:
build/spend.wasm- Spend circuit WASM (~1-2MB)build/output.wasm- Output circuit WASM (~1-2MB)build/spend_final.zkey- Spend proving key (~50-100MB)build/output_final.zkey- Output proving key (~50-100MB)build/spend_vkey.json- Spend verification key (~1KB)build/output_vkey.json- Output verification key (~1KB)
import * as snarkjs from 'snarkjs';
async function generateSpendProof(witness) {
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
witness,
'/circuits/spend.wasm',
'/circuits/spend_final.zkey'
);
return { proof, publicSignals };
}npm test- The trusted setup ceremony should involve multiple independent contributors
- Proving keys should be verified against the verification keys before use
- Circuit parameters must match the Rust backend exactly