diff --git a/.env.example b/.env.example index 8d81696..a806fd2 100644 --- a/.env.example +++ b/.env.example @@ -130,7 +130,25 @@ FEATURE_UTXO_MULTI_ADDRESS=false # ── Bitcoin (Electrum/Fulcrum only; the backend runs no Core RPC node) ───── BTC_NETWORK=mainnet BTC_ELECTRUM_URL= -BTC_ELECTRUM_FALLBACKS= +# +# ── PRIVACY: read this before setting fallbacks ─────────────────────────────── +# These are THIRD-PARTY servers. When your own Electrum/Fulcrum is unreachable, +# the backend fails over to them, and the queries it sends carry YOUR USERS' +# ADDRESSES. The failover is silent and correct, so the only symptom is that +# balances keep working: a dead Fulcrum can go unnoticed for days while every +# lookup goes to someone else's server. (That is not hypothetical; it happened +# to us for 11 days.) +# +# Two honest choices, both defensible: +# * LEAVE THESE SET for availability. Balances survive your node dying. Watch +# for the "third-party Electrum fallback(s) configured" line at startup. +# * LEAVE THEM EMPTY to fail closed. Balances break when your node breaks, and +# every query stays on infrastructure you control. This is the right default +# for a privacy-first or air-gapped deployment. +# +# More entries = more resilience and a wider spread of who sees a given query. +# They are tried in order after the primary. +BTC_ELECTRUM_FALLBACKS=ssl://electrum.blockstream.info:50002,ssl://electrum.emzy.de:50002,ssl://electrum.bitaroo.net:50002,ssl://fulcrum.sethforprivacy.com:50002,ssl://electrum.diynodes.com:50002,ssl://bitcoin.lu.ke:50002,ssl://electrum.jochen-hoenicke.de:50006,ssl://btc.electroncash.dk:60002 # Require CA-verified TLS (webpki + hostname) for ssl:// electrum servers. Applies # to BOTH BTC and LTC. Default false: public electrum servers are self-signed by # convention and their data is treated as hostile regardless, so accepting any @@ -141,7 +159,8 @@ ELECTRUM_STRICT_TLS=false # ── Litecoin (Electrum/Fulcrum only; the backend runs no Core RPC node) ──── LTC_NETWORK=mainnet LTC_ELECTRUM_URL= -LTC_ELECTRUM_FALLBACKS= +# Same privacy tradeoff as BTC_ELECTRUM_FALLBACKS above; read that note. +LTC_ELECTRUM_FALLBACKS=ssl://electrum-ltc.bysh.me:50002,ssl://electrum.ltc.xurious.com:50002,ssl://backup.electrum-ltc.org:443,ssl://electrum1.cipig.net:20063,ssl://electrum2.cipig.net:20063,ssl://electrum3.cipig.net:20063,ssl://ltc.rentonisk.com:50002 # ── Monero (daemon + light wallet server) ───────────────────────────────── XMR_LWS_URL=http://127.0.0.1:8443 diff --git a/docs/operations/OPERATOR_SETUP.md b/docs/operations/OPERATOR_SETUP.md index c24800e..9f4e38a 100644 --- a/docs/operations/OPERATOR_SETUP.md +++ b/docs/operations/OPERATOR_SETUP.md @@ -53,6 +53,28 @@ flag is on but whose source is unconfigured reports `enabled: false` via | XMR / WOW | `{XMR,WOW}_LWS_URL`, `{XMR,WOW}_LWS_ADMIN_URL`, `{XMR,WOW}_LWS_ADMIN_KEY`, `{XMR,WOW}_DAEMON_URL`. | | Grin | `GRIN_OWNER_API_URL` + `GRIN_OWNER_API_SECRET`, `GRIN_WALLET_PASSWORD`, `GRIN_FOREIGN_API_URL`, and the node's `GRIN_NODE_*`. | +> **Electrum fallbacks are a privacy decision.** +> +> `{BTC,LTC}_ELECTRUM_FALLBACKS` are third-party servers. If your own +> Electrum/Fulcrum becomes unreachable, the backend fails over to them and the +> queries it sends carry **your users' addresses**. +> +> The failover is silent and correct, which is the trap: the only symptom is that +> balances keep working. Our own Litecoin Fulcrum was dead for 11 days before +> anyone noticed, with every lookup going to public servers in the meantime. +> +> Decide deliberately: +> +> - **Keep fallbacks** for availability. Balances survive your node dying. The +> backend logs `third-party Electrum fallback(s) configured` at startup so the +> choice is visible in your logs, and you should alert on your own Electrum +> being down rather than relying on users to notice. +> - **Leave them empty** to fail closed. Balances break when your node breaks, +> and every query stays on infrastructure you control. This is the right +> default for a privacy-first or air-gapped instance. +> +> There is no third option where a dead node is both invisible and private. + ## 4. Run ```sh diff --git a/src/config.rs b/src/config.rs index 4d715fd..e67dd5f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1151,6 +1151,29 @@ impl Config { } } + // Electrum fallbacks are a PRIVACY decision, not just an availability one. + // + // When the operator's own Fulcrum is unreachable the client fails over to + // these servers, and the queries it sends carry USER ADDRESSES. On an + // instance whose whole pitch is "run your own backend and send us + // nothing", that is a posture change the operator should make knowingly. + // + // It is easy to miss: the failover is silent and correct, so the only + // symptom is that balances keep working. A dead Fulcrum went unnoticed + // for 11 days on our own fleet for exactly that reason. + for (coin, cfg) in [("BTC", &self.chains.btc), ("LTC", &self.chains.ltc)] { + if cfg.electrum_fallbacks.is_empty() { + continue; + } + tracing::info!( + "{coin}: {} third-party Electrum fallback(s) configured. If your own \ + Electrum server becomes unreachable, address queries will go to \ + them instead. Set {coin}_ELECTRUM_FALLBACKS= (empty) to fail \ + closed and keep every query on infrastructure you control.", + cfg.electrum_fallbacks.len() + ); + } + // Public tips escrow on-chain and deliver by share URL, so an instance // with FEATURE_TIPS on but no chain to escrow on, or no TIP_SHARE_BASE_URL // to build share links from, would advertise a tips subsystem it cannot