docs(10-05): record the Core-wallet fleet census — 4 nodes clear, 6 unchecked (D-07b)

Task 3 of plan 10-05, run by the operator over Tailscale on 2026-08-02 using the
read-only procedure in KEY-03-SIGNING-POSTURE.md. No escalation: nothing found.

Examined and CLEAR (4): archi-dev-box, shorty-s/.228, archy-x250-beta,
archy-x250-pa. On every one there is no wallet named `archipelago` — the deleted
handler's default wallet_name — `listwallets` returns only the unnamed default,
and that default reports blank=true, keypoolsize=0, txcount=0, balance=0. The
only named wallets are Fedimint gatewayd-*. The result holds across two
container vintages (bitcoin-knots and bitcoin-core), so it is not four copies of
one image behaving identically.

Not examined (6), recorded with reasons rather than omitted: framework-pt,
archipelago-1, archipelago and archy-dev-pa (SSH permission denied — password
rotated/not held), archipelago-5 (timed out during banner exchange), and
archy-x250-dev (offline). Password auth was deliberately not attempted: several
fleet nodes lock PAM quickly on a wrong password, and locking out an in-use
production node is a worse outcome than an incomplete census.

The conclusion is stated at the strength the evidence supports — no *examined*
node holds a wallet the deleted handler created, and no examined node holds any
wallet with keys or funds. It is deliberately NOT generalised to "the fleet is
clear" while six nodes are unknown. F-13 is closed by deletion regardless: the
code that could create such a wallet is gone from every future build.

No key material appeared in any output and `listdescriptors true` was never run.

Also corrects the now-stale R-04/F-13 entry in UNIFIED-TASK-TRACKER.md, which
still described `handle_bitcoin_init_wallet_from_seed` and a watch-only
migration as pending work — that code no longer exists. Marks it done-by-
deletion and adds the six unchecked nodes as a standing item, flagged as a
natural fold-in for KEY-04's on-node work but tracked independently so it does
not vanish if KEY-04 is re-scoped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-02 11:32:50 -04:00
co-authored by Claude Opus 5
parent 454388226c
commit 0d513a0ef7
2 changed files with 97 additions and 32 deletions
+27 -13
View File
@@ -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
+70 -19
View File
@@ -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=<name> 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