# 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. - **One event per asset per tier**, not one event per buyer. - **False positives are economically harmless.** A false positive grants one free download of one asset to one pubkey. At the spec-minimum false-positive rate of `1e-9`, grinding keypairs until one lands in the filter costs far more than the asset. Owners MAY choose a higher rate for very large filters. - **The filter is a popularity signal.** Its declared element count `n` and byte size are public - a digital gold record. Owners SHOULD publish accurate `n`. ### 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: - **Ownership** - pay once (at or above the owner's minimum price). The buyer is added to the filter. Download forever, any encoding, stream free. - **Rental** - pay per time bucket to stream without owning. The server grants an access window, streams within it, and settles with a receipt. Simplest accounting for clients: prepay a bucket, stream, repeat if desired. 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: - **Owner's fee** - per asset, paid to the owner. Buys ownership: permanent download access and membership in the owner's fan filter. - **Retailer's subscription** - recurring, paid to the retailer. Funds the channel. A subscription MAY also grant streaming access to the catalog the retailer carries, under a wholesale arrangement with the owners (the retailer is tagged on the owners' filters). 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::"`, 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. ```json { "kind": 32217, "pubkey": "", "content": "", "tags": [ ["d", "fan:32210::"], ["filter", "bloom"], ["fp", "1e-9"], ["n", "1234"], ["k", "30"], ["x", ""], ["server", "https://blossom.musiquay.com"], ["mint", "https://mint.example.com"], ["retailer", ""], ["expiration", "1799999999"] ] } ``` **Design notes:** - **`d` tag convention** makes lookup deterministic: the filter address is `32217:::`. A host needs only the asset address and the owner pubkey to find and verify the filter. - **`filter`, `fp`, `n`, `k`** declare the structure. The spec mandates `fp` no higher than `1e-6` and requires `k = round(-log2(fp))`. - **`x`** is the SHA-256 of the filter blob on Blossom. The blob is self-describing (see below), so the hash binds the parameters to the bytes. - **`server`** is a hint; clients fall back to the owner's NIP-B7 kind 10063 server list. - **`mint`** tags list the Cashu mints whose tokens the owner accepts for this asset, and MAY include `["mint", "lightning"]` if the owner accepts direct Lightning payment. The filter event is the entitlement contract, so it is the right place to publish the payment legs. - **`retailer`** tags list authorized retailer pubkeys (retailers are few; no filter needed). - **`expiration`** is optional. Absent means permanent, like a purchased record. - The event is **addressable**: every sale republishes it with a new blob and new `x`. Revocation is the same operation with the pubkey removed. **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 | Tier | `d` tag | Authored by | Contains | |------|---------|-------------|----------| | Fan | `fan:` | Asset owner | Direct buyers | | Retail | `retail:` | Retailer | The retailer's per-asset buyers | | Sub | `sub::` | Retailer | Active 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: ```json ["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. ```json { "kind": 3222, "pubkey": "", "created_at": 1758130000, "content": "", "tags": [ ["a", "32210::", "wss://relay.musiquay.com"], ["p", ""], ["tier", "fan"], ["price", "500", "sat"], ["payment", "cashu"], ["mint", "https://mint.example.com"], ["token", "cashuB..."] ] } ``` **Design notes:** - **Never published.** Like a delivery receipt, an order exists only inside NIP-59 wraps. It is a regular (immutable) kind so it can be reconstructed and archived without replacement semantics. - **One message.** The order carries the intent and the payment proof together, so the buyer needs no prior invoice round-trip when paying in Cashu. The token SHOULD be P2PK-locked (NUT-11) to the seller, so an intercepted order cannot be redirected. - **Tier.** `tier` is `fan` (ownership direct), `retail` (ownership through a retailer), or `sub` (channel subscription). Subscription orders add `["channel", ""]` and `["period", ""]`. - **Lightning leg.** For a Lightning payment the order's `payment` is `lightning` and the proof is the NIP-57 zap receipt (kind 9735) that the LNURL server publishes after the buyer pays a zap request (kind 9734) carrying the asset `a` tag. The buyer's pubkey comes from the zap request, so entitlement still follows from the payment. - **Validate before granting.** The sales service MUST check the seal author, the asset's author and current price, the mint, the amount, and the proof state, in that order, before touching the filter. ### 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: ```json { "kind": 32218, "asset": "32210::", "buyer": "", "blob": "", "server": "https://blossom.musiquay.com", "mode": "download", "start": "1758130000", "end": "1758130000", "bytes": "48216387", "amount_sats": "0", "paid": [] } ``` **Design notes:** - **One signature, the host's** - the NIP-59 seal is signed by the same host key that signs host aggregates. No countersignature. - **`mode`** is `download` or `stream`. For `stream`, `start`/`end` bound the session and `amount_sats` is the settled amount. - **`amount_sats`** is `0` for an entitled download (the purchase already happened) and for free assets; for a paid rental it is the settled amount. - **`paid`** lists the token hashes spent *at this serve* - populated for rental or bytes-fee serves, empty when access came from filter membership. It links the deed to a payment without revealing the token. - **Publisher copy** - the publisher archives every receipt for accounting. The publisher's archive is the durable record even if every relay drops the wraps. ### Play Reports (Kind 3221) Play reports remain the **listener-side** public signal, separate from private receipts: - A play report is authored by the streamer's key and references the asset, the encoding, and the time. - **Paid plays carry a blind signature** from the serving host, produced with Cashu's BDHKE (NUT-00). The statement covers only host-observed facts - the asset address, the blob hash, and the time window - and the listener publishes `(statement, C, host-pubkey, dleq)`. Verification is by the NUT-12 DLEQ proof, not by a pairing equation: `C == K * H(statement)` is not computable on secp256k1. The blind signature keeps the listener unlinkable to the session; the DLEQ proof keeps the play certified. Full signing and verification detail is in [FEATURES_MUSIC.md](./FEATURES_MUSIC.md) and [PROTOCOL_FLOWS.md](./PROTOCOL_FLOWS.md). - **Free plays** are self-attested, with no host signature - a scrobble, not a proof. - Hosts SHOULD NOT retain blind-signing requests; doing so would break unlinkability. ### 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. ```json { "kind": 32219, "pubkey": "", "content": "", "tags": [ ["a", "32210::"], ["start", "1758130000"], ["end", "1760808400"], ["plays", "48213"], ["downloads", "3921"], ["bytes", "21474836480"], ["sats", "1510000"], ["listeners", "11054", "approx"] ] } ``` - Periods are host-chosen. Aggregates are immutable; corrections are new events with a revised period. - `listeners` comes from the host's served-filter and is marked `approx` - the filter's false-positive floor makes it an estimate, and the tag says so. ### 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. ```json { "kind": 32220, "pubkey": "", "content": "", "tags": [ ["a", "32210::"], ["start", "1758130000"], ["end", "1760808400"], ["e", "", "", ""], ["e", "", "", ""], ["receipts", "52134"], ["plays", "55123"], ["downloads", "4120"], ["bytes", "22817013760"], ["sats", "1730000"] ] } ``` - The `e` tags are the authenticity chain: clients verify each host aggregate independently, then the publisher's rollup as a signed pointer-set plus the publisher's own receipt-archive totals (`receipts`). - Because the publisher archives receipts, their totals are deed-backed even when no host aggregate survives. Rollups can be recomputed and re-published at any time; signatures survive re-publication. - Relays MAY expire aggregates and rollups like any other data. The publisher's archive is the long-term record. ## The Retailer Vector ### Revenue Split - **Owner's fee** - `["price", "500", "sat"]` on the asset, paid to the owner. This is the content revenue and it is never shared with the retailer. - **Retailer's subscription** - the retailer's own recurring rate for its channel, paid to the retailer. This is the channel revenue. - **Tips:** a buyer may always pay the owner more than the fee; the excess is a fan contribution to the owner, exactly like Bandcamp. - A per-asset margin (retailer price above the owner's fee) remains a permitted alternative for retailers who prefer per-sale revenue, but it is not the default model - subscription keeps content revenue and channel revenue cleanly separated. ### Authorization - The owner's fan filter carries `["retailer", ""]` tags for each authorized retailer. - Each retailer publishes per-asset filters (`d = "retail:"`) for buyers who took ownership through them, and a subscription filter (`d = "sub::"`) for active subscribers. - A host checking a buyer runs: ``` if buyer in owner fan filter (d = fan:) -> entitled (ownership, direct) else for each ["retailer", R] on the owner filter: if buyer in R's per-asset filter (d = retail:) -> 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:`. 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: - For an ownership purchase, the excess over `price` is a tip to the owner and is carried in the same purchase order and token. - For a free asset, the tip is either an addition to the bytes-fee payment or a NIP-57 zap to the owner. It MUST NOT create or change a filter entry: an asset is free if and only if no filter event exists for it. - The receipt records the tip in `amount_sats` with `paid` listing the token hashes. The voluntary-payment path in the prototype maps onto this rule. ## 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](./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: - **Certified** - the host's signature proves the play was served, once the DLEQ proof is verified. - **Unlinkable** - the host never sees the statement, so it cannot tie the published play to a session or buyer. - **Reuses existing tooling** - BDHKE and DLEQ are already implemented in every Cashu stack, including gonuts. - **Free plays are exempt** - no host signature, self-attested only. - **Requests are not retained** - a host that logs blind-signing requests destroys the unlinkability property. ## 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 ``` - **Identity, then payment.** The requester identifies with a BUD-11 authorization token: a signed kind 24242 event with `t = get` and an `x` tag for the blob, sent as `Authorization: Nostr `. A gated asset with no identity is a `401`, not a `402`; `402` is the challenge for a known buyer who is not entitled (rental) or who owes a bytes fee. NIP-42 is relay authentication and has no part in Blossom requests. - **Filters are fetched once and cached**; a filter change is an addressable event replacement, so caches invalidate on the new event version. Hosts SHOULD verify the filter blob's SHA-256 against the event's `x` tag before trusting it. - **Fail closed.** A host that has a filter event for a priced asset but cannot parse the blob (unknown `filter` value or format version) MUST refuse to serve rather than serve ungated. An asset counts as free only when no filter event exists at all. - **Album-level access (proposed).** Audio lives on tracks, so an album with a `price` needs a rule. The proposed model is an album filter (`d = fan:`) that the host consults after resolving blob -> track -> album, when the track itself has no filter. This is recorded as an open decision in [PROTOCOL_FLOWS.md](./PROTOCOL_FLOWS.md); until it is settled, hosts treat album `price` tags as informational. ### 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 | Concept | NIP Status | Notes | |---------|------------|-------| | **Access filter (32217)** | Custom kind | No standard defines a signed probabilistic access set; NIP-63 kind 1163 (unmerged) is the nearest structural precedent. | | **Delivery receipt (32218)** | Custom kind, NIP-59 delivery | Private deed; carried in gift wraps, one signature from the host. | | **Host aggregate (32219)** | Custom kind | Public, host-signed totals. | | **Publisher rollup (32220)** | Custom kind | Public, publisher-signed, references host aggregates by `e` tag. | | **Purchase order (3222)** | Custom kind, NIP-59 delivery | Private buyer-to-seller order carrying intent plus payment proof. Never published. | | **Play report (3221)** | Custom kind | Listener-side; BDHKE blind signature for paid plays. | | **Blind signatures** | Cashu NUT-00 BDHKE + NUT-12 DLEQ | Reused directly; the DLEQ proof is what makes a play report verifiable by third parties. | | **Payment requests** | NUT-24 / BUD-07 | HTTP 402 + `X-Cashu`; buckets for rental. Requires NUT-18/26 `creqA`/`creqB` client support. | | **Media authorization** | BUD-11 / kind 24242 | How a host learns a requester's pubkey for a Blossom fetch. Not NIP-42. | | **Lightning payment leg** | NIP-57 | Zap request/receipt for ownership paid in sats. | | **Blob storage and mirroring** | NIP-B7 / BUD-01/04/12 | Filter blobs and audio blobs. | | **Private delivery** | NIP-59 | Gift wrap for receipts. | | **Addressable events** | NIP-01 | Filters are parameterized replaceable, `d` tag. | | **Addressing** | NIP-19 | `naddr` for filters and assets. |