diff --git a/docs/UNIFIED-TASK-TRACKER.md b/docs/UNIFIED-TASK-TRACKER.md index d969aeca..46ba9e66 100644 --- a/docs/UNIFIED-TASK-TRACKER.md +++ b/docs/UNIFIED-TASK-TRACKER.md @@ -203,19 +203,33 @@ those are marked ✅ below with the commit that did it, so we stop re-litigating authentication boundary on a live fleet — **needs its own `/gsd-plan-phase` with a federation re-verify**, not an opportunistic patch. -- [ ] **PSBT-first signing: Phase 1 — move the Bitcoin private key out of Core** - (entropy audit R-04, finding **F-13** + `docs/security/PSBT-SIGNING-ARCHITECTURE.md` - §8 Phase 1). `handle_bitcoin_init_wallet_from_seed` passes - `disable_private_keys = false` (`core/archipelago/src/api/rpc/bitcoin.rs:203`) and - imports `wpkh(xprv/0/*)` / `wpkh(xprv/1/*)` (`:229-231`), so the BIP-84 account - **private** key is persisted in Bitcoin Core's `wallet.dat` — with an empty wallet - passphrase (`:205`) — in addition to the daemon's Argon2 envelope. The descriptors - also carry **no `[fingerprint/derivation]` key origin**, so no hardware signer - could ever locate its key even if the private key were removed. Fix: watch-only - (`disable_private_keys=true`) + xpub-with-origin descriptors + a migration that - verifies balance and UTXO parity **before** removing the old wallet. This is the - single highest-value change in the PSBT spec and unblocks every later phase. - Needs a node with real UTXO history to verify. +- [x] **PSBT-first signing: Phase 1 — move the Bitcoin private key out of Core** — **DONE + 2026-08-02 by deletion, not conversion** (entropy audit R-04, finding **F-13**; + Phase 10 plan 10-05, decision **D-07b**). The handler that imported the BIP-84 + account **private** key into Core's `wallet.dat` had no caller anywhere, LND is the + wallet the UI drives, and the endpoint was authenticated *and* password-gated — so + it was deleted outright rather than rewritten watch-only. `bitcoin.rs`'s wallet-init + handler and its `dispatcher.rs` arm are gone; **no daemon code path writes the + BIP-84 private key into Bitcoin Core.** No migration was performed or is needed — + a 4-node fleet census found no wallet the handler created. D-09's key-origin + requirement moved to the PSBT itself: `lnd.create-psbt` now reports + `key_origin` (`psbt_key_origin_report`, `api/rpc/lnd/wallet.rs`). + **Read `docs/security/KEY-03-SIGNING-POSTURE.md` for the current state** — it also + records the verdict that **no fleet node is provisioned watch-only**, so what ships + today is PSBT *transport*, not air-gapped custody. + +- [ ] **Finish the Core-wallet fleet census — 6 nodes unchecked** (Phase 10 plan 10-05, + Task 3; standing item). The 2026-08-02 census examined 4 nodes (archi-dev-box, + shorty-s/.228, archy-x250-beta, archy-x250-pa) and found **no** wallet created by + the deleted handler and no wallet holding keys or funds. Six were not examined: + framework-pt, archipelago-1, archipelago, archy-dev-pa and archipelago-5 + (SSH auth/connectivity) and archy-x250-dev (offline). Re-run the **read-only** + procedure in `docs/security/KEY-03-SIGNING-POSTURE.md` § *Fleet census* when + credentials or connectivity allow — a natural fold-in for KEY-04's on-node work. + **Never run `listdescriptors true`** (it returns private keys). If any node reports + a wallet named `archipelago`, or any descriptor wallet with + `private_keys_enabled: true` that is not blank/empty, **stop and escalate — do not + migrate or modify it** (D-07b). - [ ] **PSBT-first signing: Phases 2-7 rollout** (`docs/security/PSBT-SIGNING-ARCHITECTURE.md` §8) — the spec is written to be diff --git a/docs/security/KEY-03-SIGNING-POSTURE.md b/docs/security/KEY-03-SIGNING-POSTURE.md index c820ea6a..f63bf3df 100644 --- a/docs/security/KEY-03-SIGNING-POSTURE.md +++ b/docs/security/KEY-03-SIGNING-POSTURE.md @@ -92,8 +92,8 @@ key was ever imported into it and no transaction ever touched it**. The two `gat are Fedimint gateway wallets, unrelated to the BIP-84 path. The `wallet.dat` at the datadir root is Core's own legacy default-wallet location, not this handler's output. -**This is one node.** archi-dev-box is verified; the rest of the fleet is **UNVERIFIED** pending -the census below. +**This is one node.** The same check was subsequently run across the reachable fleet — see the +census below: **4 nodes examined and clear, 6 unreachable and therefore unknown.** **Supporting history evidence:** `git log -S "init-wallet-from-seed"` scoped to `core/archipelago/src/api/rpc/dispatcher.rs` and `neode-ui/src` returns exactly one commit — @@ -314,11 +314,16 @@ loss. ## Fleet census — Core descriptor wallets -**Status: INCOMPLETE — one node verified, fleet pending.** This section answers one question per -node: *does this node hold a Bitcoin Core descriptor wallet that -`handle_bitcoin_init_wallet_from_seed` created, and does it hold private keys?* It is recorded -per node rather than assumed, because deletion closes the door but does not tell us whether -anyone walked through it before. +**Status: run 2026-08-02 — 4 nodes examined and CLEAR, 6 nodes UNCHECKED. No escalation.** + +This section answers one question per node: *does this node hold a Bitcoin Core descriptor wallet +that the deleted wallet-init handler created, and does it hold private keys?* It is recorded per +node rather than assumed, because deletion closes the door but does not tell us whether anyone +walked through it before. + +The nodes that could **not** be examined are listed with their reasons, not omitted. A census +that quietly drops its failures is worthless — an auditor must be able to see exactly which +machines were looked at and which were not. ### Hard constraint on every command in this census @@ -366,20 +371,66 @@ bitcoin-cli -rpcuser=archipelago -stdinrpcpass -rpcwallet= listdescriptors bitcoin-cli -rpcuser=archipelago -stdinrpcpass getnetworkinfo | head ``` -### Results +### Results — examined, 2026-08-02 (4 nodes, all CLEAR) -| Node | App / version | `wallets_dir_present` | `listwallets` | `archipelago` wallet? | Private-key-bearing wallet? | Verdict | -|---|---|---|---|---|---|---| -| **archi-dev-box** | Bitcoin Knots 29.3 (`bitcoin-knots` container) | — (`listwalletdir` used instead) | `[ "" ]` | **No** | The unnamed default wallet reports `private_keys_enabled: true`, but also `blank: true`, `keypoolsize: 0`, `txcount: 0`, `balance: 0` — Core's own statement that **no key was ever imported and no transaction ever touched it**. It is not this handler's output. | **CLEAR** — verified 2026-08-02 | -| `.228` (shorty-s, resilience node) | — | — | — | — | — | **UNCHECKED** | -| `.198` (OptiPlex) | — | — | — | — | — | **UNCHECKED** | -| `.116` (thinkpad / dev node) | — | — | — | — | — | **UNCHECKED** | -| `x250-dev` | — | — | — | — | — | **UNCHECKED** | -| framework-pt | — | — | — | — | — | **UNCHECKED** | +Run by the operator over Tailscale, read-only RPCs only. -`listwalletdir` on archi-dev-box also returned two `gatewayd-02004b91…` / `gatewayd-03443c0c…` -wallets. These are **Fedimint gateway** wallets, unrelated to the BIP-84 path and out of scope -for this finding. +| Node | Tailscale IP | Container | `listwalletdir` | `listwallets` | `archipelago` wallet? | Default wallet state | Verdict | +|---|---|---|---|---|---|---|---| +| **archi-dev-box** | `100.69.68.39` | `bitcoin-knots` | 2× `gatewayd-*`, `""` | `[ "" ]` | **No** | `blank: true`, `keypoolsize: 0`, `txcount: 0`, `balance: 0.00000000`, `descriptors: true` | **CLEAR** | +| **shorty-s** (`.228`) | `100.64.204.114` | `bitcoin-knots` | 1× `gatewayd-*`, `""` | `[ "" ]` | **No** | same | **CLEAR** | +| **archy-x250-beta** | `100.72.136.5` | `bitcoin-core` | 1× `gatewayd-*`, `""` | `[ "" ]` | **No** | same | **CLEAR** | +| **archy-x250-pa** | `100.89.209.89` | `bitcoin-core` | 1× `gatewayd-*`, `""` | `[ "" ]` | **No** | same | **CLEAR** | + +On every examined node there is **no wallet named `archipelago`** — the deleted handler's default +`wallet_name`. The only named wallets are Fedimint `gatewayd-*`, unrelated to the BIP-84 path. + +The one loaded wallet on each node is Core's unnamed default. It does report +`private_keys_enabled: true`, but also `blank: true` with `keypoolsize: 0`, `txcount: 0` and +`balance: 0.00000000` — **Bitcoin Core's own statement that no key was ever imported into it and +no transaction ever touched it.** It is not the deleted handler's output, and it holds nothing. + +**The result holds across two container vintages** — `bitcoin-knots` on two nodes and +`bitcoin-core` on two others. That matters: it is not four copies of one image behaving +identically, so the finding is a property of the fleet rather than an artefact of a single build. + +**No key material appeared in any output, and `listdescriptors true` was never run.** + +### Not examined, 2026-08-02 (6 nodes, with reasons) + +| Node | Tailscale IP | Why not checked | +|---|---|---| +| framework-pt | `100.65.115.109` | `Permission denied (publickey,password)` — SSH password rotated, not held | +| archipelago-1 | `100.82.34.38` | `Permission denied (publickey,password)` | +| archipelago | `100.70.96.88` | `Permission denied (publickey,password)` | +| archy-dev-pa | `100.64.83.15` | `Permission denied (publickey,password)` | +| archipelago-5 | `100.114.134.21` | Timed out during SSH banner exchange | +| archy-x250-dev | `100.113.100.55` | Offline — Tailscale reports last seen 2 days prior | + +**Password authentication was deliberately not attempted on any of these.** Several fleet nodes +lock PAM quickly on a wrong password, and locking an in-use production node out is a worse +outcome than an incomplete census. These are recorded as UNCHECKED, **not** as clear. + +### Conclusion, at the strength the evidence supports + +> **No examined node holds a wallet created by the deleted handler, and no examined node holds +> any wallet with keys or funds.** Four nodes, across two container vintages, on 2026-08-02. + +**This is deliberately not a claim that "the fleet is clear."** Six nodes were not examined, and +an unexamined node is unknown, not safe. F-13 is closed **by deletion** — the code that could +create such a wallet is gone from every future build, which is true regardless of the census — +and the census adds that no such wallet was found where anyone could look. + +### Standing item — finish the census + +The six unchecked nodes remain open. **Homed in `docs/UNIFIED-TASK-TRACKER.md`** (the project's +canonical "what's open" list) as *"Finish the Core-wallet fleet census — 6 nodes unchecked"*, +rather than only here, so it is visible to someone who is not already reading a security +document. It is flagged there as a natural fold-in for **KEY-04's on-node work**, which needs +node access anyway — but it is tracked independently so it does not vanish if KEY-04 is +re-scoped. + +Re-run the read-only procedure above when credentials or connectivity allow. ### Standing rule if a wallet is found