Privacy-via-proxy using Railgun protocol integration
Voidgun enables privacy pool access through Railgun's battle-tested Groth16 circuits. Keys are derived deterministically from your wallet signature.
| Component | Purpose | Technology |
|---|---|---|
| Key Derivation | Derive Baby Jubjub keys from wallet signature | BIP39/BIP32, EdDSA |
| Note Encryption | Encrypt shielded balance commitments | ChaCha20-Poly1305, AES-GCM |
| Proof Generation | Generate validity proofs for transfers | Groth16, ark-circom |
| Merkle Sync | Track commitment tree from chain events | Poseidon hash, depth-16 |
Your Railgun wallet keys are derived deterministically from a single wallet signature. No new seed phrase needed.
Click "Next Step" to walk through the derivation flow...
// Key derivation paths (Baby Jubjub curve) const SPENDING_PATH = "m/44'/1984'/0'/0'"; // EdDSA signing const VIEWING_PATH = "m/420'/1984'/0'/0'"; // Note decryption // Derive from wallet signature let entropy = keccak256(signature)[0..16]; let mnemonic = bip39_from_entropy(entropy); let master = bip32_derive_master(mnemonic); let spending_key = master.derive(SPENDING_PATH); let viewing_key = master.derive(VIEWING_PATH);
Shielded balances are stored as encrypted Notes in a Merkle tree. Each note commits to a value without revealing it.
NOTE (Private Data)
┌─────────────────────────────────────────────────────┐
│ │
│ npk ────────┐ Note Public Key │
│ │ = Poseidon(nullifyingKey, │
│ │ leafIndex) │
│ │ │
│ token ──────┼──┐ ERC20 contract address │
│ │ │ │
│ value ──────┼──┼─┐ Amount (e.g. 10 ETH) │
│ │ │ │ │
│ random ─────┼──┼─┼─┐ Random blinding factor │
│ │ │ │ │ │
└───────────────┼──┼─┼─┼──────────────────────────────┘
│ │ │ │
v v v v
┌─────────────────┐
│ Poseidon │ Hash function
│ (npk, token, │
│ value, rand) │
└────────┬────────┘
│
v
┌─────────────────────────────────────────────────────┐
│ COMMITMENT (On-Chain) │
│ │
│ 32-byte hash stored in Merkle tree leaf │
│ Hides all note details from observers │
│ │
└─────────────────────────────────────────────────────┘
NULLIFIER (Revealed on Spend)
┌─────────────────────────────────────────────────────┐
│ │
│ = Poseidon(nullifyingKey, leafIndex) │
│ │
│ - Unique per note (prevents double-spend) │
│ - Cannot be linked back to commitment │
│ - Published on-chain when note is consumed │
│ │
└─────────────────────────────────────────────────────┘
Commitments are stored in a depth-16 Poseidon Merkle tree. The tree is synced from on-chain Shield and Transact events.
Click a path button to visualize the Merkle proof...
| Property | Value |
|---|---|
| Depth | 16 levels |
| Capacity | 65,536 commitments per tree (new tree created when full) |
| Hash | Poseidon (BN254 scalar field) |
| Proof Size | 16 sibling hashes (512 bytes) |
A transfer consumes input notes and creates output notes, all verified by a Groth16 proof.
Named by inputs x outputs count
| Variant | Inputs | Outputs | Use Case |
|---|---|---|---|
| 01x01 | 1 | 1 | Simple send (no change) |
| 02x02 | 2 | 2 | Standard transfer with change |
| 08x02 | 8 | 2 | Consolidation (merge notes) |
Proofs are generated using ark-circom with Railgun's trusted setup artifacts from IPFS.
// Proof generation with ark-circom let prover = RailgunProver::new(artifact_store); // Build witness for 2-input, 2-output transfer let witness = TransactWitness { merkle_root, bound_params_hash, nullifiers: [nf1, nf2], commitments: [cm_out, cm_change], // ... private inputs }; // Generate Groth16 proof (~2-5 seconds) let proof = prover.prove(&variant, &witness).await?; // Verify locally before submitting let valid = prover.verify_proof(&variant, &proof).await?;
| Artifact | Size | Purpose |
|---|---|---|
| WASM | ~5 MB | Witness calculation (circom) |
| ZKEY | ~50-200 MB | Proving key (Groth16) |
| VKEY | ~5 KB | Verification key (JSON) |