LAB_BUD07.md raw

BUD-07 Implementation Lab

Reference implementation of Cashu micropayments for Blossom blob storage.

Overview

BUD-07 enables content monetization on Blossom servers using Cashu ecash tokens. Artists upload media with payment metadata, and listeners pay with Cashu tokens to access content.

Repository: github.com/musiquay/musiquay (lab/ directory)

Components

ComponentDescription
Bitcoin CorePrivate regtest chain for testing
LNDTwo Lightning nodes for payment channel
GonutsCashu mint (forked for LND 0.21+ compatibility)
OrlyNostr relay with BUD-07 Blossom payment gating

Payment Modes

Required Payment (MinSats > 0)

Artist specifies minimum payment. Access blocked without valid token.

# Upload with payment requirement
curl -X PUT http://localhost:10547/upload \
  -H "X-Artist-Pubkey: npub1..." \
  -H "X-Min-Sats: 100" \
  -H "X-Mint-Url: http://mint.example.com" \
  --data-binary @song.mp3

# Download without token → 402 Payment Required
curl http://localhost:10547/blossom/<sha256>

# Download with valid Cashu token → 200 OK
curl -H "X-Cashu: cashuB..." http://localhost:10547/blossom/<sha256>

Free with Optional Payment (MinSats = 0)

Content is freely accessible, but voluntary payments are accepted and forwarded to the artist.

# Upload as free content
curl -X PUT http://localhost:10547/upload \
  -H "X-Artist-Pubkey: npub1..." \
  -H "X-Min-Sats: 0" \
  -H "X-Mint-Url: http://mint.example.com" \
  --data-binary @song.mp3

# Download without token → 200 OK (free access)
curl http://localhost:10547/blossom/<sha256>

# Download with voluntary tip → 200 OK (token forwarded to artist)
curl -H "X-Cashu: cashuB..." http://localhost:10547/blossom/<sha256>

Payment Flow

┌──────────┐     1. Mint tokens     ┌──────────┐
│ Listener │ ──────────────────────▶│  Mint    │
│ (Wallet) │ ◀───────────────────── │ (Cashu)  │
└────┬─────┘     Lightning invoice  └──────────┘
     │
     │ 2. Request blob with X-Cashu token
     ▼
┌──────────┐     3. Validate token   ┌──────────┐
│ Blossom  │ ───────────────────────▶│  Mint    │
│ Server   │ ◀────────────────────── │          │
└────┬─────┘     Swap/burn proofs    └──────────┘
     │
     │ 4. Return content
     ▼
┌──────────┐
│ Listener │
└──────────┘

Security Features

Minimum Payment Unit

The minimum payment is 1 satoshi:

Quick Start

# Clone the lab
git clone git@github.com:musiquay/musiquay.git
cd musiquay/lab
git submodule update --init

# Enter devbox and build
devbox shell
just build-all

# Start everything
just start-all

# Run BUD-07 tests
just test-bud07

Clean Slate Timing

From empty environment to working stack:

Future Work

Nostr Play Events

Play reports (kind 3221) publish auditable play counts to the Nostr network - full spec in FEATURES_MUSIC.md:

Prototype Delta

The current orly BUD-07 implementation (pay-per-request token gating) predates the access model in FEATURES_ACCESS.md. The gap, in order of priority:

  1. Artist payout is broken. swapAtMint discards the swap response and blinding scalars, so the listener's proofs are burned and the new proofs are unclaimable; the artist is never paid. Must be fixed before any sale is real.
  2. Entitlement replaces per-request payment. Paid downloads should check access filter membership (kind 32217) instead of demanding a fresh X-Cashu token per request. Re-downloads must be free for entitled buyers.
  3. Delivery receipts. The host should issue kind 32218 receipts (NIP-59 wrapped, to buyer and publisher) on every serve, and publish host aggregates (32219) per period.
  4. Blind signatures. The host should blind-sign paid play statements with BDHKE so listeners can publish certified, unlinkable play reports.
  5. NUT-24 compliance. The 402 X-Cashu challenge is currently bespoke JSON; it should be a NUT-24 payment request, which requires NUT-18/NUT-26 creqA/creqB encoding. NUT-24 stays for rental stream buckets; ownership purchases move to the kind 3222 order in FEATURES_ACCESS.md. Note that the gonuts wallet does not implement NUT-18/26 today, so a client cannot yet complete the 402 handshake without adding it.
  6. Replay state. The in-memory seenTokens map is never pruned and not persisted; the mint is the real replay backstop, but the local map should be bounded.
  7. Free-with-tip mode. Upload never writes a MinSats = 0 marker, so the documented optional-tip path is unreachable. The spec now defines tips explicitly: a tip is a payment leg recorded on the delivery receipt (amount_sats plus token hashes in paid), and it never creates or changes a filter entry — an asset is free if and only if no filter event exists for it. Implement the marker so the path is reachable.

Related Documentation