VOSA: Virtual One-time Sub-Account — A Simplified Privacy Primitive for EVM
Ethereum Magicians
VOSA is a privacy pattern that trades fund-flow privacy for simplicity and compliance. Essentially: UTXO without Merkle trees — using O(1) spent-markers with epoch cleanup . We have a working implementation and are exploring whether this is worth formalizing as an EIP. Looking for feedback. What is VOSA? V irtual: Addresses exist only as mapping keys, not real EVM accounts O ne-time: Each address used exactly once (like UTXO notes) S ub- A ccount: Derived from master key using stealth addresses (ERC-5564) State is a single flat mapping: mapping(address => bytes32) balanceCommitmentHash; // bytes32(0) → never used // Poseidon(amt, blind, ts) → has balance (timestamp for replay protection) // 0xDEADDEAD...|block# → spent (prefix + spent block number, deletable after configurable window) State machine: UNUSED → ACTIVE → SPENT → (cleanup) → UNUSED No Merkle trees. No nullifier sets. Spent-tracking is O(1) and cleanable. How It Works Each VOSA holds a Poseidon hash commitment hiding the balance amount. Operations use standard ECDSA (MetaMask works directly) + Groth16 ZK proofs (on-chain verification): Deposit : Transfer ERC-20 in → ZK proof verifies commitment == Poseidon(amount, blinder, timestamp) → store at fresh VOSA Transfer : ECDSA signature + ZK proof that sum(inputs) == sum(outputs) → mark inputs SPENT, create outputs Withdraw : ECDSA signature + ZK proof → mark SPENT, transfer ERC-20 out Consolidate : ECDSA signatures + ZK proof → merge multiple VOSAs into one (simplifies account management, reduces future gas) Cleanup : SPENT markers encode block.number . After a configurable window (default ~1 month), anyone can call cleanup() to delete expired entries and save gas Why Poseidon over Pedersen? ~350 constraints vs ~3,400 — proof time drops from ~500ms to ~92ms, storage from 64 bytes to 32 bytes. Key Design Choice: SPENT_MARKER vs Nullifier | Aspect | Traditional UTXO (Tornado/Railgun) | VOSA | |--------|-----------------------------------|------| | Double-spend prevention | Nullifier set | SPENT_MARKER | | State lookup | O(log n) Merkle proof | O(1) mapping | | State growth | Unbounded (nullifiers forever) | Bounded (cleanup) | | Fund flow | Hidden | Visible (by design) | | 10-year storage (10B txs) | ~320 GB | ~2.7 GB | VOSA deliberately exposes fund flow in exchange for: ~10x fewer ZK circuit constraints O(1) state operations 97% storage savings with Epoch cleanup Simpler client implementation Privacy Model Hidden : Amounts, Balances, Real-world identity of VOSA holders (via stealth addresses) Visible : VOSA-to-VOSA transfer graph (input→output links), Depositor address, Withdrawal recipient This is NOT a bug — it’s designed for compliance-friendly privacy. Performance Groth16 + snarkjs WASM, Apple M2. Gas includes ~200K for on-chain Groth16 verification. | Operation | Proof Time | Gas (L2) | |—|—|—| | Deposit (0→1) | ~92ms | ~300K | | Transfer (1→2) | ~95ms | ~350K | | Transfer (2→2) | ~147ms | ~370K | | Withdraw (partial) | ~92ms | ~330K | | Consolidate (5→1) | ~210ms | ~475K | Use Cases Corporate balance privacy (hide amounts from competitors) Personal wallet privacy (hide holdings) Compliant private transactions (auditable fund flow) Full anonymity (use Tornado/Railgun instead) Questions for Discussion Is this pattern useful enough to formalize? Or do existing EIPs already cover this? Is SPENT_MARKER sound? Any security concerns with this approach vs nullifiers? Standalone or combined? Should VOSA be an Informational EIP (the pattern), with a separate ERC for “VOSA-20” (wrapped private ERC-20)? Epoch cleanup incentives — Gas savings sufficient motivation, or need additional incentives? Related Work ERC-5564 (Stealth Addresses) Tornado Cash (Merkle + Nullifier) Railgun (UTXO with full privacy) Aztec (Private L2) 1 post - 1 participant Read full topic