DOME Docs

Shielded ETH Pool

Core on-chain model for native ETH shielded notes, Merkle commitments, and Groth16 spends on Base.

This page is adapted from the internal protocol spec (dome/docs/protocol/shielded-eth-base.md). It describes the cryptographic and on-chain model for Dome on Base.

Live testnet today: Base Sepolia uses the EtherPool proxy with a unified transact() entrypoint, NewCommitment events, and optional @dome/backend indexer + relayer. The RPC-only sync path below still applies if you scan events directly — the indexer is a convenience layer, not a trust requirement for reads.

Threat model

The protocol reduces public address linkability between deposits, private transfers, and withdrawals. It does not hide:

  • Network metadata (RPC provider, IP, timing)
  • The EVM account that pays gas on self-submitted withdraws
  • Value/timing correlation when the anonymity set is small

Users should withdraw to fresh addresses and fund gas in a privacy-conscious way. Relayers (optional on testnet) submit withdraws so recipients do not need prefunded gas keys linked to the deposit path.

Chain and asset

ChainBase (Sepolia for development, Base mainnet post-audit)
AssetNative ETH only (msg.value deposits, contract balance vault)
Amountsuint256 wei in notes and proofs (field-safe encoding in circuits)

State model

The EtherPool contract holds pooled ETH and on-chain state:

  • Sparse Poseidon Merkle tree (depth set at deploy — 26 levels on current testnet, ~67M leaves)
  • Incremental root updates via filledSubtrees
  • Nullifier set to prevent double-spends
  • Pausing / upgradeability via proxy (testnet deployments)

Clients can rebuild commitment and nullifier history by scanning contract events with eth_getLogs in bounded block ranges, caching progress locally. The Dome indexer performs the same scan server-side for wallets that opt in.

Note format

Private notes are never stored on-chain in plaintext.

note = {
  asset_id: bytes32,      // ETH asset identifier (field-encoded)
  amount_wei: u256,
  owner_spend_pubkey: bytes32,
  owner_view_pubkey: bytes32,
  rho: bytes32,
  rseed: bytes32,
}

Derived values (Poseidon on BN254):

commitment = H(asset_id, amount_wei, owner_spend_pubkey, rho, rseed)
nullifier  = H(owner_spend_secret, rho)
note_tag   = H(owner_view_pubkey, rho)

Contract API (current)

Production testnet pools expose a single transact(Proof, ExtData) function on EtherPool (and ERCPool for ERC-20 in future). Deposits and withdrawals are both transact calls with different proof public inputs.

Typical flow:

  1. Deposit — Client generates a Groth16 proof, sends transact with msg.value matching the note amount. Pool appends the output commitment and emits NewCommitment.
  2. Withdraw — Client proves spend of existing notes; pool checks nullifier unused, verifies proof, transfers ETH to recipient (via relayer or self-submit).

Public signals (withdraw)

Public signals follow the circuit layout (6 signals):

  1. Merkle root
  2. Nullifier
  3. Output commitment A (empty sentinel if none)
  4. Output commitment B (empty sentinel if none)
  5. Withdraw wei
  6. Relayer fee wei (0 when self-submitting)

See Deposit & Withdraw for the wallet/SDK flow end-to-end.

Events (canonical sync source)

Current EtherPool events (simplified):

event NewCommitment(bytes32 commitment, uint256 index, bytes encryptedOutput);
// Nullifier consumption is enforced in-contract; scan transact() logs + pool state

Historical RPC-only spec used CommitmentInserted / NullifierSpent naming — same idea: events + eth_getLogs are the portable sync source.

RPC-only recovery

  1. Configure a public Base RPC URL, pool contract address, and deployment block.
  2. Scan commitment (and spend) events from deployment block to latest, in chunks (e.g. 2,000–10,000 blocks per eth_getLogs request).
  3. Persist lastScannedBlock in local encrypted storage.
  4. Decrypt encryptedOutput payloads with the view key, filter spent nullifiers, rebuild the Merkle tree, compute shielded balance.

No Dome HTTP API is required for reads — see Backend & Indexer for the hosted shortcut.

Privacy requirements

  • Wallets should generate fresh withdrawal addresses by default.
  • No app analytics with addresses, notes, nullifiers, or proof inputs.
  • UI should communicate anonymity set size and timing risks.
  • Users may choose their own public RPC endpoint.

Development and security

  • Pre-production: dev verifying keys, unaudited contracts/circuits on testnet.
  • Production requires: audited contracts, audited circuits, trusted setup, production verifier on-chain, no dev proof bypasses.

For local deployment and Hardhat workflow, see the monorepo dev scripts (bash scripts/dev/up.sh) or Architecture.

On this page