morphit/docs/adr/0026-transparent-chain-privacy-framework.md

276 lines
12 KiB
Markdown

# ADR-0026 — Transparent-chain privacy framework
**Status:** Accepted (Part 122 cp26)
**Date:** 2026-05-17
**Deciders:** project maintainer
**Supersedes:** none
**Superseded by:** none
**Related:** ADR-0023 (USDT multi-network), ADR-0024 (BCH
trade-only), ADR-0025 (LTC trade-only). All three established
the asset-registry pattern this ADR extends.
## Context
After cp24 shipped Litecoin, Ken raised a strategic question:
since BLURT, BTC, BCH, and LTC transactions are not private at
all, can we make them more private — and can that pattern apply
to future coins? Ken explicitly excluded two directions: (a)
wallet recommendations (even reputable wallets have been
compromised; the liability isn't worth the small UX win), and
(b) Lightning Network for BTC (Morphit will not support LN).
We can't make transparent chains private at the protocol level
— that's structural. But we can:
1. **Nudge better practices in the UX** (fresh addresses per
trade, amount randomization, reuse warnings)
2. **Surface opt-in privacy tech** that the chain or its
ecosystem supports (MWEB, CashFusion, CoinJoin, PayJoin)
3. **Educate users** with registry-driven per-asset privacy
guides
The cp23-DD-class lesson applies here too: whatever framework
we build must be registry-driven, so future asset additions
(Dash, DOGE, ZEC, ARRR, DCR, SOL, ETH, XRP, etc.) get privacy infrastructure automatically
rather than per-asset bolt-ons.
## Decision
Extend the canonical `AssetEntry` interface with a
`privacyFeatures` struct, then build four user-facing surfaces
that pull from it:
### 1. `AssetEntry.privacyFeatures` struct
Three fields:
- `freshAddressAdvice`: one of `'subaddress'` (XMR), `'hd-derived'`
(BTC/BCH/LTC/USDT), or `'account-reuse'` (BLURT). Drives the
shared i18n key explaining how to get a fresh receive address
on this chain.
- `optInPrivacyTech`: `null` for assets without opt-in privacy
technology (XMR has it built in; BLURT and USDT don't have
any), or an array of protocol-standard identifiers from a
fixed enum: `'mweb'`, `'cashfusion'`, `'coinjoin'`, `'payjoin'`,
`'privatesend'` (cp27 extension; see ADR-0027).
These are **protocol names, not wallet names** — naming a
protocol like CashFusion is an information item, not a wallet
endorsement.
- `privacyGuideKey`: lowercase i18n key prefix for the per-asset
privacy guide page. Pages live at `/[lang]/privacy/{key}` and
pull copy from `privacy.guides.{key}.*`.
Per-asset values:
| Asset | freshAddressAdvice | optInPrivacyTech | privacyGuideKey |
|-------|--------------------|------------------------|-----------------|
| XMR | subaddress | null | xmr |
| BTC | hd-derived | [coinjoin, payjoin] | btc |
| BLURT | account-reuse | null | blurt |
| USDT | hd-derived | null | usdt |
| BCH | hd-derived | [cashfusion] | bch |
| LTC | hd-derived | [mweb] | ltc |
| DASH | hd-derived | [privatesend] | dash |
> **Note (Part 122 cp27):** DASH row + `'privatesend'` enum value
> added in ADR-0027 (Dash trade-only addition) as a registry-driven
> extension of this framework. No other framework changes needed —
> DASH lit up automatically.
### 2. Generalized amount-jitter across transparent chains
cp3 shipped `jitterMoneroAmount` for XMR. cp26 generalizes to
`jitterUtxoAmount` (BTC/BCH/LTC, 8-decimal precision, 0-999 sat
jitter) and `jitterBlurtAmount` (3-decimal, 0-99 milliblurt
jitter). A dispatcher `jitterAmountForAsset(method, amount)`
routes to the right helper. USDT is pass-through (no-op) — its
privacy issue is centralization, which jitter doesn't address.
The AddressShareModal toggle (previously XMR-only) now surfaces
for every transparent asset. Default ON. Toggling OFF surfaces
an amber warning explaining the privacy cost.
### 3. Address-reuse detection
A new `lib/privacy/addressHistory.ts` helper tracks addresses
the user has shared from this device. **localStorage-only**
never transmitted to any Morphit server. Server-side address
tracking would be a privacy regression.
When the user pastes/types an address into the share modal that
matches a prior share, an amber warning chip surfaces with the
date and (if available) the previous order permlink.
Bounded at 200 entries (rolling buffer); fail-open on any
storage error.
### 4. PayJoin (BIP-78) optional endpoint
The address-share modal grows an optional advanced field for
BTC: PayJoin endpoint URL. When the seller supplies one, the
generated `bitcoin:` URI gains a `pj=<endpoint>` parameter and
the wire payload carries `payjoin_endpoint`. PayJoin-capable
buyer wallets switch to the BIP-78 PSBT exchange; wallets
without support ignore the parameter and fall back to a normal
payment. Zero footgun.
Morphit's role is URI relay only. The seller's wallet (or self-
hosted BTCPayServer / equivalent) supplies the endpoint; we
don't host it.
ChatMessage renders a green "🔐 PayJoin available" badge when
the payload carries an endpoint.
### 5. Per-asset privacy guide pages
`/[lang]/privacy` (index) lists all tradable assets with one-line
summaries (registry-driven — the page reads `ASSETS.filter(canBeTraded)`,
so additions like DASH (cp27) light up automatically).
`/[lang]/privacy/{asset}` (detail) renders an
asset-specific guide pulling copy from registry + shared i18n:
- Intro (per-asset, from `privacy.guides.{key}.intro`)
- Fresh-address advice (shared per advice type)
- Opt-in privacy techs (shared per tech, only renders when
present)
- Universal common practices (shared)
- "What to avoid" list (shared)
- Asset-specific caveats (per-asset, optional —
`privacy.guides.{key}.caveats`)
- "We don't recommend wallets" footer (shared)
Registry-driven. Adding Dash to Morphit later: populate the
`privacyFeatures` field, write `privacy.guides.dash.{intro, one_line, caveats?, meta_description}`
strings, done.
## Cp26 inline-fix: pre-existing latent bug
While auditing the encoder/decoder paths for PayJoin, we
discovered the `network` field on `AddressPayload` and
`FundsSentPayload` was declared in the interface but **silently
dropped** by `encodeAddressPayload` / `encodeFundsSentPayload`.
The decoder never read it either.
This is a cp3-era latent bug that has been undetected through
cp21, cp23, cp24, cp25. Symptom: USDT cross-network display in
ChatMessage shows `p.network` as `undefined`, breaking the
per-network header badge and the per-network explorer-link
selection.
Fixed inline because the wire-shape pattern was the same as the
PayJoin work. New smoke `payjoin-uri-wire-shape-smoke` covers
both: the cp26 PayJoin additions and the cp3-bug-fix roundtrips.
## Consequences
### Positive
- **Privacy framework is registry-driven.** Future asset
additions get the privacy guide + jitter + reuse warning for
free by populating one struct field.
- **No wallet recommendations.** Ken's call avoids liability;
protocol-standard names are descriptive, not endorsements.
- **Address-reuse detection is purely client-side.** Server-side
history would have been a privacy regression; localStorage-only
is the right shape.
- **PayJoin support without hosting the endpoint.** Morphit
stays a coordination layer; sellers bring their own PayJoin
infrastructure.
- **Latent cp3 USDT network-field bug fixed** as a side effect
of the PayJoin wire-shape work.
### Trade-offs accepted
- **Native translations only in en/es/fr/de.** Other 6 locales
(it/pl/ru/fa/zh-CN/zh-HK) ship cp26's new keys as English-
fallback to maintain locale parity at the file-shape level.
REVISIT entry filed for a translation pass; users in those
locales see English privacy-guide copy until then. Trade-off:
ship the privacy framework now versus block on translations.
- **Address-history per-device.** A user who uses Morphit on
laptop and phone won't see reuse warnings across devices.
Acceptable: server-side history is worse.
- **PayJoin requires both sides to opt-in.** Most BTC wallets
don't support BIP-78. The feature is a "real win for a small
subset, no harm to the rest" addition.
- **No wallet recommendations means users have to find their
own.** Trade-off accepted: Morphit not in the wallet-
recommendation business is the right posture.
### Future revisits
- Native translations for 6 locales (it/pl/ru/fa/zh-CN/zh-HK)
- Possible Tornado-Cash-style integration explainer for USDT
on supported host chains (with appropriate ecosystem
warnings)
- If Lightning ever becomes in-scope, expand the framework with
a `lightning` asset_network value (out of scope per Ken's
current decision)
- The `privacyFeatures` struct could grow a `walletRequirements`
field if a future asset has unusual wallet requirements (e.g.
"this asset requires a wallet that supports stealth-address
output detection"). Not needed today.
## Subsequent additions (CP35 status update — 2026-05-19)
The per-asset table in §2 was current at cp26 ship and listed
the six trade assets supported then (XMR, BTC, BLURT, USDT, BCH,
LTC). Subsequent checkpoints added
more assets that plug into this framework without changing the
framework itself; for reader convenience the current full table
is:
| Asset | freshAddressAdvice | optInPrivacyTech | privacyGuideKey |
|-------|--------------------|------------------------|-----------------|
| XMR | subaddress | null | xmr |
| BTC | hd-derived | [coinjoin, payjoin] | btc |
| BLURT | account-reuse | null | blurt |
| USDT | hd-derived | null | usdt |
| BCH | hd-derived | [cashfusion] | bch |
| LTC | hd-derived | [mweb] | ltc |
| DASH | hd-derived | [privatesend] | dash |
| USDC | hd-derived | null | usdc |
| DAI | hd-derived | null | dai |
| DOGE | hd-derived | null | doge |
| ZEC | hd-derived | [shielded-pools] | zec |
| ARRR | hd-derived | [shielded-pools] | arrr |
| DCR | hd-derived | [csppmix] | dcr |
| SOL | hd-derived | null | sol |
| ETH | hd-derived | null | eth |
| XRP | hd-derived | null | xrp |
Added after this ADR's ship date:
- **DASH** (Part 122 cp27) — opt-in PrivateSend mixing via
masternodes; otherwise transparent at base layer.
- **USDC** (Part 122 cp30) — second stablecoin; `usdc_centralized`
privacy-warning class (Circle has freeze power).
- **DAI** (Part 122 cp31) — third stablecoin; distinct
`dai_partly_centralized` class (MakerDAO has no freeze, but
Peg Stability Module USDC backing transitively affects).
- **DOGE** (Part 122 cp33) — fair-launched, merge-mined with
LTC, no native privacy upgrade.
- **ZEC** (Part 122) — Zcash; transparent (`t1`/`t3`) and shielded
(`zs1` Sapling, `u1` Unified Address) addresses coexist on the
same chain. Per-trade privacy posture is the user's choice.
- **ARRR** (Part 122) — Pirate Chain; shielded-by-construction
(Sapling only; no transparent option at the chain layer).
- **DCR** (Part 122) — Decred; hybrid PoW/PoS chain with opt-in
CoinShuffle++ mixing.
- **SOL** (Part 122) — Solana; high-throughput PoS, transparent.
- **ETH** (Part 122) — Ethereum; post-Merge PoS, transparent,
EIP-55 mixed-case-checksum address validation.
- **XRP** (Part 122) — XRP Ledger; Federated Byzantine Agreement,
transparent, destination-tag-aware.
> **Forward-note (Part 122 cp84, 2026-05-21):** asset count grew
> from the cp35 snapshot's 10 to the current 16 (ZEC, ARRR, DCR,
> SOL, ETH, XRP added). Privacy framework decision is unchanged;
> all six additions plug in via the registry-driven
> `privacyFeatures` struct without modifying framework code.
**Canonical reference** remains `packages/asset-registry/src/index.ts`.
This footnote is a convenience snapshot; the registry is
authoritative. No changes to the framework decision itself —
all additions used the framework as designed.