feat(zap): validate zap receipts before counting#12
Merged
Conversation
Verify each kind-9735 zap receipt before it inflates stats, notifications, or
the supporters list. Two NIP-57 checks (Appendix F / E):
1. Issuer: the receipt must be signed by the nostrPubkey advertised by the
recipient's LNURL pay endpoint. Lenient when the endpoint or its
nostrPubkey can't be resolved (can't prove forgery).
2. Preimage: when present, sha256(preimage) must equal the bolt11 payment hash.
lightning.validateZapReceipt() caches the per-recipient nostrPubkey lookup
(DataLoader). Wired into stuff-stats (addZapByEvent now async-validates, then
notifies itself) and ZapNotification (gates render on validation). The
supporters query drops its alby-author hardcode in favor of real validation.
Ported from upstream Jumble b2f4714c, adapted to jank's profileFetcher /
eventCache / relayListService. Adds a unit test (no upstream equivalent).
Live-verified: Wallet of Satoshi, Rizful, and yakihonne all sign with their
advertised nostrPubkey (issuer == endpoint nostrPubkey), so legit zaps pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying jank with
|
| Latest commit: |
9dd401a
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://a25e3fd6.jank-4ii.pages.dev |
| Branch Preview URL: | https://feat-validate-zap-receipts.jank-4ii.pages.dev |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Ports zap-receipt validation from upstream Jumble (
b2f4714c), adapted to jank's services. Forged kind-9735 receipts no longer inflate stats, notifications, or the supporters list.New
lightning.validateZapReceipt(receipt)runs two NIP-57 checks:pubkeymust equal thenostrPubkeyadvertised by the recipient's LNURL-pay endpoint. Lenient when the endpoint or itsnostrPubkeycan't be resolved — we can't prove forgery, so we don't drop the zap (no over-filtering).preimagetag is present,sha256(preimage)must equal the bolt11 payment hash. The tag is optional, so absence isn't a failure.The per-recipient
nostrPubkeylookup is cached viaDataLoader.Wiring
stuff-stats.service—addZapByEventis now async: it validates, thenaddZapnotifies subscribers itself (the async check can't feed the batch's end-of-loop notify).ZapNotification— gates render on an async validation result.fetchRecentSupporters— drops the hardcodedauthors: [alby]filter in favor of real per-receipt validation (matches upstream).jank adaptations vs upstream
profileFetcher.fetchProfile/eventCache.fetchEvents/relayListService.fetchRelayList(not upstream'sclient.fetch*).lightning.validate-zap-receipt.spec.ts) — upstream shipped this with no test.Known limitation (documented in code)
NIP-57 Appendix F also asks that the bolt11 invoice amount equal the zap request's
amounttag. That cross-check is not performed (matches upstream); the issuer check is the primary forgery defense. Flagging as a possible hardening follow-up.Gates
Gate 1 (NIP-57): ✅ verified against the spec itself — Appendix F (issuer = LNURL
nostrPubkey) + Appendix E (optional preimage→payment-hash).Gate 2 (signer/identity): N/A — read/validate only; nothing is signed.
Gate 3 (realistic-relay): ✅ premise proven on live data. Fetched real kind-9735 receipts via
nakand resolved each recipient's LNURL endpoint:Real providers sign with their advertised
nostrPubkey, so legitimate zaps pass validation; the lenient-on-unresolvable fallback protects the rest.In-app smoke (please confirm before merge)
Verification
npm run build✅ (real type check)npm test✅ 862 passed / 1 skipped (incl. 5 new validation tests)🤖 Generated with Claude Code