feat: electrum client (webpki TLS, bech32, checked sats)

BTC/LTC Electrum/Fulcrum client, reworked clean from the chain-audit
playbook (not ported). Every byte from a server is treated as hostile.

Security:
- TLS verified against the webpki root store WITH hostname checking; the
  accept-any-certificate verifier is gone (closes the MITM hole). The
  connector is built once and reused.
- Addresses decoded via the `bech32` crate (full checksum + bech32m +
  witness version) and base58check (checksum); the hand-rolled bech32
  underflow panic is removed. Coin/network enforced (reject ltc-on-btc,
  testnet-on-mainnet) — no byte-indexing of untrusted input.
- Amounts pass a fallible checked f64->sat converter (rejects
  non-finite/negative/>2^53) with checked_add summation; no silent
  saturation to 0 / u64::MAX. estimatefee requires finite, clamps.
- Bounded response read + per-exchange timeout (no OOM, no slowloris).
- JSON-RPC response id correlated with the request.
- Untrusted upstream text is control-stripped + length-capped before it
  reaches a (privately-logged) NodeError.

Single `call` primitive centralizes failover + id + envelope mapping;
public methods (balance/utxos/history/tip/verbose-tx/broadcast/fee) are
thin wrappers. Tip-attribution helpers dropped; total_sent_from now
declines (None) without resolved prevouts instead of mislabeling a
receive-with-change as a send.

20 L2 tests: address->script KATs (P2WPKH/P2WSH/taproot/legacy), network
rejection, panic-safety on short/garbage input, checked-sat bounds,
envelope id correlation, bounded/oversized read, duplex round-trip.
This commit is contained in:
jwinterm
2026-06-22 20:57:08 -04:00
parent 611e02e4de
commit e838565ed7
4 changed files with 1179 additions and 1 deletions
Generated
+8 -1
View File
@@ -300,13 +300,19 @@ version = "0.10.0-beta"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea"
[[package]]
name = "bech32"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8"
[[package]]
name = "bitcoin"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69197dee21fe23b45f5239bf88086efaa0cb8679f3e704906eb818e8ea169c14"
dependencies = [
"bech32",
"bech32 0.10.0-beta",
"bitcoin-internals",
"bitcoin_hashes",
"hex-conservative",
@@ -2799,6 +2805,7 @@ dependencies = [
"axum",
"axum-extra",
"base64 0.22.1",
"bech32 0.12.0",
"bitcoincore-rpc",
"bs58",
"chrono",
+1
View File
@@ -103,6 +103,7 @@ async-trait = "0.1"
# Validation
validator = { version = "0.16", features = ["derive"] }
bech32 = "0.12.0"
[dev-dependencies]
tokio-test = "0.4"
File diff suppressed because it is too large Load Diff
+1
View File
@@ -2,3 +2,4 @@
//! light-wallet servers). Chain clients are wired in as they land.
pub mod db;
pub mod electrum;