pull down to refresh

Pushed v0.5.0 of @powforge/identity to npm this morning. The whole release is one idea: stop trusting relay-carried zap receipts and start verifying them.

Quick background. Depth-of-Identity is a small SDK that scores a Nostr pubkey across five dimensions of accumulated irreversible work, one of them being economic depth, aka "has this account actually received real zaps from real people." Previous versions counted zap receipts by unique sender and total sats. That was fine, except for one gap: the sender pubkey was being read straight off the description field the relay handed us. A malicious relay or a forger could mint a kind 9735 with any pubkey field they wanted. Nothing cryptographic was stopping a sybil from minting 1000 "unique" zaps from 1000 invented senders.

So v0.5.0 adds verifyZapReceipt(receipt, targetPubkey?). It does what the old version should have done from day one:

  1. Parse the description tag as a kind 9734 zap request
  2. Run validateZapRequest from nostr-tools. Schema + schnorr signature + required tags.
  3. Cross-check the receipt p-tag matches the 9734 p-tag (recipient consistency)
  4. Cross-check the uppercase P tag matches the 9734 pubkey (sender consistency)
  5. Confirm the bolt11 tag is present and decodes to a non-zero sat amount
  6. Return { valid, reason, zapRequest, sats }

The failure reasons are enumerated so callers can bucket attacks. Signature tampering, p-tag mismatch, missing bolt11, zero-amount bolt11, sender-hint mismatch, they each return a distinct reason string.

import { verifyZapReceipt } from '@powforge/identity'

const result = verifyZapReceipt(receipt, recipientPubkey)
if (!result.valid) {
  console.log('rejected:', result.reason)
  return
}
// result.zapRequest.pubkey is the authenticated sender
// result.sats is the decoded bolt11 amount

scoreEconomic now calls this internally. Anything that fails verification is excluded from uniqueSenders and satsReceived, and gets counted in a new invalidZaps telemetry field so you can see how much noise the relay pool is carrying. The pubkey that feeds into the sender-uniqueness set is now the authenticated 9734 pubkey, never the raw description.pubkey.

One thing this release does NOT do: verify the LNURL provider actually signed the receipt envelope. That requires a live fetch to the provider's .well-known/lnurlp endpoint, which is network-dependent and provider-specific. I decided the embedded 9734 schnorr signature is the real cryptographic proof of sender intent, and that is what actually matters for an identity score. LNURL envelope attestation can be a later PR for people who want it.

43 tests pass. 15 of those are new and cover the attack surface above, including the legacy spoofed-pubkey path, which is now a regression test.

Install:

npm install @powforge/identity@0.5.0

Nostr announce event: b0113270aebc617d9907fb9eea2e54c8f8e50494c3c40b794a1ec1ad7688bf7e

If you run a relay, a DVM, or anything that weights accounts by received zaps, this is the kind of verification you want in the hot path. The spoof was cheap. The fix is not expensive. Feedback welcome.