Protocol & specs

Protocol v1

Machine-oriented companion to SCOPE.md and PROTOCOL_IDS.md.

On-chain fields

Subscribe (txpass / v1 / sub):

FieldMeaning
sellerSeller identity key or paymail
productProduct slug (e.g. imprintai-api)
planPlan slug (pro-month | pro-year | enterprise-month)
identityCommitment SHA256(I_compressed || salt_32) hex, or bare compressed pubkey (dogfood flag)
valid_fromUnix seconds
valid_untilUnix seconds
nonce32-byte hex
revoke_outOutpoint txid:vout or none

Revoke (txpass / v1 / revoke): references receipt_txid (and optional fields). The facilitator accepts it only from the receipt's identity I, an owner of the product, or a configured seller key. Spending revoke_out also marks the entitlement dead. When a Txpass-issued receipt is revoked, Txpass publishes the canonical record txpass / v1 / revoke + \n + receipt_txid <64 hex> in a transaction funded by the issuer key (no reason or business detail on chain).

Fee: txpass / fee / <sats> — exact sats set when pricing is frozen; changes only on version bump.

Transaction shape

A subscribe receipt is a record of a subscription the publisher has already billed (Txpass is not part of the billing). It is one BSV transaction with:

  • exactly one txpass OP_RETURN output: OP_FALSE OP_RETURN <single push of the payload bytes>. The legacy form OP_RETURN <push> is also accepted.
    • The payload is UTF-8 text in canonical form: the lines txpass / v1 / sub, seller …, product …, plan …, identity …, valid_from …, valid_until …, nonce …, revoke_out …, in that order.
    • Lines are separated by \n, with a single space after each key, and there are no extra lines.
    • nonce is 64 hex. revoke_out is none or <64 hex txid>:<vout>.
  • either an input that spends a P2PKH output of an accepted Txpass receipt issuer key (a Txpass-issued receipt: Txpass built and funded it at the publisher's request; this is the normal form), or, legacy, one or more outputs paying the seller's configured pay-to locking script;
  • any other outputs (e.g. change), which are ignored. There are no prices on the receipt.

Verification rules (portable)

Any operator running @txpass/verify with the same public config (GET /v1/pricing: seller keys, plan durations, grace, legacy pay-to scripts, and the receipt issuer keys and per-product issued plans from GET /v1/receipts/issuer) reaches the same answer. The facilitator applies these rules before activating a paid entitlement (verifyReceiptTx + verifyReceiptInclusion):

  1. The txid of the transaction equals the claimed receipt_txid.
  2. There is exactly one txpass OP_RETURN, and it is byte-identical to the submitted payload.
  3. product is configured, seller is one of its seller keys, and plan is one of its plans. For a Txpass-issued receipt (rule 5, issuer input) the seller is the publisher identity Txpass authorised (a product owner), so no seller list is needed; the plan follows the product's issued-receipt plans.
  4. identity = SHA256(I_compressed || salt) for the revealed I, and I is the authenticated caller.
  5. Issuer or pay-to: one input spends a P2PKH output whose public key (the key pushed in the unlocking script, matched against the parent output's hash160) is an accepted Txpass receipt issuer key; or (legacy) at least one output pays a configured pay-to locking script. There is no plan-price check; optional legacy price_sats in config is ignored.
  6. Window:
    • valid_until > now
    • valid_until − valid_from ≤ duration_days·86400 + grace_seconds (default grace 86400)
    • legacy pay-to receipts only: valid_from ≤ now + skew (default 600 s) and valid_until − valid_from ≥ 30·86400
    • Txpass-issued receipts may start in the future (a renewal chained to the end of the current receipt; it is not_yet_valid until then, and becomes the live one when the current one ends). The issuer keeps the 30-day minimum except for the receipt that replaces another after a plan change, which may cover just the rest of the replaced receipt's period; the on-chain payload has no plan-change marker, so verifiers accept issuer-attested receipts shorter than 30 days
    • Paid plan config duration_days must be an integer 30..3660. Free auto-enroll is unchanged and is not an on-chain paid receipt.
    • Agent sessions stay short (15–60 minutes) and are minted from a live entitlement; they are not short paid receipts.
  7. Inclusion: a merkle path (BUMP, BRC-74, either inside a BEEF or from a chain provider) whose root matches a block header (ChainTracker). The default minimum is 1 confirmation. Unconfirmed receipts are pending, not active.
  8. revoke_out: none, or an outpoint that is unspent. Spending it revokes the entitlement.

Txpass-issued receipts: who is trusted

The issuer key only proves Txpass recorded this subscription for the publisher named in its seller field, at that publisher's signed request. Txpass issues only for an active owner of the live product (or a configured seller key), so verifiers need no per-product seller list for these receipts: the issuer key is the trust anchor. The current and retired issuer public keys are published at GET /v1/receipts/issuer and in GET /v1/pricing (receipt_issuers). A verifier that does not want to call Txpass pins those public keys in its own config; a fork of the verifier can accept a different issuer key (or none, which keeps the legacy pay-to rule). Publishers ask Txpass to issue with requests signed by a product owner identity key (txpass/v1/publisher-request, key ID v1 publisher); see the Facilitator API.

Billing/pricing is the publisher’s off-chain concern (not a protocol constant). The table in @txpass/protocol plans.ts may hold optional legacy price_sats placeholders for diagnostics only; they are unused for receipt verify.

Identity

  • Prefer commitment form; reveal I + salt at ingest over AuthFetch.
  • Dogfood may allow bare compressed pubkey behind a feature flag.

BRC-43

AttributeValue
Originatortxpass.app
Protocol IDtxpass
Key IDsv1 auth, v1 delegate, v1 reveal, v1 publisher (signed publisher receipt requests)

Descriptor

GET /v1/protocol returns JSON: prefix, version, originator, protocolId, keyIds, feeSats, commitmentDefault, verifierPackage, docsUrl. Built by @txpass/protocol buildProtocolDescriptor().

HTTP auth (BRC-103 / BRC-104)

Mutual authentication for protected facilitator routes uses the standard Metanet stack — not a Txpass-specific header dialect:

LayerSpec / package
Mutual authBRC-103 peer session
HTTP transportBRC-104 — x-bsv-auth-* headers, POST /.well-known/auth
Server createAuthMiddleware({ wallet }) → req.auth.identityKey
Client@bsv/sdk AuthFetch
Entitlement@txpass/auth-middleware requireEntitlement after identity is known

Public routes (GET /health, GET /v1/protocol, GET /v1/pricing) remain unauthenticated. Protected routes (GET /v1/entitlements, POST /v1/receipts) require a live session identity (or dogfood stub header when TXPASS_AUTH_MODE=stub).

Originator domain: txpass.app. Protocol / key IDs: see PROTOCOL_IDS.md.