Docs
/architecture
/Overview

Overview

System design of Mirage

System Architecture

Mirage follows a hybrid architecture to balance developer experience (Python) with performance (Rust), plus optional chain-agnostic payment support via x402.

Unified Package Architecture

Mirage uses a unified Python package with embedded Rust backend:

mirage/
├── mirage/                    # Python SDK
│   ├── __init__.py
│   ├── client.py             # PrivacyClient
│   ├── types.py
│   ├── utils.py
│   └── x402/                 # x402 payment support (included)
│
├── src/                      # Rust cryptography (PyO3 bindings)
│   ├── crypto/               # Commitments, Poseidon, Merkle, encryption
│   ├── proof/                # Groth16 circuits
│   └── lib.rs                # PyO3 bindings
│
├── packages/
│   └── mirage-solana/        # On-chain Anchor program (separate)
│       └── programs/
│
├── Cargo.toml                # Rust configuration
├── pyproject.toml            # Python package configuration (maturin)
└── Makefile                  # Build commands

Installation: pip install mirage-solana (x402 support included by default)

Layer 1: Python SDK (mirage)

The user-facing API layer providing high-level primitives for privacy-preserving transactions.

PrivacyClient

Main entry point for all privacy operations:

Methods:

  • shield_assets() / shield_assets_async() - Convert public tokens to private commitments
  • private_transfer() / private_transfer_async() - Transfer privately between notes
  • unshield_assets() / unshield_assets_async() - Withdraw to public addresses
  • verify_proof() - Client-side proof verification
  • is_nullifier_spent() - Check if a nullifier has been used
  • get_merkle_root() - Query current on-chain Merkle root

Async vs Sync:

  • Async methods (*_async()): Submit to blockchain, returns transaction signature
  • Sync methods: Offline computation only, returns transaction data for later submission
  • Useful for air-gapped signing and testing scenarios

X402Client

Chain-agnostic payment client for micropayments:

Methods:

  • pay_for_access(resource_url, price_usd, merchant_address) - Execute x402 payment flow
  • Supports 15+ blockchain networks

Benefits:

  • Pay for privacy services using any supported blockchain
  • No need for native SOL for gas in some workflows
  • Cross-chain payment flexibility

Types Module

Data structures and type definitions:

  • ShieldRequest - Request to shield assets
  • TransferRequest - Request for private transfer
  • UnshieldRequest - Request to unshield assets
  • PrivateTransaction - Transaction result with status, signature, secrets
  • CommitmentData - Commitment manipulation (to_hex, from_hex)
  • TransactionStatus - Enum for transaction states
  • PaymentConfig / PaymentResponse - x402 payment types

Utilities

  • generate_secret() - Cryptographically secure random secret generation
  • commitment_to_hex() / hex_to_bytes() - Serialization helpers
  • validate_solana_address() - Address validation

Layer 2: Rust Core (src/)

The high-performance cryptographic engine, exposed to Python via PyO3 bindings.

Crypto Module

Pedersen Commitments

C = amount * G + blinding * H
  • Curve: BN254 G1 subgroup
  • Domain Separator: MIRAGE_PROTOCOL_PEDERSEN_H_V1
  • Properties: Perfectly hiding, computationally binding
  • Size: 32 bytes per commitment

Circuit-Safe Nullifier Derivation

spending_key = Poseidon(secret, "MIRAGE_SPENDING_KEY")
nullifier = Poseidon(spending_key, Hash(leaf_index || "MIRAGE_NULLIFIER"))
  • Two-step process prevents secret exposure in constraints
  • Security: Unlinkable to commitment without knowing secret
  • Size: 32 bytes per nullifier

Poseidon Hash Function

  • Parameters: t=3 (width), RF=8 (full rounds), RP=57 (partial rounds)
  • S-box: x^5 over BN254 scalar field
  • Efficiency: Optimized for zkSNARK circuits (~200 constraints per hash)
  • Usage: Merkle tree, nullifier derivation, commitment blinding

ECDH Note Encryption

  • Key Exchange: ECDH on BN254 G1 curve
  • Symmetric Cipher: ChaCha20-Poly1305 AEAD
  • Plaintext: 48 bytes (amount + blinding + asset_id)
  • Ciphertext: 64 bytes (48 data + 16 MAC) + 32 bytes ephemeral key = 96 bytes total
  • Domain Separator: MIRAGE_NOTE_ENCRYPTION_V1
  • Property: Forward secrecy via ephemeral keys

Merkle Tree

  • Depth: 20 levels (supports ~1,048,576 leaves)
  • Hash Function: Poseidon
  • Optimization: Filled subtrees for O(log n) insertions
  • Zero Hashes: Precomputed for empty nodes
  • Storage: On-chain state maintained by Solana program

Proof Module

Groth16 zkSNARKs

  • Circuit Name: TransferCircuit
  • Constraints: ~7,000 R1CS constraints
  • Proving Time: 2-5 seconds (client-side, depends on hardware)
  • Proof Size: 256 bytes (proof_a: 64, proof_b: 128, proof_c: 64)
  • Library: ark-groth16 from arkworks-rs ecosystem

Public Inputs (96 bytes):

  1. merkle_root (32 bytes) - Current Merkle tree root
  2. nullifier (32 bytes) - Nullifier to prevent double-spend
  3. new_commitment (32 bytes) - Output commitment

Private Inputs (Witness):

  • secret (32 bytes) - Master secret
  • input_amount, input_blinding - Input note data
  • output_blinding - Output note blinding factor
  • merkle_path (20 × 32 bytes) - Merkle proof siblings
  • leaf_index - Position in Merkle tree

Circuit Constraints Breakdown:

  • Spending key derivation: ~400 constraints
  • Input commitment verification: ~500 constraints
  • Merkle membership proof: ~4,000 constraints (20 Poseidon hashes)
  • Nullifier derivation: ~400 constraints
  • Output commitment: ~500 constraints
  • Range checks and validation: ~1,200 constraints

PyO3 Bindings

Rust functions exposed to Python via mirage._rust_core:

Exposed Functions:

  • generate_commitment(amount, secret) → bytes
  • generate_nullifier(spending_key, leaf_index) → bytes
  • generate_proof(witness_json) → bytes (proof)
  • verify_proof(proof, public_inputs) → bool
  • poseidon_hash(inputs) → bytes
  • encrypt_note(amount, blinding, recipient_pubkey) → EncryptedNote
  • decrypt_note(encrypted, secret_key)Option<Note>

Layer 3: Solana On-Chain Program (packages/mirage-solana)

Anchor-based smart contract handling verification and state management.

Program ID: 6p1tzefXSST8j72qcj5EU3pAcY5qSc3HnCfFqc2gWxjM

State Accounts

PrivacyPool Account (~1692 bytes)

pub struct PrivacyPool {
    pub authority: Pubkey,           // 32 bytes
    pub merkle_root: [u8; 32],       // 32 bytes
    pub leaf_count: u64,             // 8 bytes
    pub root_history: [MerkleRoot; 30], // 960 bytes (32 × 30)
    pub current_root_index: u8,      // 1 byte
    // ... additional fields
}
  • PDA Seeds: [b"privacy_pool"]
  • Purpose: Main state for the privacy protocol

NullifierMarker Account (~128 bytes each)

pub struct NullifierMarker {
    pub pool: Pubkey,          // 32 bytes
    pub nullifier: [u8; 32],   // 32 bytes
    pub spent_at: u64,         // 8 bytes (slot number)
}
  • PDA Seeds: [b"nullifier", pool.key(), &nullifier]
  • Purpose: Marks a nullifier as spent (double-spend prevention)

Instructions

1. Initialize

  • Creates the privacy pool
  • Sets authority and initial Merkle root

2. Shield / ShieldSOL

  • Deposits public tokens → creates private commitment
  • Adds commitment to Merkle tree

3. Transfer

  • Inputs: Proof, nullifier, new commitment, encrypted note
  • Verification: zkSNARK proof via groth16-solana
  • Actions: Create nullifier PDA, add commitment, update root history

4. Unshield / UnshieldSOL

  • Similar to transfer but outputs to public address
  • Withdraws tokens from pool vault to recipient

Compute Unit Analysis

OperationCU CostNotes
Shield~50,000Simple addition to Merkle tree
Transfer~250,000Includes Groth16 verification
Unshield~200,000Verification + token transfer
Groth16 Verification~200,000BN254 pairing checks

Layer 4: x402 Payment Integration

Optional chain-agnostic payment layer for paying for privacy services.

x402 Flow

1. Request Resource
   User → X402Client.pay_for_access()
   → HTTP GET resource_url
   → 402 Payment Required response

2. Payment Construction
   → Parse X-Accept-Payment header
   → Generate payment payload (amount, merchant, nonce)
   → Sign payload (EIP-712 for EVM, native for Solana)

3. Payment Verification
   → POST to facilitator /verify
   → Facilitator validates signature, balance, amount
   → Returns verification_id

4. Resource Access
   → Retry HTTP GET with X-PAYMENT header
   → Server validates payment
   → 200 OK with X-PAYMENT-RESPONSE
   → User receives access token

Supported Networks

EVM Networks:

  • Base, Base Sepolia
  • Polygon, Polygon Amoy
  • Avalanche, Avalanche Fuji
  • IoTeX, Peaq, Sei, XLayer

Solana:

  • Mainnet, Devnet

Performance Characteristics

Client-Side (Python + Rust)

OperationTimeRAMCPU
Generate Secret<1msMinimalMinimal
Create Commitment<10ms<10 MB1 core
Generate Proof2-5s2-4 GBMulti-core (beneficial)
Verify Proof (local)<100ms<100 MB1 core
Encrypt Note<5msMinimalMinimal
x402 Payment~500msMinimalMinimal

On-Chain (Solana)

MetricValue
Proof Verification~250k CU (~0.00025 SOL)
Block Time~400-600ms (Solana average)
Confirmation~2-3 seconds (finalized)

Security Model

Cryptographic Assumptions:

  1. Discrete Log: BN254 discrete log is hard
  2. Trusted Setup: At least one MPC participant is honest
  3. Solana Security: Blockchain provides integrity and availability
  4. Randomness: OsRng provides secure randomness

Guarantees:

  • Amount privacy (information-theoretic)
  • Sender/recipient unlinkability (computational)
  • Double-spend prevention (protocol-enforced)
  • Front-running protection (30-root buffer)

x402 Security:

  • EIP-712 signatures for EVM chains
  • Nonce uniqueness prevents replay attacks
  • Facilitator validation before settlement

See also: