Universal Circom circuits for the Stellar ZK L2 settlement system.
circuits/
├── src/
│ ├── settlement.circom # Main settlement circuit (universal)
│ ├── transfer.circom # Single transfer sub-circuit
│ └── withdrawal.circom # Withdrawal authorization
├── lib/
│ ├── poseidon.circom # Poseidon hash wrappers
│ ├── merkle.circom # Sparse Merkle tree verification
│ ├── nullifier.circom # Nullifier computation
│ └── commitment.circom # UTXO commitment logic
├── scripts/
│ ├── compile.sh # Compile circuits
│ ├── setup.sh # Trusted setup (powers of tau)
│ └── test.sh # Run circuit tests
├── test/
│ └── settlement.test.js # Circuit unit tests
├── keys/ # Generated proving/verification keys
└── build/ # Compiled circuit artifacts
A single circuit handles all L2 operations (deposit, transfer, withdraw) via an operation_type selector:
operation_type:
0 = Transfer (L2 → L2)
1 = Deposit (L1 → L2, commitment only)
2 = Withdraw (L2 → L1, nullifier only)
This avoids multiple trusted setups and simplifies Mopro integration.
- Proof System: Groth16 on BN254 (arkworks compatible)
- Hash Function: Poseidon with t=3 (2-to-1 hash)
- Constraint Target: <100,000 for mobile proving (<5s on iPhone)
- Field: BN254 scalar field (Fr)
The circuit supports both:
- Individual proofs: Single transfer for P2P mesh transactions
- Batch proofs: Multiple transfers aggregated by relayer
When batch_size = 1, it's a mesh transaction.
When batch_size > 1, it's a relayer settlement.
UTXO Commitment = Poseidon(owner_pubkey, balance, asset_id, nonce)
State Root = MerkleRoot(all UTXO commitments)
Nullifier = Poseidon(owner_privkey, commitment)
[0] old_state_root - Previous state tree root
[1] new_state_root - New state tree root after batch
[2] old_nullifier_root - Previous nullifier accumulator
[3] new_nullifier_root - New nullifier accumulator
[4] deposit_hash - Poseidon hash of deposit commitments
[5+] withdrawal_nullifiers - Nullifiers for L1 withdrawals
# Install Circom 2.x
cargo install --git https://github.com/iden3/circom
# Install snarkjs
npm install -g snarkjs
# Install circomlib (in circuits directory)
npm install circomlibcd circuits
# Compile circuit
./scripts/compile.sh
# Trusted setup (uses existing powers of tau or generates new)
./scripts/setup.sh
# Run tests
./scripts/test.sh| Parameter | Value | Rationale |
|---|---|---|
MERKLE_DEPTH |
20 | Supports ~1M accounts |
MAX_BATCH_SIZE |
8 | Mobile-friendly constraint count |
POSEIDON_T |
3 | Standard 2-to-1 hash |
ASSET_ID_BITS |
32 | 4B+ asset types |
The compiled circuit integrates with Mopro for mobile proof generation:
// In mopro bindings
crate::set_circom_circuits! {
("settlement_final.zkey", witness::settlement_witness),
}See /workspace/LumenBroMobile/docs/MOPRO_BUILD_GUIDE.md for full pipeline.
- Nullifier Uniqueness: Derived from private key + commitment
- Balance Conservation: Enforced in-circuit (inputs = outputs)
- Merkle Membership: Verified for all spent UTXOs
- Trusted Setup: Use existing Powers of Tau ceremony for production