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

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:

  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.