S7 — Consume LNbits sidecar bunker (was: NIP-46 bunker option) #12

Open
opened 2026-06-14 07:08:44 +00:00 by padreug · 1 comment
Owner

Migrated from aiolabs/satmachineadmin#21 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated.

Part of #8. Closes gap G6 in full (Account.prvkey readable from DB) and the residual part of G5.

2026-05-26 — no longer "future / optional"

aiolabs/lnbits#9 was reframed 2026-05-25: the bunker is now the steady-state architecture, not a sovereignty escape valve. aiolabs/lnbits#18 (filed same day) is the concrete phase-2 plan: sidecar nsecbunkerd on every lnbits host, RemoteBunkerSigner as the default signer_type for new accounts, LocalSigner retained only as a transitional shim for migrating existing rows.

S7 in this repo therefore becomes "consume the bunker once aiolabs/lnbits#18 ships." Our role shrinks dramatically — the heavy lifting (NIP-46 client, admin client, scoped-token issuance, NIP-05 publication) lives upstream.

Architecture (revised)

Operator's human admin nsec  — stays on Amber / hardware signer (not LNbits)
            │
            │ admin RPC (kind:24134, NIP-44 v2 over internal relay)
            ▼
nsecbunkerd  — sidecar on the lnbits host
            │   holds: M_lnbits (admin), X_alice, X_bob, ...
            │   per-target connection tokens with SigningConditions
            ▼
LNbits      — holds: pubkey + signer_type=RemoteBunkerSigner + signer_config (token + perms)
            │   NO nsec material
            ▼
satmachineadmin — issues per-ATM scoped tokens via the lnbits admin client
            │   (sign_event:21000 only, 30-day expiry)
            ▼
ATM         — NIP-46 client; connects via bunker:// URL from the pairing seed

Daily ATM operations are unchanged in pattern from the original S7 sketch: the ATM signs kind-21000 with its own ephemeral keypair (the connection token's client identity), and the bunker mediates the operator-side signing of long-lived events (delegations are gone — replaced by token rows in nsecbunkerd's Prisma DB).

Changes in this repo

Satmachineadmin backend

  • POST /api/v1/dca/machines/:id/pair builds the seed URL by calling the lnbits admin client's create_token API (introduced by aiolabs/lnbits#18):
    token = await bunker_admin.create_token(
        target_pubkey=operator_pubkey,
        client_name=f"satmachine-{machine.id}",
        perms=["sign_event:21000"],
        expires_at=int(time.time()) + 86400 * 30,
    )
    
    The seed URL embeds the returned bunker_url.
  • dca_machines gets a bunker_connection_id column so we can revoke_user / re-issue.
  • All operator-authored long-lived events (fleet roster updates, super-fee policy, NIP-78 config) route through resolve_signer(operator_account).sign_event(event). No direct access to operator's prvkey.

Satmachineadmin frontend

  • Fleet tab "Pair ATM" wizard already exists from S0; no UI change needed beyond showing the bunker round-trip status during seed issuance.
  • New "Revoke ATM access" button per machine row → revoke_user admin RPC.

LNbits side

  • Everything in aiolabs/lnbits#18. We consume; we don't implement.

Acceptance

  • Operator pairs a new ATM → wizard calls bunker_admin.create_token → seed URL embeds the resulting bunker://...?secret=... → ATM redeems on first boot → kind-21000 round-trip works.
  • Revoke from operator dashboard → bunker rejects subsequent requests; satmachineadmin reflects revoked state in the Fleet UI.
  • Super-fee config change requires bunker round-trip (operator account is RemoteBunkerSigner post-migration).
  • Daily cash-out path is not slowed by the bunker — the ATM holds the connection token locally; only operator-authored events incur the bunker round-trip.
  • Post-migration: zero accounts.prvkey rows for any operator that owns satmachineadmin machines.

Sequencing

Hard-blocked on aiolabs/lnbits#18. Sprint 3, after Sprint 1 (S0+S1+S5) + Sprint 2 (S2+S3+S4) land. Practically: once lnbits#18 ships nsecbunkerd-on-aio-demo and RemoteBunkerSigner.sign_event works end-to-end, S7 here becomes a 1-week consumer-side task instead of a 4-6 week build.

Reference

  • aiolabs/lnbits#9 — operator-IdP framing (the why).
  • aiolabs/lnbits#18 — sidecar bunker integration (the how). §F is our alignment.
  • github.com/kind-0/nsecbunkerd — the bunker we'll consume; see OAUTH-LIKE-FLOW.md.
  • NIP-46 spec: ~/dev/nostr-protocol/nips/46.md.
  • Design doc: docs/security-pathway-v1.md §5.1, §6.S7 — needs follow-up edit reflecting that the bunker is standard, not optional.
> _Migrated from aiolabs/satmachineadmin#21 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated._ Part of #8. Closes gap G6 in full (`Account.prvkey` readable from DB) and the residual part of G5. ## 2026-05-26 — no longer "future / optional" `aiolabs/lnbits#9` was reframed 2026-05-25: the bunker is now the **steady-state architecture**, not a sovereignty escape valve. `aiolabs/lnbits#18` (filed same day) is the concrete phase-2 plan: sidecar `nsecbunkerd` on every lnbits host, `RemoteBunkerSigner` as the default `signer_type` for new accounts, `LocalSigner` retained only as a transitional shim for migrating existing rows. S7 in this repo therefore becomes **"consume the bunker once aiolabs/lnbits#18 ships."** Our role shrinks dramatically — the heavy lifting (NIP-46 client, admin client, scoped-token issuance, NIP-05 publication) lives upstream. ## Architecture (revised) ``` Operator's human admin nsec — stays on Amber / hardware signer (not LNbits) │ │ admin RPC (kind:24134, NIP-44 v2 over internal relay) ▼ nsecbunkerd — sidecar on the lnbits host │ holds: M_lnbits (admin), X_alice, X_bob, ... │ per-target connection tokens with SigningConditions ▼ LNbits — holds: pubkey + signer_type=RemoteBunkerSigner + signer_config (token + perms) │ NO nsec material ▼ satmachineadmin — issues per-ATM scoped tokens via the lnbits admin client │ (sign_event:21000 only, 30-day expiry) ▼ ATM — NIP-46 client; connects via bunker:// URL from the pairing seed ``` Daily ATM operations are **unchanged in pattern** from the original S7 sketch: the ATM signs kind-21000 with its *own* ephemeral keypair (the connection token's client identity), and the bunker mediates the operator-side signing of long-lived events (delegations are gone — replaced by token rows in nsecbunkerd's Prisma DB). ## Changes in this repo **Satmachineadmin backend** - `POST /api/v1/dca/machines/:id/pair` builds the seed URL by calling the lnbits admin client's `create_token` API (introduced by `aiolabs/lnbits#18`): ```python token = await bunker_admin.create_token( target_pubkey=operator_pubkey, client_name=f"satmachine-{machine.id}", perms=["sign_event:21000"], expires_at=int(time.time()) + 86400 * 30, ) ``` The seed URL embeds the returned `bunker_url`. - `dca_machines` gets a `bunker_connection_id` column so we can `revoke_user` / re-issue. - All operator-authored long-lived events (fleet roster updates, super-fee policy, NIP-78 config) route through `resolve_signer(operator_account).sign_event(event)`. No direct access to operator's `prvkey`. **Satmachineadmin frontend** - Fleet tab "Pair ATM" wizard already exists from S0; no UI change needed beyond showing the bunker round-trip status during seed issuance. - New "Revoke ATM access" button per machine row → `revoke_user` admin RPC. **LNbits side** - Everything in `aiolabs/lnbits#18`. We consume; we don't implement. ## Acceptance - [ ] Operator pairs a new ATM → wizard calls `bunker_admin.create_token` → seed URL embeds the resulting `bunker://...?secret=...` → ATM redeems on first boot → kind-21000 round-trip works. - [ ] Revoke from operator dashboard → bunker rejects subsequent requests; satmachineadmin reflects revoked state in the Fleet UI. - [ ] Super-fee config change requires bunker round-trip (operator account is `RemoteBunkerSigner` post-migration). - [ ] Daily cash-out path is *not* slowed by the bunker — the ATM holds the connection token locally; only operator-authored events incur the bunker round-trip. - [ ] Post-migration: zero `accounts.prvkey` rows for any operator that owns satmachineadmin machines. ## Sequencing **Hard-blocked on `aiolabs/lnbits#18`.** Sprint 3, after Sprint 1 (S0+S1+S5) + Sprint 2 (S2+S3+S4) land. Practically: once lnbits#18 ships nsecbunkerd-on-aio-demo and `RemoteBunkerSigner.sign_event` works end-to-end, S7 here becomes a 1-week consumer-side task instead of a 4-6 week build. ## Reference - `aiolabs/lnbits#9` — operator-IdP framing (the why). - `aiolabs/lnbits#18` — sidecar bunker integration (the how). §F is our alignment. - `github.com/kind-0/nsecbunkerd` — the bunker we'll consume; see `OAUTH-LIKE-FLOW.md`. - NIP-46 spec: `~/dev/nostr-protocol/nips/46.md`. - Design doc: `docs/security-pathway-v1.md` §5.1, §6.S7 — needs follow-up edit reflecting that the bunker is standard, not optional.
Author
Owner

Status 2026-06-16 — bunker primitives shipped; API + naming

Same unblock as #9: RemoteBunkerSigner (sign_event + nip44_decrypt) and NsecBunkerAdminClient (policy + token issuance) are live on lnbits dev, verified against aiolabs/nsecbunkerd@fb1c239 (lnbits#18 status 2026-06-16).

API correction: token issuance is policy-based — create_new_policycreate_new_token(key_name, client_name, policy_id)get_key_tokens — not the create_token(target, perms, expiry) sketch above.

Naming: this repo is now aiolabs/spirekeeper. Operator-authored events already route through resolve_signer(account) in nostr_publish.py, so the S7 "no direct prvkey" property already holds for operators migrated to RemoteBunkerSigner — the demo super 8b2498… is bunker-backed and proven (it signed the fee-config publish on 2026-06-16, event_id=dded6a3d…).

Remaining S7 work is the per-machine token lifecycle (mint on pair, revoke on un-pair) — shares the pair endpoint with S0/#9.

## Status 2026-06-16 — bunker primitives shipped; API + naming Same unblock as #9: `RemoteBunkerSigner` (sign_event + nip44_decrypt) and `NsecBunkerAdminClient` (policy + token issuance) are live on `lnbits` `dev`, verified against `aiolabs/nsecbunkerd@fb1c239` (lnbits#18 status 2026-06-16). **API correction:** token issuance is policy-based — `create_new_policy` → `create_new_token(key_name, client_name, policy_id)` → `get_key_tokens` — not the `create_token(target, perms, expiry)` sketch above. **Naming:** this repo is now `aiolabs/spirekeeper`. Operator-authored events already route through `resolve_signer(account)` in `nostr_publish.py`, so the S7 "no direct prvkey" property already holds for operators migrated to `RemoteBunkerSigner` — the demo super `8b2498…` is bunker-backed and proven (it signed the fee-config publish on 2026-06-16, `event_id=dded6a3d…`). Remaining S7 work is the per-machine token lifecycle (mint on pair, revoke on un-pair) — shares the `pair` endpoint with S0/#9.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/spirekeeper#12
No description provided.