Voidgun Protocol

Privacy-via-proxy using Railgun protocol integration

1. Protocol Overview

Voidgun enables privacy pool access through Railgun's battle-tested Groth16 circuits. Keys are derived deterministically from your wallet signature.

[W]
Wallet
->
[S]
Sign Message
->
[K]
Derive Keys
->
[P]
ZK Proofs
->
[R]
Railgun Pool
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

2. Key Derivation Flow

Your Railgun wallet keys are derived deterministically from a single wallet signature. No new seed phrase needed.

1
Sign
2
Hash
3
Entropy
4
Mnemonic
5
BIP32
6
Keys
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);

3. Note Structure

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        │
    │                                                     │
    └─────────────────────────────────────────────────────┘

4. Merkle Tree

Commitments are stored in a depth-16 Poseidon Merkle tree. The tree is synced from on-chain Shield and Transact events.

Root
H1
H2
H3
H4
H5
H6
C0
C1
C2
C3
C4
C5
C6
C7
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)

5. Shielded Transfer

A transfer consumes input notes and creates output notes, all verified by a Groth16 proof.

Input Note

value 10 ETH
nullifier Revealed on-chain
proof Merkle path to root

Output Note (Recipient)

value 7 ETH
npk Recipient's key
encrypted ChaCha20-Poly1305

Change Note (Sender)

value 3 ETH
npk Sender's key
encrypted ChaCha20-Poly1305

Circuit Variants

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)

6. Groth16 Proof Generation

Proofs are generated using ark-circom with Railgun's trusted setup artifacts from IPFS.

[1]
Witness
->
[2]
WASM
->
[3]
ZKEY
->
[+]
Proof
// 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)