refactor(nostr): route signing + encryption through a Signer abstraction

Introduce a Signer interface (signEvent / nip44Encrypt / nip44Decrypt +
sync pubkey) with an in-process LocalSigner backed by an nsec, and route
every signing/encryption call site through it. Behaviour is unchanged —
LocalSigner wraps the same MachineIdentity the code used directly before.

This is Phase A of the bunker migration (aiolabs/bitspire#52): it puts the
seam in place so Phase B can drop in a NIP-46 BunkerSigner at the bootstrap
without touching any call site. The whole chain becomes async (the bunker
path is a relay round-trip; LocalSigner resolves immediately).

Sites moved onto the signer:
- packages/nostr-client: createSignedEvent / createAuthEvent (now async),
  NostrClient config (signer not identity), AUTH challenge handler.
- packages/lnbits: LnbitsClient.initialize(nostr, signer); kind-21000 RPC
  encrypt + sign + reply-decrypt; handleReply is now async (event-id dedup
  still runs synchronously before the awaited decrypt, so replay safety and
  per-subscription hash dedup are preserved).
- apps/machine: lightning.ts builds a LocalSigner and exposes it on
  LightningServices; operator-config / operator-fees / availability beacon /
  maintenance beacon / fund-atm all sign + encrypt via the signer.

NIP-42 auth (kind 22242) is included — under the bunker it must be in the
spire policy (aiolabs/spirekeeper#26, already merged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-18 19:56:35 +02:00
commit d6b22e1156
16 changed files with 300 additions and 260 deletions

View file

@ -13,7 +13,7 @@
*/
import { readFileSync } from 'node:fs'
import { NostrClient, loadIdentityFromHex } from '@bitSpire/nostr-client'
import { NostrClient, LocalSigner, loadIdentityFromHex } from '@bitSpire/nostr-client'
import { LnbitsClient } from '@bitSpire/lnbits'
// @ts-ignore — qrcode is a transitive dep (via qrcode.vue), no types needed
@ -64,11 +64,11 @@ async function main() {
console.error(`Generating invoice for ${amountSats} sats...`)
const identity = loadIdentityFromHex(atmPrivateKey)
const signer = new LocalSigner(loadIdentityFromHex(atmPrivateKey))
const nostrClient = new NostrClient({
relays: [{ url: relayUrl }],
identity,
signer,
})
await nostrClient.connect()
@ -76,7 +76,7 @@ async function main() {
serverPubkey: lnbitsServerPubkey,
relays: [relayUrl],
})
lnbits.initialize(nostrClient, identity)
lnbits.initialize(nostrClient, signer)
const wallets = await lnbits.listWallets()
const wallet = wallets[0]