feat(wallet): subaddress provisioning and batched UTXO endpoints (off by default)

Backend support for two address-privacy features. Both are behind env flags
that default to off, so an unconfigured instance behaves exactly as before:
the routes are not mounted and no provisioning is attempted.

FEATURE_XMR_SUBADDR_PROVISIONING
  Adds POST /wallet/lws/provision_subaddrs and provisions account-0 subaddress
  indices on the LWS at register/import.

  Three money-safety properties worth calling out:

  * The provisioned ceiling is read from the LWS response, never echoed back
    from the request. It is the contiguous run starting at minor 0, not
    max(hi): given ranges [[0,99],[200,299]], reporting 299 would let a wallet
    hand out indices 100..199 that the LWS never scans, so funds sent there
    would be invisible. No usable run is a hard error, not a guess.
  * import_account now always runs the backward rescan even when provisioning
    fails. Previously a provisioning error aborted first, leaving the account
    registered at the chain tip while every retry returned 200 OK, so a
    restoring wallet would show a zero balance permanently.
  * A capacity probe reads max_subaddresses from the LWS and refuses up front
    with a message naming the required --max-subaddresses value, rather than
    failing per-account later. The option defaults to 0 upstream.

  Spent outputs now carry their subaddress index. monero-lws emits this as
  `sender` (the index the spent output was received at), not `recipient`;
  without it a client recomputes a primary key image for a subaddress spend,
  treats the mismatch as a decoy, and never subtracts the amount.

FEATURE_UTXO_MULTI_ADDRESS
  Adds balance_multi / utxos_multi / history_multi for HD gap-limit wallets.
  Capped at 32 addresses, summed with checked_add, per-UTXO address tag
  preserved so the client never has to re-guess which key owns an output.
  Bounded concurrency with a per-batch deadline returning 504, so a slow
  Electrum server cannot stack n * IO_TIMEOUT.

Both flags are advertised in /capabilities so a client can negotiate instead
of calling routes that 404.
This commit is contained in:
jwinterm
2026-07-28 16:01:25 -04:00
parent cf85dce89f
commit fb48e04ea6
12 changed files with 2379 additions and 44 deletions
+17
View File
@@ -107,6 +107,23 @@ FEATURE_XMR=true
FEATURE_WOW=true
FEATURE_GRIN=true
# ── Address-privacy features (opt-in) ─────────────────────────────────────
# Per-payment receive addresses. Off by default; enable once your node stack
# meets the requirement noted below.
#
# FEATURE_XMR_SUBADDR_PROVISIONING: provisions account-0 subaddress indices on
# the LWS at register/import so per-payment XMR/WOW receive addresses are
# scanned. REQUIRES your monero-lws / wownero-lws to run with
# `--max-subaddresses >= 200` (it defaults to 0, which disables subaddress
# support entirely). Check yours before enabling.
FEATURE_XMR_SUBADDR_PROVISIONING=false
#
# FEATURE_UTXO_MULTI_ADDRESS: mounts the batched BTC/LTC endpoints
# (/wallet/utxo/{balance,utxos,history}_multi) used by HD gap-limit wallets,
# capped at 32 addresses per request. When off, the routes are not mounted and
# the single-address endpoints are unchanged.
FEATURE_UTXO_MULTI_ADDRESS=false
# ── Bitcoin (Electrum/Fulcrum only; the backend runs no Core RPC node) ─────
BTC_NETWORK=mainnet
BTC_ELECTRUM_URL=