Security

Threat model

What shh protects, what it does not, the trust assumptions it makes, and how each concrete threat — double spend, forged membership, inflation, front-running, bridge spoofing — is mitigated.

Read this alongside The cryptographic core. It states the assets shh defends, the assumptions it rests on, and the mitigation for each threat — most of which are tested.

Assets & goals

  • Unlinkability between a deposit and its withdrawal (Privacy Pool), and between note owners and transfers (Shielded Pool).
  • Fund integrity — no inflation, no theft, no double spend.
  • Compliance optionality — the ASP can scope which deposits exit privately, without learning more than the public deposit set.

Trust assumptions

  • Groth16 soundness and a non-backdoored trusted setup (dev = single contributor; prod = MPC).
  • Poseidon and BN254 are secure, and the on-chain hasher equals the circuit's Poseidon (enforced: MerkleTreeWithHistory uses the circomlibjs-generated hasher; the SDK uses poseidon-lite, verified byte-equal).
  • The OP Stack settlement layer (Base) and its bridge/portal behave per spec.

Threats & mitigations

ThreatMitigation
Double spendPer-spend nullifierHash recorded on-chain; reuse reverts. Tested.
Forged membershipWithdrawals verify a Groth16 proof of Merkle inclusion against a known root; isKnownRoot accepts only current/recent roots (ring buffer).
Value inflation (Shielded)Circuit enforces Σ in + publicAmount = Σ out (mod p); outputs range-checked to 248 bits. Tested.
Withdrawal front-running / proof theftCircuit binds recipient, relayer, fee, refund (Pool) and extDataHash (Shielded); a stolen proof can't be re-aimed. Tested.
Non-compliant exitPool requires membership in an ASP-published association root; excluded deposits are non-withdrawable through the private path. Tested.
Bridge spoofing (deposit)finalizeShieldedDeposit accepts only the aliased L1 bridge address (OP deposit aliasing). Tested.
Bridge spoofing (withdraw)bridgeWithdraw spends via a proof bound to the bridge, then uses the canonical L2StandardBridge for the authenticated L2→L1 transfer. Tested.
Trusted-setup backdoorMPC ceremony for production (Phase 8); dev keys clearly marked non-production.
Reentrancy on payoutEffects-before-interactions: nullifier marked spent before transfers; payouts via checked call.
Relayer censorship / fee griefingRelayer untrusted for safety (proof binds recipient/fee); can only refuse service. Users can self-submit.
Anonymity-set erosionFixed denominations and shared trees concentrate the set; UX should discourage amount/timing fingerprints.
Weak randomness for secretsSDK uses Web Crypto (getRandomValues) reduced into the field.

Known limitations

  • Single-contributor dev trusted setup (see Trusted setup).
  • No audit yet; fuzz/invariant tests are still to be added.
  • Encrypted-note discovery (ECIES ciphertexts) is stubbed (0x) until the frontend lands.
  • Metadata privacy (IP, timing) is out of scope at the protocol layer; relayers + client behavior must address it.