S4 — NIP-78 per-machine config + fleet roster cross-check #18
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #13. Closes gaps G1 (routing is wallet_id-only) and G9 (no ACL on auto-account-from-npub).
Problem
The stale-
npub1111…incident: a test machine row routed a real cash-in because routing isget_active_machine_by_wallet_id(payment.wallet_id)— no signed identity check. Any wallet on the LNbits instance with adca_machinesrow pointing at it can absorb settlements.Fix
Operator publishes a signed NIP-78 replaceable event per machine + a fleet roster:
Plus an aggregate roster (
d="bitspire-fleet") listing every machine pubkey the operator owns.LNbits handler and satmachineadmin's settlement-land code cross-check that the inbound's
atm_pubkeyis in the operator's roster, with config matching.Changes
This repo (
aiolabs/satmachineadmin)crud.publish_machine_config(machine)— composes + signs the kind:30078 event using the operator's nsec (via LNbits Account.prvkey today; via NIP-46 bunker once S7 lands).POST/PUT /api/v1/dca/machines, publish/update config.DELETE /api/v1/dca/machines/:id, republish roster without the machine — relays drop the older replaceable.tasks.pyadds a post-router check:machine.machine_npub == settlement.sender_pubkey(from S5 Payment.extra) ∧machine_npub ∈ operator's fleet roster. If either fails, refuse to record + alert.lamassu-next#43— render whatever fields the beacon carries; em-dash for absent ones.LNbits side
Acceptance
d="bitspire-config:…".dca_machinesrow pointing at an npub not in any operator's published roster refuses to record settlements.Reference
NIP-78 spec:
~/dev/nostr-protocol/nips/78.md.Adjacent upstream:
aiolabs/lamassu-next#43(richer kind-30078 beacon).Design doc:
docs/security-pathway-v1.md§5.1, §6.S4.Shipped on
v2-bitspireat commit131ff92. Builds on the canonical-vocabulary commitd717a6efrom earlier today.Implementation:
nostr_publish.py:publish_machine_config(machine)— per-machine kind:30078 withd="bitspire-config:<machine_id>"+["p", atm_npub]tag.publish_fleet_roster(operator_user_id)— aggregate kind:30078 withd="bitspire-fleet"+ one["p", atm_npub]tag per active machine.tombstone_machine_config(operator, machine_id, atm_npub)— replaceable kind:30078 withcontent.deleted=trueon delete (NIP-09-friendly tombstone pattern, survives non-deletion-compliant relays).views_api.api_create_machine/api_update_machine/api_delete_machine— publish + roster-refresh on every CRUD mutation. Re-publishing replaceable events is idempotent.Publish path: direct in-process singleton import (
from nostrclient.router import nostr_client; nostr_client.relay_manager.publish_message(...)). Bypasses the private WebSocket entirely after the pre-flight survey found no existing in-process consumer of the encrypted ws_id endpoint. Cross-extension guard pattern matches lnbits core'snostrmarket.services/nostrrelay.crudimports.Soft-failure model:
Acceptance status:
content.deleted=true) + roster refreshes without the machine.dca_machinesrow pointing at an npub not in any operator's published roster refuses to record settlements. Deferred to S6 (lnbits#14 Item 3) — this is the LNbits-side roster-gating in the nostr-transport handler. satmachineadmin's job here is to publish the roster; the consumer side lives upstream.(author_pubkey, d-tag)).Cross-codebase implications for future ATM-side consumption (lamassu-next):
When the ATM-side machine app (per
aiolabs/lamassu-next#42/ fleet management) wants to learn its operator's config, it should:{"kinds": [30078], "#p": ["<my_atm_npub>"]}on the configured relays.(author_pubkey == known_operator_pubkey)to confirm the config came from the operator we expect.contentJSON forname,location,fiat_code,is_active. Ifcontent.deleted == truetreat as "operator removed me — exit gracefully."Not blocking for this issue; flagging so the bitspire session has the consumer spec when they pick this up.
Closing.