Commit graph

6 commits

Author SHA1 Message Date
5179a21da6 fix(nostr-client): reject non-ws(s):// relays in the spire seed
The npubs in the seed are bech32-checksummed, so a mis-scanned character is
caught — but the relay strings are raw inside the base64. A QR misread silently
turned `ws://192.168.0.32:5001/...` into `As://192.168.0.32:5001/...`, which
parsed fine and then crash-looped the machine on an unreachable NIP-46 relay.

Validate every `relays[]` entry (and `bunker_relay`) is a `ws://`/`wss://` URL
at parse time, so a garbled scan is rejected as an invalid seed instead of
persisted. Part of bitspire-#70 pairing robustness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:53:51 +00:00
98bdd92044 refactor(nostr-client): slim the spire-seed to carry the pubkey once, add lnbits_npub
The v1 seed spelled the spire pubkey three times — spire_npub, spire_pubkey
(hex), and again inside a full bunker_url — which bloats a QR that's already
hard to scan off the machine's camera. Carry it once, as an npub, and derive
the rest:

- spire_pubkey (hex) ← decode(spire_npub). npub is ~the same length as hex but
  carries a bech32 checksum, so a mis-scanned character is caught instead of
  yielding a wrong-but-valid-looking key.
- bunker_url ← reconstructed from spire_pubkey + bunker_secret + bunker_relay.
- bunker_relay is OPTIONAL, defaulting to relays[0] (option 3): minimal in the
  common case where the bunker shares the event relay, explicit when it differs.
- lnbits_npub is NEW — gives a paired machine its LNbits transport server pubkey
  from the seed itself, so nothing else needs provisioning (bitspire-#70 part 2).

Kept as v: 1 (redefined in place, no compat shim): the seed is a one-shot
pairing token, no bitspire machine has shipped, and a paired machine resumes
from its stored binding, not by re-parsing the seed. Roughly a third smaller
encoded — ~180-200 fewer chars in the QR.

Lockstep: aiolabs/spirekeeper pairing.py must emit the new shape (spire_npub +
lnbits_npub + bunker_secret, drop spire_pubkey/bunker_url) before a new seed can
be minted. Consumer wiring (relays + lnbitsServerPubkey into LightningConfig)
and a resolver-resilience guard for machines holding an old-shape seed land
separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:53:51 +00:00
9c9009af31 feat(nostr-client): NIP-46 bunker signer + spire pairing seed
Phase B of aiolabs/bitspire#52 — the consumer surface for routing signing
to the operator's nsecbunkerd (model A1: the ATM holds only its own NIP-46
transport key; the signing identity lives in the bunker).

- seed.ts: parseSpireSeed for the `spire-seed:v1:<base64url>` contract from
  spirekeeper pairing.py — re-pads stripped base64url, validates
  {v, spire_pubkey, bunker_url, relays}, leaves percent-decoding of the
  bunker URL to parseBunkerInput. seedFingerprint() detects a re-pair.
- bunker-signer.ts: BunkerSigner implements Signer by delegating
  sign_event / nip44_* to nostr-tools' nip46 over the bunker relay. pubkey
  is the spire identity, known synchronously from the seed. connectNewSeed
  redeems the one-shot connect secret; resumeFromBinding reuses the
  persisted transport key WITHOUT re-redeeming (the binding is
  server-persistent). Per-RPC timeout + typed BunkerRejectedError /
  BunkerTimeoutError so callers can distinguish revoked-binding (re-pair)
  from a transient outage.

Unit-tested against a fake inner client (delegation, sync pubkey, timeout,
error mapping) + seed round-trip/validation fixtures. Live-relay wiring is
Phase C; live bunker integration is Phase F.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:24:22 +02:00
787de5bff1 refactor(nostr-client): retire dead NIP-44 v1 / Lightning.Pub path
Drop encryptContent / decryptContent / decryptJSON and the hand-rolled
XChaCha20 + v1 conversation-key machinery they depended on (~230 lines).
The only callers were createMachineStatusEvent / createTransactionEvent,
which had no callers in apps/ and were removed in the Signer migration.

This closes the open question carried in aiolabs/bitspire#52: every live
encryption path is NIP-44 v2, and the nsecbunkerd signer is v2-only, so
there is nothing to keep v1 for. encryptContentV2 / decryptContentV2 stay
as the v2 helpers used by the dormant CLINK client + tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:57:02 +02:00
d6b22e1156 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>
2026-06-18 19:56:35 +02:00
Patrick Mulligan
c98f126ba7 feat(docker): add dev.sh with auto-funding and ATM app setup
- Add dev.sh script for managing regtest development environment
- Implement cmd_fund to fund ATM app owner via Lightning.Pub API
- Add --fund flag to cmd_up for automatic funding on startup
- Update setup_atm_app to write VITE_APP_ID to machine .env
- Fix Electron IPC to pass appId and extensionApiUrl to renderer
- Restructure repo from nested lamassu-next/ to root

The dev.sh script now supports:
- ./dev.sh up --fund  # Start regtest and auto-fund ATM
- ./dev.sh fund       # Fund existing ATM app
- ./dev.sh status     # Show environment status
- ./dev.sh reset      # Clean restart

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-15 14:19:16 -05:00