Skip to content

Latest commit

 

History

History
167 lines (114 loc) · 8.59 KB

tech-stack.md

File metadata and controls

167 lines (114 loc) · 8.59 KB

ShieldLend V2A — Tech Stack Rationale

Every technology choice in ShieldLend V2A was made deliberately. This document explains the reasoning behind each decision and what was evaluated and rejected.


Chain: Base Sepolia (testnet) / Base (mainnet target)

What it is: An EVM Layer 2 on Ethereum. Full Solidity compatibility, low gas fees, and the same toolchain as Ethereum mainnet.

Why Base for V2A:

  • EVM-compatible: all Solidity contracts, Foundry tests, and wagmi frontend work unchanged
  • Low gas fees — epoch flush with dummy insertion and shuffling would be impractical on Ethereum L1
  • Fast finality (~2s block time) — allows the 50-block epoch wait (~100s) to be reasonable UX
  • Base Sepolia testnet has reliable public RPC endpoints and a well-stocked faucet
  • BaseScan block explorer works out of the box for debugging

What was evaluated and rejected:

  • Ethereum L1: proof verification gas (~500k) makes withdrawals cost $30+ per tx even with zkVerify
  • Polygon zkEVM/Scroll: not purpose-built for privacy DeFi; more complex bridge setup for zkVerify
  • Aztec: would require rewriting all circuits in Noir and deploying on Aztec infrastructure

Proof Verification: zkVerify Volta

What it is: A modular, chain-agnostic proof verification layer. Accepts Groth16 proofs via SDK and emits on-chain attestations that any EVM contract can verify.

Why zkVerify:

Cost: On-chain Groth16 verification costs 250-500k gas on Ethereum L1 ($5-30 per proof at moderate gas). zkVerify moves verification off-chain — reducing per-user verification cost by ~91%. On Base L2 the direct gas cost is lower, but zkVerify adds proof aggregation infrastructure that scales to multi-proof batching without changing the on-chain interface.

Decoupled verification: Proof verification is handled by a dedicated verifier network, not bundled into the application contract. This means circuit upgrades (new ptau, new vkey) don't require redeploying ShieldedPool — only the vkey registered with zkVerify changes.

Modularity: The same proof submission pipeline works on any EVM chain. No chain-specific changes needed to the withdrawal logic.

Single-leaf aggregation: V2A uses a single-leaf aggregation pattern — each proof is its own 1-leaf aggregation tree. This simplifies integration while remaining compatible with multi-proof batching in production.

What was evaluated:

  • On-chain Groth16 Solidity verifier (generated by snarkjs): works but ties verification to a specific chain; acceptable on L2 but chains the verifier to a deployment
  • PLONK with universal setup: no per-circuit ceremony, but larger proofs (~700 bytes vs 192 bytes) and slower verification

Circuit Language: Circom 2.1.6 + circomlib

What it is: Circom is a DSL for ZK-SNARK arithmetic circuits. circomlib is the standard template library.

Why Circom:

  • Most mature ZK toolchain for the EVM ecosystem — largest community, most production deployments
  • snarkjs compiles Circom circuits to WebAssembly — enables browser-side proof generation with no server trust assumption
  • circomlib provides: Poseidon hash, MerkleTreeChecker, GreaterEqThan comparators, MultiMux1 — all used in V2A circuits
  • Tornado Cash (the architectural inspiration) was written in Circom — circuits are a direct reference

What was evaluated and rejected:

  • Noir (Aztec): different language, requires Aztec infrastructure
  • Halo2 (zcash): Rust-based, no snarkjs/WASM compilation, no browser proving
  • Leo (Aleo): Aleo-specific, not EVM-compatible

Proof System: Groth16 (via snarkjs)

What it is: Non-interactive ZK proof system producing 3 elliptic curve points (192 bytes).

Why Groth16:

  • Smallest proof size of any practical SNARK (192 bytes) — minimizes calldata and zkVerify submission cost
  • Fastest verification — 3 pairing operations regardless of circuit size
  • snarkjs has first-class Groth16 support and compiles to WASM
  • zkVerify supports Groth16 natively

Trade-off: Groth16 requires a per-circuit trusted setup (Powers of Tau + circuit-specific contribution). PLONK uses a universal setup but produces larger proofs. For V2A, Groth16 proof size and verification speed outweigh the setup overhead.


ZK Circuits: Ring Proofs (K=16, LEVELS=24)

This is the core V2A innovation over V1.

V1 used standard Merkle membership proofs (same as Tornado Cash). Problem: with a sparse pool, the anonymity set is trivially small — timing correlation attacks work even with valid ZK proofs.

V2A ring approach:

  • Prover selects K=16 commitments from the last 30 epoch flushes
  • These include real deposits AND protocol-inserted dummy commitments
  • The proof shows the prover's note is one of the 16, without revealing which
  • With 10 dummies/epoch × 30 epochs = 300 minimum anonymity set at launch
  • ring_index (which position is the prover's) is a private circuit input

Result: anonymity guarantees hold even at protocol launch with zero real users.


Note Encryption: AES-256-GCM + HKDF

V1 stored notes as plaintext in localStorage. V2A encrypts every note before storage.

Key derivation:

  1. User signs a fixed message with MetaMask: "ShieldLend: Sign to derive vault key"
  2. The 65-byte ECDSA signature is the key material
  3. HKDF(keccak256("ShieldLend note key") as salt, signature as IKM) -> 32-byte AES key

Why this approach:

  • Key is deterministic — user can recover all notes on any device by signing the same message
  • Key is wallet-bound — only the wallet owner can decrypt
  • Key never touches a server — derived entirely client-side
  • AES-256-GCM provides authenticated encryption — prevents silent tampering

Epoch Batching + Dummy Insertion

This is the temporal privacy layer — it breaks timing correlation between deposit and withdrawal.

How it works:

  1. All deposits go into pendingCommitments[] queue (not immediately in the Merkle tree)
  2. Every 50 blocks (~100s on Base Sepolia), flushEpoch() is called
  3. The batch is shuffled using block.prevrandao (unmanipulable by miners post-merge)
  4. Adaptive dummy commitments are inserted alongside real ones (2/5/10 dummies based on batch size)
  5. All commitments (real + dummy) are inserted into the Merkle tree together

Why this matters: without batching, a deposit at block N followed by a withdrawal at block N+50 is easily identifiable even with valid ZK proofs — there are only a handful of deposits in that range. Batching creates an indistinguishable group.


Smart Contracts: Solidity + Foundry

Why Foundry over Hardhat:

  • Significantly faster test execution (Rust vs Node.js)
  • forge test --match-test for targeted runs during development
  • forge script for reproducible deployments with full type safety
  • 60+ tests passing in contracts/test/, including SecurityAudit.t.sol regression suite

Vault-strategy separation: ShieldedPool is the sole ETH custodian. LendingPool is purely accounting. This limits the blast radius of any LendingPool bug to the loan accounting state — no direct ETH drain path.


Frontend: Next.js + wagmi + viem

Why Next.js:

  • API routes for server-side zkVerify SDK calls (requires DEPLOYER_PRIVATE_KEY — never exposed to browser)
  • snarkjs WASM loads cleanly with next/dynamic and ssr:false
  • Static export possible for IPFS deployment

Why wagmi + viem:

  • Type-safe contract interactions (parseAbi, readContract, writeContractAsync)
  • usePublicClient for RPC access (getLogs, getBlockNumber, waitForTransactionReceipt)
  • Wallet connection + chain switching built-in

Why browser-side proof generation: The alternative (server-side) would require sending secret + nullifier to a server. If that server is compromised, all user funds can be stolen. WASM keeps private inputs on the user's device. The ~25s proving time is the cost of this security guarantee.


Summary

Decision Choice Key Reason
Chain Base Sepolia EVM-native, low gas for epoch flush + dummy insertion
Proof verification zkVerify Volta 91% cheaper; modular; single-leaf aggregation pattern
Circuit language Circom 2.1.6 Most mature EVM ZK toolchain; WASM compilation; circomlib
Proof system Groth16 Smallest proof (192 bytes); fastest verification; snarkjs
Circuit design Ring proofs K=16 User-base-independent anonymity (300+ set at launch)
Temporal privacy Epoch batching 50 blocks Breaks timing correlation; prevrandao shuffle
Note storage AES-256-GCM + HKDF Encrypted, wallet-bound, cross-device recoverable
Contracts Solidity + Foundry EVM standard; fastest test runner; vault-strategy separation
Frontend Next.js + wagmi + viem SSR; API routes for server secrets; type-safe contracts
Proof generation Browser WASM Private inputs never leave user's device