{
"adversaries": {
"promise": {
"protects": "recipient",
"text": "Hides which registered recipient a stealth payment is for. The payer knows; sender, asset, amount and where the funds go next are public."
},
"adversaries": [
{
"id": "publicObserver",
"label": "Public observer",
"description": "Everyone with a block explorer and some basic tools. Sees every public onchain event, but does no correlation beyond following links.",
"examples": "A curious counterparty, an employer, a journalist."
},
{
"id": "chainAnalyst",
"label": "Chain analyst",
"description": "Scrapes all public data and correlates it: timing, amounts, gas and wallet fingerprints, address clusters.",
"examples": "Chain analytics firms, ZachXBT, data brokers."
},
{
"id": "networkObserver",
"label": "Network observer",
"description": "Sits between the user and the services and sees traffic only: ISPs, VPNs, Tor exits, public RPCs and relayers. Learns IP addresses, timing, fingerprints and ciphertext. Services the operator runs count as the insider.",
"examples": "Infura or Alchemy, a Tornado relayer, a wallet vendor selling telemetry, Google captcha or analytics in the dapp frontend."
},
{
"id": "privilegedInsider",
"label": "Privileged insider",
"description": "Holds an operator role, custodies funds, receives keys or plaintext by design, or delivers the client: upgrade admin, custodian, view key holder, enclave operator, association set provider, hosted prover, registry, frontend host.",
"examples": "A compliance backdoor key, a DAO with an upgrade key, a KMS committee, an ASP operator, a custodial bridge."
},
{
"id": "futureAdversary",
"label": "Future adversary",
"description": "Harvest now, decrypt later. Holds every byte ever written onchain plus any retained logs, and breaks all public-key cryptography, by quantum computer or new mathematics: elliptic curves, pairings, RSA, lattices and isogenies. Hashes, Poseidon included, and symmetric ciphers survive.",
"examples": "An intelligence agency archiving chain data today, then anyone once a break is public."
}
],
"fields": [
{
"id": "sender",
"label": "Sender",
"subject": "Sender",
"promiseLabel": "Sender privacy",
"description": "The Ethereum address that funds entered from, or that initiated a transfer."
},
{
"id": "recipient",
"label": "Recipient",
"subject": "Recipient",
"promiseLabel": "Recipient privacy",
"description": "Who receives the funds: the address they exit to, the receiver of a transfer, or whoever controls a one-time receiving address."
},
{
"id": "amount",
"label": "Amount",
"subject": "Amounts",
"promiseLabel": "Amount privacy",
"description": "The value moved."
},
{
"id": "asset",
"label": "Asset",
"subject": "Asset",
"promiseLabel": "Asset privacy",
"description": "Which token is moved."
},
{
"id": "linkage",
"label": "Link",
"subject": "Link",
"promiseLabel": "Link privacy",
"description": "Whether the entry and exit of the same funds, or sender and recipient of the same transfer, can be tied together."
}
],
"cells": {
"publicObserver": {
"sentiment": "good",
"exposureShort": "Announcements mark each stealth payment but not the recipient whose keys were used.",
"exposureContinued": "The registry publishes the candidate recipients and their key history.",
"advice": "Withdraw to a fresh address with no ENS name or prior activity, through the token relayer so your known wallet never funds the stealth address for gas. Keep funds from different stealth addresses apart.",
"sources": [
{
"contract": "Umbra",
"title": "Announcement, tokenPayments and TokenWithdrawal expose the fund path"
},
{
"contract": "StealthKeyRegistry",
"title": "StealthKeyChanged publishes recipients and key changes"
},
{
"title": "Recipient matching decrypts locally and checks the stealth address",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/umbra-js/src/classes/Umbra.ts#L704-L720"
}
],
"id": "publicObserver",
"value": "Recipient private",
"alsoExposed": []
},
"chainAnalyst": {
"sentiment": "warning",
"exposureShort": "Protocol use is public once a payment is announced, which narrows the anonymity set to registered recipients.",
"exposureContinued": "Withdrawals to registered addresses, round trips back to the sender and a shared collecting address reveal or cluster recipients; membership alone does not identify them. The client withdraws one stealth address at a time and never merges them.",
"advice": "Keep withdrawal destinations separate across payments and chains, and check that timing, amounts or recurring counterparties do not reconnect them to an identified account.",
"sources": [
{
"title": "Sections 6–7: reuse heuristics, false positives and historical evaluation",
"url": "https://arxiv.org/html/2308.01703v2#S6"
},
{
"title": "Table 2: denominator is withdrawn payments; section 7.4: no fee matches",
"url": "https://arxiv.org/html/2308.01703v2#S7"
},
{
"title": "Client warns on linked destinations, but allows proceeding",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/components/AccountReceiveTable.vue#L671-L715"
}
],
"id": "chainAnalyst",
"value": "Recipient at risk",
"alsoExposed": []
},
"networkObserver": {
"sentiment": "warning",
"exposureShort": "On the payer's side the wallet RPC resolves the recipient, reads their registry entry and broadcasts the stealth payment seconds apart, so that provider can pair the payment with the recipient.",
"exposureContinued": "On your side the wallet RPC receives the matched stealth-address balance batch, while a build-configured mainnet RPC looks up your connected wallet and the senders of matched payments, and withdrawal destinations are checked against ENS, POAP and Gitcoin APIs.",
"advice": "Run an inspected local build with every RPC pointed at your own node and the external name and safety lookups disabled. Send broadcasts and relay requests over Tor.",
"sources": [
{
"title": "Payer flow: registry lookup and send through the wallet provider",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/pages/AccountSend.vue#L955-L1059"
},
{
"title": "The wallet provider wraps the connected wallet",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/store/wallet.ts#L262-L267"
},
{
"title": "Mainnet and Polygon RPCs are configured separately from the wallet",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/utils/constants.ts#L1-L17"
},
{
"title": "Connected-wallet name lookup uses MAINNET_PROVIDER",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/store/wallet.ts#L336-L358"
},
{
"title": "Matched senders go to mainnet name lookup; stealth balances to wallet RPC",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/components/AccountReceiveTable.vue#L584-L626"
},
{
"title": "Withdrawal checks send destination to ENS, POAP and Gitcoin lookups",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/utils/address.ts#L255-L319"
},
{
"title": "Signed relay request carries stealthAddr and acceptor",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/components/AccountReceiveTable.vue#L758-L768"
}
],
"id": "networkObserver",
"value": "Recipient at risk",
"alsoExposed": []
},
"privilegedInsider": {
"sentiment": "good",
"exposureShort": "The contracts are immutable and give no administrator a viewing key or a way to replace registered keys without the registrant.",
"exposureContinued": "The client matches announcements locally, so no indexer or relayer has a protocol-wide view.",
"advice": "Run an inspected local build of the client instead of the hosted frontend.",
"sources": [
{
"contract": "Umbra",
"title": "Owner sets toll, tollCollector and tollReceiver; withdrawals require authorization"
},
{
"contract": "StealthKeyRegistry",
"title": "Direct registration or registrant-authorized signature, no admin override"
},
{
"title": "Viewing-key decryption is local to the client",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/umbra-js/src/classes/Umbra.ts#L704-L720"
},
{
"title": "Relay API receives signed withdrawal data, not a viewing key",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/frontend/src/utils/umbra-api.ts#L96-L111"
}
],
"id": "privilegedInsider",
"value": "Recipient private",
"alsoExposed": []
},
"futureAdversary": {
"sentiment": "bad",
"exposureShort": "Each announcement stores the ephemeral public key and the encrypted scalar.",
"exposureContinued": "Whoever breaks secp256k1 decrypts the scalar against every registered viewing key and checks which spending key yields the stealth address, identifying the recipient of every past payment.",
"sources": [
{
"contract": "Umbra",
"title": "Announcement archives pkx and ciphertext"
},
{
"contract": "StealthKeyRegistry",
"title": "Historical public viewing and spending keys"
},
{
"title": "ECDH-derived hash encrypts the scalar by XOR",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/umbra-js/src/classes/KeyPair.ts#L121-L157"
},
{
"title": "Recovered scalar is checked against the receiving address",
"url": "https://github.com/ScopeLift/umbra-protocol/blob/a81df24e76a0d6ab1ec79c6353e28c527b1b1a80/umbra-js/src/classes/Umbra.ts#L704-L720"
}
],
"id": "futureAdversary",
"value": "Recipient exposed",
"alsoExposed": []
}
}
},
"anonymitySet": {
"type": "key-registrations",
"registry": "eth:0x31fe56609C65Cd0C510E7125f051D440424D38f3",
"sinceTimestamp": 1629943007,
"event": "0xe879dede910dd3b22239a11044df83c95adcd4b54003516f42866cc1fe4f0a19",
"extractor": "umbraStealthKeyChanged"
},
"attributes": [
{
"id": "anyAmount",
"label": "Any amount",
"description": "Deposits and withdrawals can have any size."
},
{
"id": "stealthAddresses",
"label": "Stealth addresses",
"description": "Uses fresh, one-time recipient addresses to obscure who controls the receiving address."
}
],
"category": {
"id": "stealthAddress",
"label": "Stealth address",
"description": "Payments land on fresh one-time addresses. Hides which recipient a payment is for."
},
"exitWindow": {
"value": "Infinite",
"sentiment": "good",
"orderHint": 9007199254740991,
"description": "The core contracts are immutable. The owner can change the toll for future payments, but cannot stop recipients from accessing payments that have already been sent.",
"walkawayTest": {
"passed": true
}
},
"reproducibility": {
"value": "Reproducible",
"sentiment": "good",
"description": "The immutable core contracts, cryptographic library, and frontend are published and can be built and run locally."
},
"riskSummary": "## Funds can be lost if\n1. an unsupported ERC-20, such as a fee-on-transfer or rebasing token, causes the Umbra contract's internal accounting to diverge from its token balance.\n \n## Privacy can be lost if\n1. a recipient sends funds from a stealth address to a publicly linked address or consolidates payments in a recognizable way.\n2. public timing, amount, token, or ENS registration patterns let an observer narrow down the possible recipient.\n \n## New payments can be stopped if\n1. the Umbra owner sets an arbitrarily high toll for contract-routed payments.",
"tokens": [
{
"token": {
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"iconUrl": "https://assets.coingecko.com/coins/images/9956/large/Badge_Dai.png?1696509996",
"symbol": "DAI",
"decimals": 18,
"priceId": "dai",
"sinceTimestamp": 1619813937
},
"buckets": [
{
"id": "umbra-DAI",
"type": "pool",
"label": "DAI",
"address": "eth:0xFb2dc580Eed955B528407b4d36FfaFe3da685401",
"sinceTimestamp": 1619813937,
"deposit": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
},
"withdrawal": {
"event": "0x30eb3583ad09933b693a45452ab07512244cdbc5868701aa004c27b7b267c249",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F"
}
}
}
]
},
{
"token": {
"address": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
"iconUrl": "https://assets.coingecko.com/coins/images/279/large/ethereum.png?1595348880",
"symbol": "ETH",
"decimals": 18,
"priceId": "ethereum",
"sinceTimestamp": 1619813937
},
"buckets": [
{
"id": "umbra-ETH",
"type": "pool",
"label": "ETH",
"address": "eth:0xFb2dc580Eed955B528407b4d36FfaFe3da685401",
"sinceTimestamp": 1619813937,
"deposit": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
}
},
"withdrawal": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"
}
}
}
]
},
{
"token": {
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"iconUrl": "https://assets.coingecko.com/coins/images/6319/large/usdc.png?1696506694",
"symbol": "USDC",
"decimals": 6,
"priceId": "usd-coin",
"sinceTimestamp": 1619813937
},
"buckets": [
{
"id": "umbra-USDC",
"type": "pool",
"label": "USDC",
"address": "eth:0xFb2dc580Eed955B528407b4d36FfaFe3da685401",
"sinceTimestamp": 1619813937,
"deposit": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
},
"withdrawal": {
"event": "0x30eb3583ad09933b693a45452ab07512244cdbc5868701aa004c27b7b267c249",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
}
}
}
]
},
{
"token": {
"address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
"iconUrl": "https://assets.coingecko.com/coins/images/325/large/Tether.png?1696501661",
"symbol": "USDT",
"decimals": 6,
"priceId": "tether",
"sinceTimestamp": 1619813937
},
"buckets": [
{
"id": "umbra-USDT",
"type": "pool",
"label": "USDT",
"address": "eth:0xFb2dc580Eed955B528407b4d36FfaFe3da685401",
"sinceTimestamp": 1619813937,
"deposit": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
}
},
"withdrawal": {
"event": "0x30eb3583ad09933b693a45452ab07512244cdbc5868701aa004c27b7b267c249",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7"
}
}
}
]
},
{
"token": {
"address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
"iconUrl": "https://assets.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857",
"symbol": "WBTC",
"decimals": 8,
"priceId": "wrapped-bitcoin",
"sinceTimestamp": 1619813937
},
"buckets": [
{
"id": "umbra-WBTC",
"type": "pool",
"label": "WBTC",
"address": "eth:0xFb2dc580Eed955B528407b4d36FfaFe3da685401",
"sinceTimestamp": 1619813937,
"deposit": {
"event": "0x29877766fa2bfe3b90008d6d92f965eca91cbc5757ed775740e460799fb92219",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
}
},
"withdrawal": {
"event": "0x30eb3583ad09933b693a45452ab07512244cdbc5868701aa004c27b7b267c249",
"extractor": "umbraAmount",
"params": {
"tokenAddress": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
}
}
}
]
}
],
"trackedOn": [
"ethereum"
],
"upgradesAndGovernance": {
"content": "Umbra has no governance process or contract upgrade mechanism. The Umbra and StealthKeyRegistry contracts are immutable.\n\nThe Umbra contract has a permissioned owner that can immediately set the ETH toll charged on every contract-routed payment and change the addresses that collect and receive those tolls. Because the toll has no upper bound, the owner can effectively stop new payments through the Umbra contract at any time. The owner cannot change the payment or withdrawal logic and cannot prevent recipients from accessing payments already sent to them, so the exit window is infinite and the protocol passes the walkaway test."
}
}