FEATURES_ACCESS.md raw

Musiquay - Feature: Paid Access and Certified Delivery

Access is a signed filter, not a receipt per buyer. Payment buys membership; the filter gates the bytes; deeds record what was served.

Overview

Musiquay's paid-access layer answers three separate questions, and keeps them separate:

  1. Who may download or stream a paid asset? - a signed probabilistic filter published by the asset owner (kind 32217).
  2. What was actually served, and what was paid? - private, host-signed delivery receipts (kind 32218).
  3. What are the public totals? - host aggregates (kind 32219) and publisher rollups (kind 32220).

The design deliberately avoids per-buyer entitlement events. One addressable filter per asset (per tier) holds every authorized pubkey, backed by a Blossom blob so the filter can grow to any size while the event stays tiny. Payment proves a purchase once; membership in the filter grants unlimited re-downloads and streaming, like owning a record rather than renting a stream.

This is the Bandcamp model - pay once, re-download forever, every encoding - rebuilt on Nostr, Blossom, and Cashu, with the trust made explicit instead of entrusted to a platform.

Core Concepts

Access is a Filter, Not a List of Records

A paid asset is gated by a probabilistic membership filter (Bloom) containing the pubkeys of everyone entitled to it. The filter is signed by the asset owner and published as an addressable event. Any host that agrees to the terms can fetch the filter, verify the signature, and gate downloads without contacting the owner.

Free Assets Have No Filter

An asset is free if and only if no access filter exists for it. Free assets are served by open GET, with no entitlement check. Hosts still issue delivery receipts so listen counts remain certifiable. A priced asset (price tag present) MUST have a filter; if it does not, hosts treat it as free - the filter, not the price tag, is what gates.

Receipts Are Deeds

A delivery receipt is a private, host-signed document recording one serve: which asset, which blob, which pubkey, how many bytes, over what time window, for what payment. It is a deed - one signature from the serving host - not a contract, so there is no countersignature. Receipts are encrypted to the buyer and to the publisher, and delivered only over authenticated channels. They never appear in public feeds.

Ownership and Rental

Two payment modes coexist:

A host MAY separately charge for bytes served (its own hosting policy); that is orthogonal to entitlement and lives outside this spec.

Retailers Are Channels

A retailer does not hold the asset and does not take a cut of the owner's fee. A retailer sells access to its channel - storefront, curation, discovery, community, and the delivery rails it operates. Two independent payments exist:

Nothing is free to run: someone pays for storage and delivery, whether through a subscription (a "paywall" in the relay sense), a per-asset fee, or a wholesale arrangement. The protocol does not mandate any of these, but it does not pretend the rails cost nothing.

Subscription Filters

A retailer's subscription is a time-boxed membership set, represented with the same filter machinery: kind 32217, d = "sub:<retailer-pubkey>:<channel-d>", authored by the retailer, containing the pubkeys of currently subscribed members. The retailer republishes it each period (or on renewal/lapse), so membership in the latest version is the current subscriber set. No per-subscriber event is needed, and lapse is simply removal at the next rebuild.

A subscription filter MUST carry a catalog tag - a list of covered asset addresses - or the retailer MUST publish an addressable catalog list the filter's d resolves to. The retailer's catalog is authoritative for what a subscription covers; the owner's retailer tag on a fan filter is authorization to sell, not coverage. One source of truth avoids a host disagreeing with itself about what a subscriber may stream.

Data Model

Access Filter Event (Kind 32217, Addressable)

The filter event carries parameters and a pointer to a Blossom blob containing the filter itself.

{
  "kind": 32217,
  "pubkey": "<asset-owner-pubkey>",
  "content": "",
  "tags": [
    ["d", "fan:32210:<publisher-pubkey>:<track-d-tag>"],
    ["filter", "bloom"],
    ["fp", "1e-9"],
    ["n", "1234"],
    ["k", "30"],
    ["x", "<filter-blob-sha256>"],
    ["server", "https://blossom.musiquay.com"],
    ["mint", "https://mint.example.com"],
    ["retailer", "<retailer-pubkey>"],
    ["expiration", "1799999999"]
  ]
}

Design notes:

Filter blob format: a short header followed by the bit array, so the hash binds everything:

magic "MQF1" | version | asset address | fp | n | k | bit array

The blob lives on Blossom and mirrors like any other blob (BUD-04). Hosts SHOULD delete the superseded filter blob (BUD-12 DELETE) once the replacement event is observed; superseded filters have no archival value.

Tier Naming

Tierd tagAuthored byContains
Fanfan:<asset-address>Asset ownerDirect buyers
Retailretail:<asset-address>RetailerThe retailer's per-asset buyers
Subsub:<retailer-pubkey>:<channel-d>RetailerActive channel subscribers

The owner's fan filter carries retailer tags. There is no separate retailer filter authored by the owner - the retailer authors their own per-asset and subscription filters.

Minimum Price

The owner declares a minimum fee on the asset event itself:

["price", "500", "sat"]

This fee is the owner's revenue and buys ownership. It is paid to the owner directly, whether the buyer arrives through the owner or through a retailer. The retailer's revenue is separate (a subscription), so no margin is derived and no protocol field is needed for one.

Purchase Order (Kind 3222)

A purchase is a message, not an implicit side effect of a payment. The buyer seals an order rumor with NIP-59 and wraps it to the seller; the seller's sales service unwraps it, validates the payment, and adds the buyer to the filter.

{
  "kind": 3222,
  "pubkey": "<buyer-pubkey>",
  "created_at": 1758130000,
  "content": "",
  "tags": [
    ["a", "32210:<owner-pubkey>:<track-d-tag>", "wss://relay.musiquay.com"],
    ["p", "<seller-pubkey>"],
    ["tier", "fan"],
    ["price", "500", "sat"],
    ["payment", "cashu"],
    ["mint", "https://mint.example.com"],
    ["token", "cashuB..."]
  ]
}

Design notes:

Delivery Receipt (Kind 32218)

A receipt is an inner document carried in a NIP-59 gift wrap: the wrap hides sender and recipient from relays, the inner seal is signed by the serving host, and the payload is encrypted to the recipient. One wrap is sent to the buyer, one to the publisher.

Inner receipt payload:

{
  "kind": 32218,
  "asset": "32210:<publisher-pubkey>:<track-d-tag>",
  "buyer": "<buyer-pubkey>",
  "blob": "<blob-sha256>",
  "server": "https://blossom.musiquay.com",
  "mode": "download",
  "start": "1758130000",
  "end": "1758130000",
  "bytes": "48216387",
  "amount_sats": "0",
  "paid": []
}

Design notes:

Play Reports (Kind 3221)

Play reports remain the listener-side public signal, separate from private receipts:

Host Aggregate (Kind 32219, Public)

Hosts publish signed totals per asset and period. The signature is the same host key that signed the underlying receipts, so counts are attributable.

{
  "kind": 32219,
  "pubkey": "<serving-host-pubkey>",
  "content": "",
  "tags": [
    ["a", "32210:<publisher-pubkey>:<track-d-tag>"],
    ["start", "1758130000"],
    ["end", "1760808400"],
    ["plays", "48213"],
    ["downloads", "3921"],
    ["bytes", "21474836480"],
    ["sats", "1510000"],
    ["listeners", "11054", "approx"]
  ]
}

Publisher Rollup (Kind 32220, Public)

The publisher consolidates host aggregates without losing authenticity by referencing them rather than restating them. The rollup is publisher-signed; each leaf remains host-signed.

{
  "kind": 32220,
  "pubkey": "<publisher-pubkey>",
  "content": "",
  "tags": [
    ["a", "32210:<publisher-pubkey>:<track-d-tag>"],
    ["start", "1758130000"],
    ["end", "1760808400"],
    ["e", "<host-aggregate-id>", "", "<host-pubkey>"],
    ["e", "<host-aggregate-id-2>", "", "<host-2-pubkey>"],
    ["receipts", "52134"],
    ["plays", "55123"],
    ["downloads", "4120"],
    ["bytes", "22817013760"],
    ["sats", "1730000"]
  ]
}

The Retailer Vector

Revenue Split

Authorization

if buyer in owner fan filter (d = fan:<asset>)               -> entitled (ownership, direct)
else for each ["retailer", R] on the owner filter:
    if buyer in R's per-asset filter (d = retail:<asset>)    -> entitled (ownership, via R)
    if buyer in R's subscription filter
       and asset is in R's covered catalog                   -> entitled (subscription access)
else                                                         -> 402 / not entitled

Single-signer per level: the owner signs the fan filter and the retailer list; each retailer signs their own filters. The owner can go offline after onboarding a retailer.

Sale Flow Through a Retailer

Ownership purchase (forward model):

  1. Buyer sends a kind 3222 purchase order, wrapped to the retailer, with tier = retail.
  2. The retailer re-wraps the order to the owner (or tells the buyer to send it directly). The buyer pays the owner's fee; the retailer takes no cut.
  3. The owner's sales service verifies the payment and adds the buyer's pubkey to the owner's fan filter.
  4. The buyer downloads from any cooperating host. Membership is the ticket; no payment token is presented for the download itself. The buyer does identify with a BUD-11 authorization token so the host can test membership.

Ownership purchase (on-behalf model):

  1. Buyer sends a kind 3222 order to the retailer with tier = retail.
  2. The retailer swaps the token at the mint to outputs P2PK-locked to the owner (not itself) - forwarding the exact amount - and adds the buyer to its per-asset filter d = retail:<asset-address>.
  3. The buyer downloads; hosts accept the retailer's filter because the retailer is listed on the owner's fan filter.

The on-behalf model is a trust relationship: the retailer holds a token locked to the owner and is trusted to forward it. The protocol cannot enforce forwarding. Where that matters, use the forward model or the owner's own checkout.

Subscription:

  1. Buyer sends a kind 3222 order to the retailer with tier = sub, the channel, and the period.
  2. The retailer settles the payment, then rebuilds its subscription filter with the active subscriber set (new blob, replace event, delete old blob).
  3. While subscribed, the buyer streams the retailer's covered catalog. If the subscription lapses, the next rebuild drops them.

Direct sales are the same ownership flow without the retailer: the owner's service adds the buyer to the fan filter. Subscription access is always a retailer-provided channel service; the owner's fee is always the ownership path.

Payment Modes

Ownership (Forever)

One payment of the owner's fee. The buyer is added to the fan filter. Downloads and streams are then free of per-request payment. Re-downloads are unlimited and every encoding on the track is covered - one entitlement, all formats, exactly like Bandcamp.

Subscription (Per Time, Channel)

A recurring payment to a retailer for its channel. The retailer maintains a subscription filter and the buyer streams the covered catalog while subscribed. This is the "paywall" in its natural sense: a subscription to a service, not a toll on each asset. It funds the delivery rails and the retailer's curation.

Rental (Per Time, Host)

A host that is not running a subscription channel may still sell time directly:

  1. Client requests a stream; host replies 402 with a NUT-24 X-Cashu payment request for one bucket (bucket length is host policy, e.g. 10 minutes).
  2. Client pays; host opens an access window of that length.
  3. Host streams within the window and issues a stream receipt at settlement with start/end/amount_sats.
  4. If the listener continues, the client buys the next bucket.

No escrow, no per-second micropayments, no change handling. The window is a bucket; the receipt is the deed.

Bytes Fee (Host Policy)

A host MAY charge for bytes served independently of entitlement - its hosting economics. That charge is a host policy expressed through the same NUT-24 mechanism; it does not affect filter membership.

Tips

A buyer may always pay more than the fee, and a listener may tip a free asset. A tip is a payment leg, not a second entitlement mechanism:

Blind Signatures (BDHKE)

Paid play reports reuse Cashu's NUT-00 BDHKE blind signature scheme on secp256k1. The host holds a signing key k, public K = k*G. The listener holds a statement m and blinds Y = hash_to_curve(m) with a secret scalar r to produce B_ = Y + r*G; the host returns the blinded signature C_ = k*B_ without seeing m; the listener unblinds to C = C_ - r*K = k*Y.

Verification requires a DLEQ proof. The check C == K*H(m) is not computable on secp256k1 - it is a pairing equation, and secp256k1 has no pairing. A third party can only verify a blind signature from the NUT-12 DLEQ proof (e, s) that accompanies it, which proves log_G(K) == log_Y(C) without revealing k. Listeners therefore publish the proof alongside the statement, and play reports carry a dleq tag (see FEATURES_MUSIC.md). Without it, a play report is unverifiable by anyone but the host.

Blind signing is an oracle. A host that signs H(statement) without seeing the statement can be made to certify false statements, so the statement is restricted to facts the host itself observed - asset, blob hash, and time window. Money is accounted by receipts and aggregates, not by play reports, so tier and amount are deliberately not part of the signed statement.

Properties that matter:

Host Behavior

Gating Check Order

0. Resolve the requested blob to an asset address
   (sha256 -> asset, from the host's ingest index or a #x query)
1. No filter event for the asset (free asset)          -> serve
2. No requester identity on a gated asset             -> 401 + BUD-11 challenge
3. Buyer pubkey in owner fan filter                    -> serve, receipt (ownership)
4. Buyer pubkey in any authorized retailer's per-asset filter -> serve, receipt (ownership)
5. Buyer pubkey in a retailer's subscription filter
   and asset is in that retailer's covered catalog     -> serve, receipt (subscription)
6. Otherwise                                           -> 402 + NUT-24 payment request

Priority Downloads

A host prioritizes buyers who have not yet downloaded an asset: first acquisition is more valuable to the network than a repeat fetch. To make that decision cheaply, the host keeps a local served-filter - a low-false-positive Bloom filter of (asset, buyer) pairs already served. This filter is host-local, never published, and its false positives are benign (a repeat downloader occasionally gets priority). No new event kind is involved.

Filter Federation

Any host that accepts the filter terms can serve the asset: fetch the addressable filter event, verify the owner's signature, gate by membership. This is how hosting scales without a platform - the filter is the shared contract, and the bytes are content-addressed and mirrorable.

Superseded Filter Cleanup

When a host observes a new version of a filter event, it SHOULD delete the superseded blob (BUD-12) to keep Blossom storage bounded. Old filters have no archival value once replaced.

NIP Strategy

ConceptNIP StatusNotes
Access filter (32217)Custom kindNo standard defines a signed probabilistic access set; NIP-63 kind 1163 (unmerged) is the nearest structural precedent.
Delivery receipt (32218)Custom kind, NIP-59 deliveryPrivate deed; carried in gift wraps, one signature from the host.
Host aggregate (32219)Custom kindPublic, host-signed totals.
Publisher rollup (32220)Custom kindPublic, publisher-signed, references host aggregates by e tag.
Purchase order (3222)Custom kind, NIP-59 deliveryPrivate buyer-to-seller order carrying intent plus payment proof. Never published.
Play report (3221)Custom kindListener-side; BDHKE blind signature for paid plays.
Blind signaturesCashu NUT-00 BDHKE + NUT-12 DLEQReused directly; the DLEQ proof is what makes a play report verifiable by third parties.
Payment requestsNUT-24 / BUD-07HTTP 402 + X-Cashu; buckets for rental. Requires NUT-18/26 creqA/creqB client support.
Media authorizationBUD-11 / kind 24242How a host learns a requester's pubkey for a Blossom fetch. Not NIP-42.
Lightning payment legNIP-57Zap request/receipt for ownership paid in sats.
Blob storage and mirroringNIP-B7 / BUD-01/04/12Filter blobs and audio blobs.
Private deliveryNIP-59Gift wrap for receipts.
Addressable eventsNIP-01Filters are parameterized replaceable, d tag.
AddressingNIP-19naddr for filters and assets.