Research: Circom Circuit Design for Writz Protocol
Author: Research Date: 2026-06-22 Status: CompleteOverview
Writz requires three distinct ZK circuits:- Deposit circuit — prove BTC was deposited and create a private position commitment
- Borrow/repay circuit — prove the state transition of a position without revealing amounts
- Liquidation circuit — prove a position is undercollateralized without revealing amounts
Chosen Stack: Circom + Groth16 + BN254
Why Circom:- Most mature ZK circuit language in production (Tornado Cash, Zcash Sapling, Stellar Private Payments)
- Compiles to R1CS (Rank-1 Constraint System) — directly compatible with Groth16
- Large library of reusable components (Poseidon hash, Merkle proofs, comparators, bit decomposition)
- Browser-based proof generation via snarkjs + WebAssembly
- Constant-size proofs: 192 bytes regardless of circuit complexity
- Fastest verification: single bilinear pairing check
- Production-proven on Soroban via Protocol X-Ray (BN254 pairing host function)
- Stellar Private Payments uses exactly this stack — direct reference available
- Protocol 25 added BN254 host functions specifically
- Protocol 26 added BN254 MSM and scalar arithmetic host functions
- Most Circom tooling targets BN254
- Same curve as Ethereum EIP-196/197 — large ecosystem of audited tooling
Shared Components
Poseidon Hash (ZK-friendly)
All commitments use Poseidon instead of SHA256. Poseidon operates over the BN254 scalar field, making it 10–100x more efficient in Wasm circuits.Merkle Tree Inclusion Proof
All position commitments are stored in an on-chain Merkle tree. ZK proofs must prove membership in this tree without revealing which leaf.Circuit 1: Deposit
Purpose
Prove that a valid BTC deposit has been made via SPV and create a private commitment to the position.Note on separation
The SPV verification (Bitcoin Merkle proof + header chain) is done in a separate Soroban contract, not in this Circom circuit. SPV is verified on-chain using the Rust SPV library. The Circom circuit only handles the ZK privacy layer — creating the commitment.Inputs/Outputs
- Soroban SPV contract verifies BTC transaction → emits:
verified(txid, amount_satoshis, recipient_p2wsh_address) - PrivateLend reads SPV verification result
- User submits ZK deposit proof with
commitmentas public output - PrivateLend adds
commitmentto the Merkle tree - User stores
(collateral_satoshis, 0, secret, nonce, commitment_index)privately
Constraint count estimate
Poseidon(4): ~240 constraints. Range check: ~32 constraints. Total: ~280 constraints. Very light circuit.Circuit 2: Borrow / Repay (State Transition)
Purpose
Prove that a position’s debt is being correctly updated (borrowing more USDC or repaying) without revealing the actual amounts.Inputs/Outputs
Constraint count estimate
- Old commitment: ~240 constraints
- Merkle proof (depth 20): ~20 × 240 = ~4,800 constraints
- New commitment: ~240 constraints
- Merkle root update: ~4,800 constraints
- Range checks + arithmetic: ~200 constraints
- Division for ratio: ~300 constraints (expensive in ZK)
- Total: ~10,500 constraints
Circuit 3: Liquidation
Purpose
Prove that a specific position (identified by its commitment) is undercollateralized, without revealing the amounts, enabling a liquidator to claim the collateral.Inputs/Outputs
Privacy tradeoff in liquidation
There’s a fundamental tension in private liquidations: the liquidator needs to know how much USDC to pay to execute the liquidation. This meansusdc_to_repay (the debt) must be a public input.
Options:
- Reveal debt publicly — simplest, but partially breaks privacy
- Trusted keeper pays the debt — keeper knows the private amounts, handles the USDC payment without public disclosure
- Encrypted debt in commitment — liquidator receives an encrypted hint containing the debt amount, decryptable only with a specific key
Trusted Setup Ceremony
Groth16 requires a per-circuit trusted setup. The setup has two phases: Phase 1 (Powers of Tau): A multi-party ceremony generating universal SRS (Structured Reference String) parameters. Stellar Private Payments uses the Hermez Powers of Tau ceremony — Writz can reuse this. Already done. Phase 2 (Circuit-specific): A circuit-specific setup generating the proving key and verification key. This must be done separately for each of Writz’s three circuits. It is a one-time event per circuit version. Ceremony requirements for production:- Minimum 5 independent participants (more is better)
- At least one participant must be trustworthy (the setup is secure if at least one person destroys their randomness)
- Publicly verifiable transcript
- Can be done by the Writz team + community members + security researchers
Browser-Side Proof Generation
Proof generation runs on the client side via WebAssembly. Users generate their own ZK proofs in the browser before submitting to Soroban. Proof generation time benchmarks (Groth16, snarkjs in WASM):- Deposit circuit (~280 constraints): < 1 second
- Borrow/Repay circuit (~10,500 constraints): 3–8 seconds
- Liquidation circuit (~9,000 constraints): 2–6 seconds
.wasmfile (circuit compiled to WebAssembly).zkeyfile (proving key from trusted setup)- Both served from Writz frontend / CDN
Key Findings
- Three circuits needed: Deposit (light), Borrow/Repay (medium), Liquidation (medium)
- Circom + Groth16 on BN254 is the right stack — production-proven on Stellar
- Merkle tree depth 20 — supports 1M positions, adds ~4,800 constraints per circuit
- Division in ZK is expensive — collateral ratio checks require range proofs; benchmark carefully
- Trusted setup is required — one-time ceremony per circuit; pre-mainnet mandatory item
- Liquidation privacy is partially limited — debt amount revealed in Phase 1; keeper-based approach mitigates
- Browser-side proof generation is viable — 3–8 second proof time is acceptable for DeFi
Last updated: 2026-06-22 Sources: Circom Documentation · NethermindEth/stellar-private-payments · ShieldLend Architecture · RareSkills: Intro to ZK Circuits with Circom