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:
MerkleTreeWithHistoryuses 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
| Threat | Mitigation |
|---|---|
| Double spend | Per-spend nullifierHash recorded on-chain; reuse reverts. Tested. |
| Forged membership | Withdrawals 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 theft | Circuit binds recipient, relayer, fee, refund (Pool) and extDataHash (Shielded); a stolen proof can't be re-aimed. Tested. |
| Non-compliant exit | Pool 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 backdoor | MPC ceremony for production (Phase 8); dev keys clearly marked non-production. |
| Reentrancy on payout | Effects-before-interactions: nullifier marked spent before transfers; payouts via checked call. |
| Relayer censorship / fee griefing | Relayer untrusted for safety (proof binds recipient/fee); can only refuse service. Users can self-submit. |
| Anonymity-set erosion | Fixed denominations and shared trees concentrate the set; UX should discourage amount/timing fingerprints. |
| Weak randomness for secrets | SDK 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.