The problem
Stealth addresses are great, but for an ongoing relationship — a vendor, a donation address, a paying client — the sender needs your stealth address each time, or has to maintain a contact channel. BIP47 fixes this with a one-time notification transaction that establishes a permanent shared secret.
How it works
- Alice generates a single payment code she can publish anywhere
- Bob sends a tiny notification TX to Alice's notification address — it carries Bob's payment code, blinded with an ECDH shared secret, in OP_RETURN
- Now Bob (or anyone with the same payment code relationship) can derive infinite unique stealth addresses for Alice without further interaction
- Each subsequent payment is a fresh stealth output. The notification TX itself reveals only that some notification happened — not the payment code relationship
Step-by-step
-
Get your payment code
$ MaryJaneCoind getpaymentcode { "payment_code": "PM8TJJvP5XPvcn4oy75XBydTpZrZxtKSYBqRtj7WuVywEYaWJmWcnpPyCi2AZVhbtZudqu4Go2fGQeFdeyRFtqtwkjRkmFPPWFDkLYvtpGPBeipi8nG4", "notification_address": "MPNLrBVBYYWKpSNdKJ3ABipZEUC8aDvsYZ", "scan_pubkey": "0209683bcb222d6f8f8d0c9d13432a...", "spend_pubkey": "0328c613cd05ba604e049acfc5cdf1..." }Publish the
payment_codestring anywhere — Twitter bio, website, QR code, business card. -
Sender: establish a payment channel
To send Alice money the first time, Bob runs:
$ MaryJaneCoind sendtonotify "PM8TJJvP5XP..." { "txid": "abc123...", "notification_address": "MPNLrBVBYY...", "status": "notification_sent" } -
Receiver: scan for incoming notifications
$ MaryJaneCoind scannotifications 2 # found 2 inbound payment-code notifications -
Send unlimited payments — no more interaction
Once the channel exists, Bob can derive any address index for Alice:
$ MaryJaneCoind deriveaddress "PM8TJJvP5XP..." 0 {"index": 0, "address": "MQx7k9f..."} $ MaryJaneCoind deriveaddress "PM8TJJvP5XP..." 1 {"index": 1, "address": "MRy8l0g..."}Each address is a fresh DKSAP destination — same privacy as standard stealth.
-
List all your payment relationships
$ MaryJaneCoind listpaymentcodes [ { "payment_code": "PM8TJKx...", "label": "Bob", "notification_sent": true, "notification_received": true, "next_send_index": 3, "next_recv_index": 1 } ]
Notification blinding
The 80-byte payment-code payload is XOR-masked with ECDH(sender_scan_priv, receiver_scan_pub). Only the recipient can unblind it. Observers see a notification TX exists, but cannot identify which payment code it carries — preventing attackers from building a directory of "who knows whose code".