S5 — Persist sender_pubkey on Payment.extra (LNbits-side) #19
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 gap G5 (sender_pubkey not persisted by the nostr-transport dispatcher).
Primary work is in
aiolabs/lnbitsnostr-transport. This issue tracks both the LNbits-side fix and the satmachineadmin-side consumption.Scope changes since first filing
aiolabs/lnbits#14) confirmedauth.py:69-73constructs auto-accounts withprvkey=None, falling throughcrud/users.py:36-45's existing "Nostr login user" branch. Server never holds the key. No work needed here.Problem (G5)
When LNbits' nostr-transport dispatcher creates a
Paymentrow from a kind-21000 RPC, the originating event'spubkey(the ATM's npub, the client's npub) is not stamped ontoPayment.extra. The dispatcher captures it on the request object (dispatcher.py:72) but_handle_create_invoice(line 161) passesextra=body.get("extra")— the sender is dropped before persistence.Consequence: after the fact we cannot tell which Nostr identity triggered a payment. No audit trail for "which ATM asked for this invoice." For our threat model this is the missing link that turns delivery into attribution.
Fix
In the dispatcher's Payment-creating handlers (
_handle_create_invoiceand any future siblings), merge the originating event's pubkey intoPayment.extrabefore persistence:Use a
nostr_prefix to avoid colliding with any consumer-set keys (bitSpire setssource,net_sats, etc.).satmachineadmin-side consumption
bitspire.parse_settlementreadsextra["nostr_sender_pubkey"]and cross-checks it against the resolved machine'smachine_npub. Mismatch ⇒ log + mark the settlementerroredwith a tamper-evident note + skip distribution.Note: until S4 (NIP-78 fleet roster, #18) lands, the cross-check is "wallet's account pubkey == sender pubkey" — which is a 1:1 mapping today. Once fleets exist, the check becomes "sender pubkey ∈ operator's fleet for this machine."
Acceptance criteria
dispatcher.py:_handle_create_invoiceappendsnostr_sender_pubkeyandnostr_event_idtoPayment.extrabefore persisting the row.bitspire.parse_settlementreads and cross-checksnostr_sender_pubkeyagainst the machine's wallet account pubkey.nostr_sender_pubkeythan the machine — satmachineadmin flags the row, doesn't distribute.Why this matters
Per the hook discussion in the security epic: LNbits commits to delivery of payment events, not to attribution. Persisting
sender_pubkeyis the minimum-cost way to give downstream consumers (us, future extensions) the attribution data point — without coupling LNbits to any particular consumer's trust model. The HMAC work (G2, split out) is the next layer on top: signing the metadata itself so post-write mutation is detectable. This issue only addresses writing the metadata in the first place.Merge blocker status
Per the nostr-transport branch audit: this is one of three blockers for upstream PR of the transport branch. The other two are S1 (#15, NIP-40 expiration) and ruff lint cleanup. Doing this now serves two goals: closes G5 in the security epic and unblocks the upstream PR.
Reference
Design doc:
docs/security-pathway-v1.md§5.1, §6.S5.LNbits primitive review: design doc §4.2 G5.
Audit findings: comment on
aiolabs/lnbits#14.S5 — sender_pubkey persistence + HMAC over Payment.extra key fields (LNbits-side)to S5 — Persist sender_pubkey on Payment.extra (LNbits-side)Closing as done — landed before the sprint started.
LNbits side (
aiolabs/lnbitsnostr-transport branch):4ce568d8 feat(nostr-transport): persist sender_pubkey + event_id on Payment.extra (G5 / S5)—dispatcher.py:_extra_with_nostr_attributionstampsnostr_sender_pubkey+nostr_event_idon Payment.extra for both_handle_create_invoiceand_handle_pay_invoice.satmachineadmin side (this repo,
v2-bitspire):9414a18 feat(v2): reject settlements that fail nostr attribution cross-check (S5 G5)—bitspire.assert_nostr_attribution()enforces sender_pubkey == machine.machine_npub attasks.py:91; mismatches insert asrejectedsettlements viacrud.create_settlement_idempotent.tests/test_nostr_attribution.pycovers happy / missing / mismatched / npub-vs-hex normalisation.Acceptance criteria status:
dispatcher._handle_create_invoiceappends both fields before persistence._handle_pay_invoice(future Payment-creating handler) does the same.bitspire.parse_settlementreads + cross-checks against the machine wallet's account pubkey.Gap G5 closed.