Phase C: resolve signer from spire seed / bunker binding at bootstrap (#52) #60

Merged
padreug merged 5 commits from phase-c-bunker-bootstrap into dev 2026-06-21 10:35:56 +00:00

5 commits

Author SHA1 Message Date
09ed5e95de docs(nostr-client): TTL expiry is now a post-bind deauth cause
nsecbunkerd#27 enforces token lifecycle at sign time (Option D): an expired
token (`expiresAt`) now stops signing post-bind, not just at connect —
reversing the earlier #24 "TTL is connect-window-only" note. A lapsed TTL
now surfaces as the same BunkerRejectedError as a revoke, so the Phase D
re-pair handling covers both. Docstring corrected to say so.

refs nsecbunkerd#27/#24/#25, aiolabs/bitspire#52

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 23:19:58 +02:00
0391dbaeb0 chore(machine): fund-atm resumes from binding; VITE_SPIRE_SEED docs/env
fund-atm resolves its signer by resuming the bunker binding from state.db
(the connect token is already spent by the main app, so it can't re-pair);
falls back to a dev nsec via VITE_ATM_PRIVATE_KEY. better-sqlite3 marked
external in the esbuild bundle. .env.example + CLAUDE.md document
VITE_SPIRE_SEED as the prod identity, VITE_ATM_PRIVATE_KEY as dev-only.

(fund-atm is slated for deprecation in favour of the operator funding the
wallet directly via the LNbits UI — kept working for now.)

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:15:59 +02:00
82a9e79d0e feat(machine): resolve signer from spire seed / bunker binding at bootstrap
New signer-resolver.ts turns the ATM's pairing state into a Signer:
 - seed present, fingerprint differs from stored binding → pair: generate a
   transport key, redeem the one-shot connect secret, persist the binding,
   reset the bootstrap gate (re-publish hello to the new operator, #56);
 - seed matches binding, or binding-only → resume (no re-redeem);
 - neither → ephemeral LocalSigner (dev) or throw (strict/prod).

lightning.ts drops the atmPrivateKey plumbing and calls resolveSigner; the
Phase-A Signer seam means nothing downstream changes. App.vue's maintenance
beacon resolves the same way (best-effort, skips if unpaired).

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:15:45 +02:00
209e4c3e20 feat(machine): seed + bunker-binding IPC bridge
get-atm-secrets now returns { spireSeed, bunkerBinding } instead of the raw
nsec (one-shot semantics kept). Adds IPC handlers + preload bindings for
saveBunkerBinding / clearBunkerBinding / resetBootstrapGate so the renderer
can persist a pairing and re-arm the cassette-state hello on re-pair (#56).
resetBootstrapGate added to state-store. Types mirrored in electron.d.ts.

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:15:31 +02:00
40239aa075 refactor(clink): route CLINK signing + encryption through the Signer
Swap CLINKClient's MachineIdentity for the Signer abstraction: sign_event /
nip44 now go through the signer (async), so the spire identity can live in a
NIP-46 bunker. The kind-21003 management path (operator-driven manual
dispense, the one live CLINK path on dev) decrypts as the spire via the
bunker; the dormant offer/debit paths are migrated too so they're
bunker-ready when CLINK is re-implemented for the upcoming ndebit/k1 spec
(shocknet/CLINK#7, #8).

Part of Phase C, aiolabs/bitspire#52.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 00:15:17 +02:00