Draft spec: BCH payment address from a Nostr npub (Cashonize #405)

Cashonize issue #405 asks for a way to pay a Nostr identity (npub) from a BCH wallet, and says it would follow “a CHIP or NIP that defines how a BCH payment target is found from an npub, implemented by at least one other wallet”. I wrote a short draft spec for that, plus a working demo. Feedback wanted before anyone builds on it.

Draft spec: Draft: BCH payment address from a Nostr npub

The short version:

  • The npub owner publishes a replaceable Nostr event (kind 30078) that holds their BCH address (a token-aware z-address).
  • The binding is signed twice, by the Nostr key and by the BCH key, so a relay or a third party can’t swap the address.
  • A wallet resolves npub → address through a relay the user picks, checks both signatures, and shows the address before the user pays.
  • Privacy: this publishes one public address. The spec lists an optional RPA paycode field for anyone who doesn’t want address reuse.

Live demo (publish and resolve): https://bch-nostr-id.surge.sh

Open questions: is kind 30078 (d=bch-nostr-bind) acceptable, or would a dedicated NIP kind be better? Should the kind-0 profile fallback exist at all? Which relays should wallets query by default?

All of it is CC0.

@MathieuG, this follows the directions you listed in Cashonize #405, so your view on which one fits best would help a lot.

1 Like

The way we resolve this in bchat (alpha, https://bchat.cash) is by deriving the npub from your wallet keys. No separate NIP is required, it’s the exact same secpk256k curve, so all you have to do is decode the npub and it would match the public key of the derived address.

There’s some fiddly bit about compression that I don’t remember the details of offhand, but there’s plenty of reference code out there including this repository from Bastian Carmichael

I suppose it’s probably worth mentioning that I’m currently working on a way to put together a CashAccount, RPA cashcode, and nostr identity but I need to work out some details first before I’m ready to share the work.

Thanks @kzKallisti, that’s a good point and much simpler when the Nostr key and the wallet key are the same. I’d like to add it to the draft as a second method next to the signed event.

The compression detail, as I understand it: an npub is the 32-byte x-only key from BIP340, so there are two points with that x, with prefixes 02 and 03. BIP340 always means the even-y one, so the canonical derived address is P2PKH(hash160(02||x)). If the wallet’s private key d gives an odd-y point, Nostr signers silently sign with n−d, and the 02 address is spendable with n−d but not with d. A wallet that derives from the same seed has to normalise to even y (or watch both addresses), or the payment lands somewhere the wallet doesn’t look. Is that the same issue you ran into in bchat?

Where I think the signed-event method still helps:

  1. Most npubs today live in browser extensions, bunkers or other Nostr apps, not in a BCH wallet. If someone pays the derived address of one of those npubs, the funds can only be recovered by importing the nsec into a BCH wallet. That’s recoverable, but the payee won’t expect it.
  2. The derived address links the whole Nostr identity to one reused BCH key. Some users will want a separate wallet or an RPA paycode instead.

So my proposal for the draft:

  • Method A (derived): if the npub owner says their Nostr key is also their BCH key, pay P2PKH(02||x). The owner signals this with a binding event whose address equals the derived address (it then self-verifies, since one key covers both signatures), or with a simple tag.
  • Method B (bound): signed binding event pointing at any address or paycode, as now.
  • With no event at all, wallets should warn before paying the derived address instead of doing it silently.

Very interested in the CashAccount + RPA + Nostr work when you’re ready. The draft already has an optional RPA paycode field, and I’m happy to rename or restructure it to match what you build, so there aren’t two incompatible formats.