12 KiB
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:
- Nudge better practices in the UX (fresh addresses per trade, amount randomization, reuse warnings)
- Surface opt-in privacy tech that the chain or its ecosystem supports (MWEB, CashFusion, CoinJoin, PayJoin)
- 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:nullfor 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 fromprivacy.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
lightningasset_network value (out of scope per Ken's current decision) - The
privacyFeaturesstruct could grow awalletRequirementsfield 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_centralizedprivacy-warning class (Circle has freeze power). - DAI (Part 122 cp31) — third stablecoin; distinct
dai_partly_centralizedclass (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 (zs1Sapling,u1Unified 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
privacyFeaturesstruct 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.