The ZK Privacy Layer
How your position stays hidden — even from the protocol. Zero-knowledge proofs are the electric fence around the Writz house. They allow the protocol to verify facts about your position — that your loan is adequately collateralized, that you actually made the deposit you claim, that your repayment amount is correct — without ever learning the underlying numbers. This page explains how the ZK layer works, what it hides, and what the cryptographic guarantees are.Zero-Knowledge Proofs in Plain English
A zero-knowledge proof lets you prove something is true without revealing anything else. The classic analogy: imagine you want to prove to someone that you know the solution to a Sudoku puzzle, without revealing the solution. A ZK proof is a mathematical protocol that makes this possible — you can demonstrate you know the answer, and the verifier becomes convinced, without ever seeing the answer. In Writz, the “fact” being proved is: “I have a deposit commitment in this Merkle tree, my loan amount is X% or less of my collateral value, and I have the secret that unlocks this commitment.” The ZK proof convinces the Soroban contract that all of this is true — without the contract ever learning the deposit amount, the loan amount, or anything else about the position.What Is Hidden, What Is Visible
| Information | Visibility |
|---|---|
| That a commitment exists in the tree | Public (commitment hash stored on-chain) |
| The collateral amount | Private — never on-chain |
| The loan amount | Private — never on-chain |
| The health factor | Private — never on-chain |
| The user’s public key linked to a position | Private |
| That a liquidation occurred | Public (event emitted) |
| Who was liquidated or for how much | Private |
| Total TVL (aggregate) | Public |
| Total USDC outstanding (aggregate) | Public |
The Commitment Scheme
At the heart of the ZK layer is a commitment. A commitment is a hash of private data:amountis the BTC deposit amount in satoshis (private)secretis a random number only the user knows (private)nonceis a unique value preventing commitment reuse (private)
The Nullifier
When a commitment is “consumed” (used in a borrow, repay, or withdraw), a nullifier is published:secret.
The Three ZK Circuits
Writz uses three Circom circuits, each compiled to a Groth16 proof system over the BN254 curve.1. Deposit Circuit
What it proves: “I know (amount, secret, nonce) such thatcommitment = Poseidon(amount, secret, nonce) AND the transaction ID from SPV verification matches my claimed deposit.”
Public inputs:
- The commitment hash (stored on-chain)
- The
txidreturned by the SPV contract (binding the ZK proof to a specific Bitcoin transaction)
- Amount (satoshis)
- Secret
- Nonce
2. Borrow/Repay Circuit
What it proves for borrow: “I know the preimage of a commitment in the Merkle tree, the Oracle price P, and a loan amount L such that:L / (amount × P) ≤ 1/min_ratio (the loan is within the allowed LTV).”
What it proves for repay: “I know the preimage of a commitment in the Merkle tree, and the repay amount correctly reduces the outstanding debt.”
Public inputs:
- The Merkle root (current state of the commitment tree)
- The oracle price
- The minimum collateral ratio (150%)
- A nullifier for the old state
- A new commitment for the updated state (after borrow or repay)
- Amount, secret, nonce (commitment preimage)
- Current debt
- Delta (borrow or repay amount)
- Merkle path (proof of membership in the tree)
loan_amount × min_ratio_bp ≤ collateral_amount × price × 10000. This avoids division entirely.
Field negation for repay: The repay amount is encoded as a field element negation — p − delta_stroops where p is the BN254 field prime. The circuit recovers the repay amount correctly. This was verified on-chain.
3. Liquidation Circuit
What it proves: “I know the preimage of a commitment in the Merkle tree, and the position’s current health ratio is below 120% at the current oracle price.” Public inputs:- The Merkle root
- The oracle price
- The usdc_debt (the debt amount — extracted from the proof so the liquidator cannot inflate it)
- The liquidation threshold (120%)
- Amount, secret, nonce (commitment preimage)
- Current debt
- Merkle path
usdc_debt binding: The liquidation circuit’s usdc_debt public output is computed inside the circuit from the private debt_stroops field. The liquidator cannot supply an arbitrary debt amount — it is derived from the private commitment and constrained by the circuit. This prevents a malicious keeper from claiming a larger debt than actually exists.
Groth16 and Protocol X-Ray
All three circuits use Groth16 — the most widely deployed ZK proof system, used by Zcash, Tornado Cash, and Stellar’s own Private Payments reference implementation. A Groth16 proof is a small constant-size object (3 elliptic curve points, ~128 bytes on BN254) that can be verified in constant time — regardless of the size of the computation being proven. Verification uses BN254 pairing checks: elliptic curve operations that confirm the relationship between the proof and the verification key. These are computationally expensive but deterministic. Stellar’s Protocol X-Ray (Protocol 26) added native host functions for BN254 operations:bn254_g1_msm— multi-scalar multiplication on the G1 curvebn254_pairing_check— pairing check across multiple pairs
zk-verifier contract uses these host functions to run the Groth16 verification:
The Merkle Commitment Tree
All commitments are stored in a Poseidon Merkle tree with depth 20 — supporting up to 1,048,576 (2²⁰) positions. The tree root is stored on-chain. When a new commitment is inserted, the root is updated. Every ZK proof that references the tree must match the current root — ensuring ZK proofs are valid only against the current state of the protocol. Sparse tree: The tree uses a sparse representation. Only occupied leaves require storage. Empty subtrees are represented by a precomputed empty-subtree hash. On-chain initial root:0x2134e76ac74b4b8765b6e37992aa15f06ff... (Poseidon-2 empty tree root — verified on-chain on Soroban testnet)
The Trusted Setup Ceremony
Groth16 requires a one-time trusted setup ceremony — a multi-party computation event that generates the proving and verification keys for each circuit. The ceremony has two phases:- Powers of Tau (Phase 1): A universal setup shared across all circuits. Writz will use the Hermez ceremony artifact (the same trusted setup used by Stellar’s own Private Payments reference implementation).
- Phase 2 (circuit-specific): A separate ceremony for each of Writz’s three circuits, incorporating the circuit-specific parameters.
pot15 from snarkjs) are used for testing only — they are NOT suitable for production.
What Gets Stored On-Chain
The ZK layer is designed to minimize on-chain storage while preventing double-spending and enabling verifiable state.| Data | Storage type | Lifetime |
|---|---|---|
| Commitment (leaf hash) | Per-entry persistent | 180-day window (refreshable) |
| Nullifier (spent marker) | Per-entry persistent | 180-day window (refreshable) |
| Merkle root | Instance persistent | 180-day window (refreshable) |
| Verification keys | Instance persistent | Permanent |
refresh_* functions that any keeper can call to extend the TTL of critical entries — ensuring user positions never expire unexpectedly.
Next: The Stellar Side →