# Threat model

> This page lays out the threat model for Privacy Pools v2: what is private and when, who is in control of what, and what happens in adversarial conditions.

This page lays out the threat model for Privacy Pools v2: what is private and when, who is in control of what, and what happens in adversarial conditions.

## Honest-but-curious chain observer

The default adversary is someone watching every transaction on Ethereum.

-   **Sees:** deposits (depositor address and amount, both public). Transact events (commitment count in/out, no values). Withdrawals (destination address and amount).
-   **Doesn't see:** which input commitment is being spent. Recipient of a transact. Linkage between deposit and any later spend.
-   **Anonymity set:** privacy depends on on-chain transact volume. A quiet pool provides less unlinkability than a busy one.

## Malicious ASP

The [ASP](/concepts/asp-attestation) service refuses to attest, attests selectively, or goes offline.

-   **Worst case (refuses to attest):** your deposit can't be spent normally, but the note's *current owner* can always [ragequit](/operations/ragequit) to recover, gated by `msg.sender == ownerAddress` (since notes are transferable, this is whoever holds the note now, not necessarily the original depositor).
-   **Sees:** deposit labels (`Poseidon(precommitment, depositSecret)`) and the on-chain pool events. The ASP cannot decrypt note payloads, derive nullifiers, or learn values from labels alone.
-   **Doesn't compromise:** the privacy of other users. The ASP never sees decrypted note contents and never holds your keys.
-   **Trust scope:** liveness only. Whether your specific deposit gets approved depends on the ASP's policy.

## Compromised relayer

A [relayer](/operations/relaying) is malicious or compromised.

-   **Sees:** the asset, the public fee and public-outflow leg, the proof shape (input/output counts), the encrypted note payloads, and request metadata such as the originating IP.
-   **Doesn't see:** the amount of a private in-pool transfer (a withdrawal's amount is public), the sender or recipient, or your keys.
-   **Can:** refuse to submit or censor your transaction. Mitigate this by using an alternative relayer if available or publicly exiting via [ragequit](/operations/ragequit).
-   **Cannot:** steal funds. The proof binds the destination, so the relayer can only submit the transaction as prepared or refuse.

## Compromised viewing key

An attacker has your `viewingPrivateKey`.

-   **Reveals:** every incoming note ever encrypted to you, past and future, until you migrate.
-   **Doesn't enable:** spending. Spending requires `privateNullifyingKey`, which is held separately.
-   **Mitigation:** migrate to a new wallet for future activity. Old notes remain visible to the attacker but stay spendable by you alone.

## Wallet compromise

Your wallet's signing key is leaked.

-   **Attacker can:** re-derive all four protocol keys from your wallet, which gives them full account access.
-   **Mitigation:** move funds via [withdraw](/operations/withdraw) to a fresh wallet as fast as possible.

## Selective disclosure receipt

You handed an auditor a payment [receipt](/operations/receipts-and-viewing-keys).

-   **They see:** exactly that one payment: the value, asset, recipient address, tx hash, and pool address.
-   **They don't see:** any other transaction in your wallet's history, any other note's content, or your viewing private key.
-   **They cannot:** forge other receipts, spend the note, or derive nullifiers.

## Trusted setup

Every user operation is authorised by a Groth16 proof, and Groth16 relies on a trusted setup: a phase-2 ceremony per circuit whose "toxic waste" must be destroyed by every contributor.

-   **If the setup is sound:** the contracts' checks are exactly as strong as the circuits, since a verifier accepts only proofs of true statements.
-   **If the phase-2 secret is known to anyone:** they can forge a proof for any statement, which defeats every on-chain check at once: mint notes from nothing, spend notes they do not own, and forge keystore authorisation. This is the single assumption that everything else in this document sits on top of.
-   **What to check before trusting a deployment:** that the twenty-seven verifier contracts (deposit, ragequit, and the `transact_NxM` family) were generated from a multi-party ceremony with a published transcript and a public random beacon, and that the deployed verifier bytecode matches those ceremony keys. A verifier produced by a single-party development setup is fine for a testnet and unacceptable for real value.
-   **Governance surface:** `UPGRADER_ROLE` can swap any verifier on the [PoolVault](/protocol/contracts/pool-vault) without an upgrade. Verifier replacement is therefore a governance action with the same weight as a contract upgrade.

## Root freshness windows

Three roots gate a spend, and they age differently.

-   **State root:** the vault keeps a circular buffer of the last `rootHistorySize` roots (16 on the deployed configuration), so a proof built a few insertions ago still lands.
-   **Keystore root:** a superseded root stays valid for `keystoreRootLiveness` seconds after replacement (1200, twenty minutes, on the deployed configuration). This is what lets in-flight proofs survive a registration landing ahead of them, and it is also a **revocation delay**: rotating your revocable key with `updateAuthPolicy` does not invalidate a proof built against the old root for up to twenty minutes. Rotation is a recovery step, not a panic button.
-   **ASP root:** exact equality with `latestASPRoot()`, no history. The moment a new root is published, every in-flight transact proof is invalid and must be rebuilt. A postman posting frequently can grief pending withdrawals, and a postman could time a publication against a specific one. Funds are never at risk, since [ragequit](/operations/ragequit) bypasses the ASP root entirely, but this is a liveness asymmetry to plan for.

## Aave, when you hold yield shares

A [yield-share](/concepts/yield-shares) note is backed by aTokens the wrapper holds on Aave v3.

-   **You are exposed to Aave:** a loss on the reserve is a loss on your position; the pool cannot insulate you from it.
-   **Unwrapping depends on Aave liquidity:** if the reserve is paused, frozen, or fully utilised, a withdrawal to the underlying reverts and the note is not spent. You can still withdraw the shares as shares through the plain relay, or exit to the aToken with `PPYieldToken.redeem`, which never calls Aave.
-   **The wrapper's owner:** can set the fee (capped at 20% of yield, never retroactive), claim accrued fees, pause new deposits, and rescue tokens other than the backing. It cannot reach principal. See [PPYieldToken](/protocol/contracts/pp-yield-token).
-   **Aave itself is an assumed-honest external protocol** with a standard, hook-free, monotonically accruing aToken; the factories only wrap aTokens of one trusted pool.

## Aggregator and bridge, when you swap

[Withdraw-and-swap](/operations/withdraw-and-swap) and [reshield](/operations/reshield) forward your withdrawn value to a DEX aggregator with calldata you committed in the proof.

-   **The relayer cannot change the route:** target, calldata, recipient, fee, and output floor are all in the proof's `context`. It can submit what you proved or nothing.
-   **You trust the aggregator to execute the committed calldata,** and the bridge, on a cross-chain route, to deliver on the destination chain. A same-chain swap is checked on-chain by the recipient's balance delta; a cross-chain one cannot be, and is checked by the aggregator's destination-side executor instead.
-   **The RelaySwaps owner** curates which routers and zaps may receive funds. A compromised owner could allowlist a malicious router for *future* quotes, but could not alter any routing already proven.

## Stealth announcements

A [stealth withdrawal](/operations/stealth-withdraw) is public about one thing: the announcement marks the withdrawal as a stealth payment.

-   **An observer sees** a withdrawal to a fresh address and an ERC-5564 announcement in the same transaction, and can tell the two belong together.
-   **They cannot** link the stealth address to the recipient's meta-address or to any other payment, and the on-chain binding prevents a relayer from announcing an address the withdrawal does not fund.
-   **A relayer sees** the recipient's meta-address material only if you resolve it client-side, which the SDK does; it receives the derived address and announcement, not your recipient's keys.
