Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/collinsville22/Sable/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Garaga is a high-performance ZK proof verification library for StarkNet that enables on-chain verification of Groth16 BN254 proofs. Sable uses Garaga to verify zero-knowledge proofs for all shielded pool withdrawals, ensuring full privacy without trusted relayers.
Garaga powers all Sable shielded pools, verifying Groth16 proofs on-chain for every private withdrawal.

What is Garaga?

Garaga is a Cairo library for verifying cryptographic proofs on StarkNet:
  • Groth16 BN254 verification: Industry-standard ZK proof system
  • On-chain verification: No trusted parties required
  • Optimized for Cairo: Native StarkNet implementation
  • Gas-efficient: Compressed proof verification

Key Features

  • Groth16 Support: Verify proofs generated by snarkjs, circom, or other Groth16 tools
  • BN254 Curve: Compatible with Ethereum’s zkSNARK infrastructure
  • Poseidon Hashing: StarkNet-native hash function for efficient in-circuit operations
  • Proof Compression: Minimize on-chain verification costs

How Sable Integrates with Garaga

Sable uses Garaga to verify withdrawal proofs in all shielded pool contracts, enabling zero-knowledge privacy for deposits and withdrawals.

Shielded Pool Architecture

┌──────────────────────────────────────────────────┐
│                 SHIELDED POOL CONTRACT               │
│                                                      │
│  Deposit:                                            │
│    User ──► Submit commitment (Poseidon hash)        │
│    Pool ──► Insert into Merkle tree                  │
│                                                      │
│  Withdrawal:                                         │
│    User ──► Submit Groth16 proof + public inputs    │
│    Pool ──► Call Garaga verifier contract          │
│    Garaga ──► Verify proof on-chain                  │
│    Pool ──► If valid: release funds, mark nullifier │
│           If invalid: revert transaction           │
└──────────────────────────────────────────────────┘

Groth16 BN254 Proof System

Garaga verifies Groth16 proofs, one of the most efficient zkSNARK schemes.

Proof Components

A Groth16 proof consists of 3 elliptic curve points:
proof = {
  pi_a: [x1, y1],           // Point A on BN254 curve
  pi_b: [[x2, y2], [x3, y3]], // Point B (G2 element)
  pi_c: [x4, y4],           // Point C on BN254 curve
}

Public Inputs (V4 Circuit)

Sable’s V4 shielded pools use 7 public inputs:
publicSignals = [
  root,          // Merkle root (proves commitment exists in tree)
  nullifierHash, // Hash of nullifier (prevents double-spend)
  recipient,     // Withdrawal address (felt252)
  relayer,       // Relayer address (felt252)
  fee,           // Relayer fee in sats (string)
  batchStart,    // First leaf index in batch
  batchSize,     // Number of deposits in batch (3)
]

Contract Addresses

Garaga Groth16 Verifier V4 (Current)

The active verifier contract for all V4 shielded pools.
0x03329c4d5c2e37dfd20d46c3c20be9230b2152c71947ead441c342d989d52ffa
View on Voyager

Garaga Groth16 Verifier V3 (Legacy)

Previous version for V3 shielded pools (6 public inputs).
0x041011d7912b6759f2fe3cc66a1f746b81ec012c2774adabc77eecc299e0878d
View on Voyager

Shielded Pool Integration

Sable’s shielded pools verify withdrawal proofs via Garaga.

Deposit Flow (No Proof Required)

// Simplified Cairo pseudocode

#[external(v0)]
fn deposit(commitment: felt252) {
    // No proof verification needed for deposits
    // Just insert commitment into pending batch
    
    let batch_id = self.pending_batch.read();
    self.pending_commitments.write(batch_id, commitment);
    
    // If batch full (3 deposits), auto-deploy to Merkle tree
    if pending_count == 3 {
        process_batch();
    }
}

Withdrawal Flow (Proof Verification)

// Simplified Cairo pseudocode

#[external(v0)]
fn withdraw(
    proof: Groth16Proof,
    root: felt252,
    nullifier_hash: felt252,
    recipient: felt252,
    relayer: felt252,
    fee: felt252,
    batch_start: felt252,
    batch_size: felt252,
) {
    // 1. Verify Merkle root is valid
    assert(is_known_root(root), "Invalid root");
    
    // 2. Verify nullifier hasn't been spent
    assert(!self.nullifiers.read(nullifier_hash), "Already spent");
    
    // 3. Verify Groth16 proof via Garaga
    let public_inputs = array![
        root,
        nullifier_hash,
        recipient,
        relayer,
        fee,
        batch_start,
        batch_size,
    ];
    
    let is_valid = garaga_verifier.verify_groth16_proof(
        proof,
        public_inputs,
    );
    
    assert(is_valid, "Invalid proof");
    
    // 4. Mark nullifier as spent (prevent double-spend)
    self.nullifiers.write(nullifier_hash, true);
    
    // 5. Transfer funds to recipient (minus relayer fee)
    let amount = self.denomination.read();
    wbtc.transfer(recipient, amount - fee);
    wbtc.transfer(relayer, fee);
}

Poseidon Hashing

Garaga includes support for Poseidon, a ZK-friendly hash function optimized for StarkNet.

Why Poseidon?

  • Efficient in circuits: Requires fewer constraints than SHA-256 or Keccak
  • Native to StarkNet: StarkNet VM has Poseidon built-in
  • Fast verification: Significantly reduces proof generation time

Poseidon in Sable

Sable uses Poseidon for two critical operations:
  1. Commitment Generation:
    commitment = Poseidon(nullifier, secret)
    
  2. Nullifier Hash:
    nullifierHash = Poseidon(nullifier)
    

Example: Generating a Commitment

// From: ~/workspace/source/src/lib/privacy/note.ts

import { buildPoseidon } from "circomlibjs";

const poseidon = await buildPoseidon();

const nullifier = "0x1234...";  // 31-byte random value
const secret = "0x5678...";     // 31-byte random value

// Compute commitment
const commitment = poseidon([BigInt(nullifier), BigInt(secret)]);
const commitmentHex = "0x" + poseidon.F.toObject(commitment).toString(16);

// Compute nullifier hash
const nullifierHash = poseidon([BigInt(nullifier)]);
const nullifierHashHex = "0x" + poseidon.F.toObject(nullifierHash).toString(16);

console.log({
  commitment: commitmentHex,
  nullifierHash: nullifierHashHex,
});

Proof Generation (Client-Side)

Users generate Groth16 proofs in the browser using snarkjs.

Proof Generation Flow

// From: ~/workspace/source/src/lib/privacy/prover.ts

import { generateWithdrawalProof } from "@/lib/privacy/prover";

const proof = await generateWithdrawalProof(
  note,         // User's shielded note
  merkleProof,  // Merkle path to commitment
  recipient,    // Withdrawal address
  relayer,      // Relayer address
  fee,          // Relayer fee
  batchStart,   // First leaf in batch
  batchSize,    // Batch size (3)
);

console.log(proof);
// {
//   proof: {
//     pi_a: ["0x123...", "0x456..."],
//     pi_b: [["0x789...", "0xabc..."], ["0xdef...", "0x012..."]],
//     pi_c: ["0x345...", "0x678..."],
//   },
//   publicSignals: [
//     "0x...root",
//     "0x...nullifierHash",
//     "0x...recipient",
//     "0x...relayer",
//     "12345",  // fee in sats
//     "0",      // batchStart
//     "3",      // batchSize
//   ],
// }

Circuit Files

Sable ships with precompiled circuit WASM and proving keys:
public/circuits-groth16-v4/
  ├── withdraw_v4.wasm        # Circuit WASM (runs in browser)
  └── circuit_final.zkey      # Proving key (generated via ceremony)
Proof generation takes ~2-5 seconds in the browser on modern hardware.

Which Sable Features Use Garaga?

Sentinel Lending Pool

Private WBTC Deposits4 denominations: 0.0002, 0.0004, 0.0006, 0.0008 BTCVerified by Garaga V4 verifier.

Delta Neutral Yield Pool

Private WBTC Deposits4 denominations: 0.00036, 0.00072, 0.00108, 0.00144 BTCVerified by Garaga V4 verifier.

Stablecoin Vault

Private USDC Deposits4 denominations: 10, 25, 50, 100 USDCVerified by Garaga V4 verifier.

Swap Pool

Private Token Swaps4 input denominations: 0.0002, 0.0004, 0.0006, 0.0008 BTCSwap WBTC → ETH/USDC/STRK privately.Verified by Garaga V4 verifier.

Circuit Versions

Sable has evolved through multiple ZK circuit versions.
VersionProof SystemPublic InputsVerifierStatus
V1UltraHonk (Noir)Variable@aztec/bb.jsLegacy
V2Groth16 BN254Variablesnarkjs + GaragaLegacy
V3Groth16 BN2546Garaga 0x0410...Legacy
V4Groth16 BN2547Garaga 0x0332...Current

V4 Improvements

  • Batch info: Added batchStart and batchSize as public inputs
  • Auto-deployment: Batches of 3 deposits auto-deploy to Merkle tree
  • Better UX: No manual batch deployment step required
  • Gas-efficient: Optimized proof verification

Security Properties

Zero-Knowledge Privacy

Garaga verification ensures:
  1. Commitment Validity: Proof proves the commitment exists in the Merkle tree
  2. Nullifier Uniqueness: Each nullifier can only be spent once
  3. No Linking: On-chain observer cannot link deposit to withdrawal
  4. No Trusted Parties: Verification is purely cryptographic (no relayer trust)

Attack Resistance

  • Double-spend prevention: Nullifier hashes stored on-chain
  • Invalid proof rejection: Garaga verifier rejects malformed proofs
  • Root validation: Only proofs against known Merkle roots accepted
  • Commitment privacy: Poseidon preimage resistance protects secret/nullifier

Frontend Integration

Sable’s privacy page generates and verifies proofs entirely in the browser.
// From: ~/workspace/source/src/hooks/use-shielded-vault.ts

import { generateWithdrawalProof, fetchRelayerFee } from "@/lib/privacy/prover";
import { getMerkleProof } from "@/lib/privacy/merkle";

async function withdrawFromPool(note: ShieldedNote, recipient: string) {
  // 1. Fetch current Merkle root and tree data
  const poolStatus = await fetch(`/api/relayer/pool-status?pool=${poolAddress}`);
  const { root, tree } = await poolStatus.json();
  
  // 2. Generate Merkle proof for the note's commitment
  const merkleProof = getMerkleProof(tree, note.leafIndex);
  
  // 3. Fetch relayer fee
  const { feeSats } = await fetchRelayerFee(poolAddress);
  
  // 4. Generate Groth16 proof in browser
  const proof = await generateWithdrawalProof(
    note,
    merkleProof,
    recipient,
    relayerAddress,
    feeSats.toString(),
    note.batchStart,
    note.batchSize,
  );
  
  // 5. Submit to relayer for on-chain verification
  await fetch(`/api/relayer/withdraw`, {
    method: "POST",
    body: JSON.stringify({ proof, poolAddress }),
  });
  
  // Garaga verifies proof on-chain → funds released if valid
}

External Resources

Garaga Documentation

Official Garaga library documentation

Garaga GitHub

Garaga source code and examples

Groth16 Paper

Original Groth16 zkSNARK paper

Poseidon Hash

Poseidon hash function specification

Integration Source Code:
  • Proof generation: ~/workspace/source/src/lib/privacy/prover.ts
  • Poseidon hashing: ~/workspace/source/src/lib/privacy/note.ts
  • Shielded pool contract: ~/workspace/source/contracts/src/shielded_pool_v4.cairo