# README

A repository for submitting, discussing, sharing, and indexing technical proposals for use across the Bitcoin ecosystem. Data models, user interfaces, script templates, encoding formats, communication protocols, and constructive critique of existing industry practice are all welcome. The goal is to provide a platform for sharing ideas without any bureaucratic overhead.

## Contributing

Contributions from all builders are welcome and encouraged. To propose a new BRC, fork the repo and create a new markdown file using [EXAMPLE.md](/example/example) as the template. The common structure is outlined below, which is a guideline to aid you rather than a strict requirement. Once your proposal is ready to share, submit a pull request so that others can review and discuss it.

To participate in discussions about existing proposals, simply open an issue and link back to the BRC file in question.

## Iterative improvement

We believe in encouraging discussion and iterative improvement of proposals, resulting in incremental improvement within the bounds of the Bitcoin protocol. We welcome suggestions for improvement and are committed to working with contributors to improve proposals and ensure that they align with our guidelines.

**Note** that substantial revisions to standards (beyond fixing typos, adding context or wording) should go into a new standard that extends or revises the old one, so as not to disrupt existing implementations.

We look forward to your contributions and helping to create a world where transactions are seamlessly formed, and applications interact with each other with ease.

Read more about areas of interest on [OpenStandards.cash](https://openstandards.cash)

## Current implementation map

The standards in this repository are vendor-neutral. Current BSV Association reference implementations include:

* `@bsv/sdk` for TypeScript SDK primitives, transactions, identities, overlays, storage, and the BRC-100 `WalletClient` interface.
* `@bsv/wallet-toolbox` for BRC-100 wallet storage, services, signing, monitoring, and wallet implementation tooling.
* BSV Desktop and BSV Browser as BSV Association reference wallet/browser applications for the BRC-100 interface.
* Overlay service implementations in [`bsv-blockchain/ts-stack`](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays), including the `overlay` and `overlay-express` packages, and related overlay example repositories.

Vendor distributions can implement the same standards with their own branding and hosted service defaults. Examples include Babbage's Metanet Desktop / Metanet Explorer as well as the Hudos Browser built by Matt Archbold.

## Structure

The BRCs repository is organized into directories, each representing a different category of proposal. Categories may include, but are not limited to:

* Transaction Templates
* Bitcoin Script Templates
* Communication Protocols

Each proposal should be written as a markdown file and should loosely adhere to the following:

* **Title:** A descriptive title for the standard being defined.
* **Author(s):** Who wrote the standard and where did it come from? How can they be reached?
* **Abstract:** A brief description of the proposed standard or template.
* **Motivation:** The reasoning behind the proposal and why it is needed.
* **Specification:** A detailed technical specification of the proposal.
* **Implementations:** Information on how the proposal has been or can be implemented.
* **References:** Any relevant literature or external resources related to the proposal.

**Note** that additional relevant content, identifiers or other information may be added to the document. Documents that already existed before the repository may not follow these requirements.

Things that help depict and understand the document, such as media, may also be added in a media subdirectory where appropriate.

Refer to the [Banana-Powered Bitcoin Wallet Control Protocol](/example/example) for a fun example template you can copy when proposing your own standards.

## Interpreting the Repository

This repository contains both active interoperability specifications and historical design context.

Use the directory README files and in-spec status notes to determine which documents currently define interoperable behavior in a given area. Where a newer BRC narrows, extends, or authoritatively re-specifies behavior, implementers should follow the newer normative document rather than inferring precedence from chronology alone.

## Standards

| BRC | Standard                                                                                                                   |
| --- | -------------------------------------------------------------------------------------------------------------------------- |
| 0   | [Banana-Powered Bitcoin Wallet Control Protocol](/example/example)                                                         |
| 1   | [Transaction Creation](/wallet/0001)                                                                                       |
| 2   | [Data Encryption and Decryption](/wallet/0002)                                                                             |
| 3   | [Digital Signature Creation and Verification](/wallet/0003)                                                                |
| 4   | [Input Redemption](/wallet/0004)                                                                                           |
| 5   | [HTTP Wallet Communications Substrate](/wallet/0005)                                                                       |
| 6   | [XDM Wallet Communications Substrate](/wallet/0006)                                                                        |
| 7   | [Window Wallet Communication Substrate](/wallet/0007)                                                                      |
| 8   | [Everett-style Transaction Envelopes](/transactions/0008)                                                                  |
| 9   | [Simplified Payment Verification](/transactions/0009)                                                                      |
| 10  | [Merkle proof standardised format](/transactions/0010)                                                                     |
| 11  | [TSC Proof Format with Heights](/transactions/0011)                                                                        |
| 12  | [Raw Transaction Format](/transactions/0012)                                                                               |
| 13  | [TXO Transaction Object Format](/transactions/0013)                                                                        |
| 14  | [Bitcoin Script Binary, Hex and ASM Formats](/scripts/0014)                                                                |
| 15  | [Bitcoin Script Assembly Language](/scripts/0015)                                                                          |
| 16  | [Pay to Public Key Hash](/scripts/0016)                                                                                    |
| 17  | [Pay to R Puzzle Hash](/scripts/0017)                                                                                      |
| 18  | [Pay to False Return](/scripts/0018)                                                                                       |
| 19  | [Pay to True Return](/scripts/0019)                                                                                        |
| 20  | [There is no BRC-20](/tokens/0020)                                                                                         |
| 21  | [Push TX](/scripts/0021)                                                                                                   |
| 22  | [Overlay Network Data Synchronization](/overlays/0022)                                                                     |
| 23  | [Confederacy Host Interconnect Protocol (CHIP)](/overlays/0023)                                                            |
| 24  | [Overlay Network Lookup Services](/overlays/0024)                                                                          |
| 25  | [Confederacy Lookup Availability Protocol (CLAP)](/overlays/0025)                                                          |
| 26  | [Universal Hash Resolution Protocol](/overlays/0026)                                                                       |
| 27  | [Direct Payment Protocol (DPP)](/payments/0027)                                                                            |
| 28  | [Paymail Payment Destinations](/payments/0028)                                                                             |
| 29  | [Simple Authenticated BSV P2PKH Payment Protocol](/payments/0029)                                                          |
| 30  | [Transaction Extended Format (EF)](/transactions/0030)                                                                     |
| 31  | [Authrite Mutual Authentication](/peer-to-peer/0031)                                                                       |
| 32  | [BIP32 Key Derivation Scheme](/key-derivation/0032)                                                                        |
| 33  | [PeerServ Message Relay Interface](/peer-to-peer/0033)                                                                     |
| 34  | [PeerServ Host Interconnect Protocol](/peer-to-peer/0034)                                                                  |
| 35  | [Layered Key-Value Store for Wallets and Overlay Services](/overlays/0035)                                                 |
| 36  | [Format for Bitcoin Outpoints](/outpoints/0036)                                                                            |
| 37  | [Basket and Custom Instructions Extension for Bitcoin Outpoints](/outpoints/0037)                                          |
| 38  | [User Wallet Data Format](/outpoints/0038)                                                                                 |
| 39  | [User Wallet Data Format Encryption Extension](/outpoints/0039)                                                            |
| 40  | [User Wallet Data Synchronization](/outpoints/0040)                                                                        |
| 41  | [PacketPay HTTP Payment Mechanism](/payments/0041)                                                                         |
| 42  | [BSV Key Derivation Scheme (BKDS)](/key-derivation/0042)                                                                   |
| 43  | [Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)                                          |
| 44  | [Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044)                                             |
| 45  | [Definition of UTXOs as Bitcoin Tokens](/tokens/0045)                                                                      |
| 46  | [Wallet Transaction Output Tracking (Output Baskets)](/wallet/0046)                                                        |
| 47  | [Bare Multi-Signature](/scripts/0047)                                                                                      |
| 48  | [Pay to Push Drop](/scripts/0048)                                                                                          |
| 49  | [Users should never see an address](/opinions/0049)                                                                        |
| 50  | [Submitting Received Payments to a Wallet](/wallet/0050)                                                                   |
| 51  | [List of user experiences](/opinions/0051)                                                                                 |
| 52  | [Identity Certificates](/peer-to-peer/0052)                                                                                |
| 53  | [Certificate Creation and Revelation](/wallet/0053)                                                                        |
| 54  | [Hybrid Payment Mode for DPP](/payments/0054)                                                                              |
| 55  | [HTTPS Transport Mechanism for DPP](/payments/0055)                                                                        |
| 56  | [Unified Abstract Wallet-to-Application Messaging Layer](/wallet/0056)                                                     |
| 57  | [Legitimate Uses for mAPI](/opinions/0057)                                                                                 |
| 58  | [Merkle Path JSON format](/transactions/0058)                                                                              |
| 59  | [Security and Scalability Benefits of UTXO-based Overlay Networks](/opinions/0059)                                         |
| 60  | [Simplifying State Machine Event Chains in Bitcoin](/state-machines/0060)                                                  |
| 61  | [Compound Merkle Path Format](/transactions/0061)                                                                          |
| 62  | [Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062)                                            |
| 63  | [Genealogical Identity Protocol](/peer-to-peer/0063)                                                                       |
| 64  | [Overlay Network Transaction History Tracking](/overlays/0064)                                                             |
| 65  | [Transaction Labels and List Actions](/wallet/0065)                                                                        |
| 66  | [Output Basket Removal and Certificate Deletion](/wallet/0066)                                                             |
| 67  | [Simplified Payment Verification](/transactions/0067)                                                                      |
| 68  | [Publishing Trust Anchor Details at an Internet Domain](/peer-to-peer/0068)                                                |
| 69  | [Revealing Key Linkages](/key-derivation/0069)                                                                             |
| 70  | [Paymail BEEF Transaction](/payments/0070)                                                                                 |
| 71  | [Merkle Path Binary Format](/transactions/0071)                                                                            |
| 72  | [Protecting BRC-69 Key Linkage Information in Transit](/key-derivation/0072)                                               |
| 73  | [Group Permissions for App Access](/wallet/0073)                                                                           |
| 74  | [BSV Unified Merkle Path (BUMP) Format](/transactions/0074)                                                                |
| 75  | [Mnemonic For Master Private Key](/key-derivation/0075)                                                                    |
| 76  | [Graph Aware Sync Protocol](/transactions/0076)                                                                            |
| 77  | [Message Signature Creation and Verification](/peer-to-peer/0077)                                                          |
| 78  | [Serialization Format for Portable Encrypted Messages](/peer-to-peer/0078)                                                 |
| 79  | [Token Exchange Protocol for UTXO-based Overlay Networks](/tokens/0079)                                                    |
| 80  | [Improving on MLD for BSV Multicast Services](/opinions/0080)                                                              |
| 81  | [Private Overlays with P2PKH Transactions](/overlays/0081)                                                                 |
| 82  | [Defining a Scalable IPv6 Multicast Protocol for Blockchain Transaction Broadcast and Update Delivery](/peer-to-peer/0082) |
| 83  | [Scalable Transaction Processing in the BSV Network](/transactions/0083)                                                   |
| 84  | [Linked Key Derivation Scheme](/key-derivation/0084)                                                                       |
| 85  | [Proven Identity Key Exchange (PIKE)](/peer-to-peer/0085)                                                                  |
| 86  | [Bidirectionally Authenticated Derivation of Privacy Restricted Type 42 Keys](/key-derivation/0086)                        |
| 87  | [Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](/overlays/0087)                     |
| 88  | [Overlay Services Synchronization Architecture](/overlays/0088)                                                            |
| 89  | [Web 3.0 Standard (at a high level)](/opinions/0089)                                                                       |
| 90  | [Thoughts on the Mandala Network](/opinions/0090)                                                                          |
| 91  | [Outputs, Overlays, and Scripts in the Mandala Network](/opinions/0091)                                                    |
| 92  | [Mandala Token Protocol](/tokens/0092)                                                                                     |
| 93  | [Limitations of BRC-69 Key Linkage Revelation](/key-derivation/0093)                                                       |
| 94  | [Verifiable Revelation of Shared Secrets Using Schnorr Protocol](/key-derivation/0094)                                     |
| 95  | [Atomic BEEF Transactions](/transactions/0095)                                                                             |
| 96  | [BEEF V2 Txid Only Extension](/transactions/0096)                                                                          |
| 97  | [Extensible Proof-Type Format for Specific Key Linkage Claims](/wallet/0097)                                               |
| 98  | [P Protocols: Allowing future wallet protocol permission schemes](/wallet/0098)                                            |
| 99  | [P Baskets: Allowing Future Wallet Basket and Digital Asset Permission Schemes](/wallet/0099)                              |
| 100 | [Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100)      |
| 101 | [Diverse Facilitators and URL Protocols for SHIP and SLAP Overlay Advertisements](/overlays/0101)                          |
| 102 | [The deployment-info.json Specification](/apps/0102)                                                                       |
| 103 | [Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol](/peer-to-peer/0103)                                 |
| 104 | [HTTP Transport for BRC-103 Mutual Authentication](/peer-to-peer/0104)                                                     |
| 105 | [HTTP Service Monetization Framework](/payments/0105)                                                                      |
| 106 | [Bitcoin Script ASM Format](/scripts/0106)                                                                                 |
| 107 | [Enhanced Mandala Token Protocol](/tokens/0107)                                                                            |
| 108 | [Identity-Linked Token Protocol](/tokens/0108)                                                                             |
| 109 | [PCW-1 : Peer Cash Wallet Protocol](/wallet/0109)                                                                          |
| 110 | [Zero-Friction, Mobile-First Onboarding for MetaNet-Enabled Apps](/opinions/0110)                                          |
| 111 | [P Labels: Allowing Future Wallet Action Label Permission Schemes](/wallet/0111)                                           |
| 112 | [Balance Baskets: Backwards-Compatible Basket Balance Queries](/wallet/0112)                                               |
| 113 | [Merkle Proof Token](/tokens/0113)                                                                                         |
| 114 | [Time Labels: Backwards-Compatible Action Timestamp Filters for List Actions](/wallet/0114)                                |
| 115 | [Identity-Linked Deterministic Token Verification Framework](/tokens/0115)                                                 |
| 116 | [Wallet Permissions and Counterparty Trust](/wallet/0116)                                                                  |
| 117 | [Proof-of-Indexing Hash-to-Mint Tokens](/tokens/0117)                                                                      |
| 118 | [Multipart Body Transport for BRC-105 Payments](/payments/0118)                                                            |
| 119 | [SubTree Unified Merkle Path (STUMP) Format](/transactions/0119)                                                           |
| 120 | [x402 Stateless Settlement-Gated HTTP Protocol](/payments/0120)                                                            |
| 121 | [Simple 402 Payments](/payments/0121)                                                                                      |
| 122 | [Auditable Real-time Inference Architecture (ARIA)](/apps/0122)                                                            |
| 123 | [Basket Permission Scheme Registry and Governance](/wallet/0123)                                                           |
| 124 | [Multicast Transaction Frame Format](/transactions/0124)                                                                   |
| 125 | [PeerPay URI Scheme for BRC-29 Payments](/payments/0125)                                                                   |
| 126 | [Multicast Transaction NACK Retransmission Protocol](/transactions/0126)                                                   |
| 127 | [Multicast Subtree Group Announcement Frame Format](/transactions/0127)                                                    |
| 128 | [Multicast Extended Transaction Frame Format](/transactions/0128)                                                          |
| 129 | [IPv6 Multicast Group Address Assignments](/transactions/0129)                                                             |
| 130 | [Multicast Transaction Frame Fragmentation](/transactions/0130)                                                            |
| 131 | [Multicast Block Announcement Frame Format](/transactions/0131)                                                            |
| 132 | [Multicast Subtree Data Frame Format](/transactions/0132)                                                                  |
| 133 | [Multicast Coinbase Transaction Frame Format](/transactions/0133)                                                          |
| 134 | [Multicast Anchor Transaction Frame Format](/transactions/0134)                                                            |
| 135 | [Multicast Block Header Frame Format](/transactions/0135)                                                                  |
| 136 | [Block-Anchored Overlay Synchronization via Block-Aligned Sparse Merkle Trees (BASM)](/overlays/0136)                      |
| 137 | [Device-Aware Wallet Onboarding and Fallback Login for BRC-100 Applications](/opinions/0137)                               |
| 138 | [Single-Use Signed Proofs for Request Authentication](/peer-to-peer/0138)                                                  |
| 139 | [Multicast Shard Manifest Announcement Protocol](/transactions/0139)                                                       |
| 140 | [Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme](/key-derivation/0140)                                |
| 141 | [Fountain-Coded Air-Gap Transport for Arbitrary Payloads](/peer-to-peer/0141)                                              |
| 142 | [Multicast Transaction Bundle Frame Format](/transactions/0142)                                                            |
| 143 | [Subtree Data Frame Format](/transactions/0143)                                                                            |
| 144 | [Block Frame Format](/transactions/0144)                                                                                   |
| 145 | [Registry-Free Typed Content Anchor with On-Chain Code Provenance](/apps/0145)                                             |
| 146 | [Access Gates for Metanet Rooms](/apps/0146)                                                                               |
| 147 | [1Sat Ordinals Basket Profile for BRC-46 / BRC-100](/tokens/0147)                                                          |
| 148 | [Multicast Shard Domain Partitioning and the BEEF Object Plane](/transactions/0148)                                        |
| 149 | [Multicast BEEF Object Frame Format](/transactions/0149)                                                                   |
| 150 | [1Sat Provenance Remittance for Basket `1sat`](/tokens/0150)                                                               |
| 151 | [BRC-100 Risk Assessment and Best Integration Practices](/opinions/0151)                                                   |
| 152 | [Best Practices for Regulated Tokens in a BRC-100 Ecosystem](/opinions/0152)                                               |
| 153 | [Action Reference Labels for BRC-100 Wallets](/wallet/0153)                                                                |
| 154 | [Pluggable Backup Services for BRC-140 Share Vaults](/wallet/0154)                                                         |
| 155 | [Pull-Based Receive Discovery](/wallet/0155)                                                                               |
| 157 | [Entropy-Rooted Backup and Recovery with Mnemonics and Backup Shares](/key-derivation/0157)                                |
| 158 | [Outpoint BEEF](/transactions/0158)                                                                                        |
| 159 | [1Sat Ordinals — Single-Satoshi Tokens and Origin Tracking](/tokens/0159)                                                  |
| 160 | [1Sat Ordinals — Inscription Envelopes](/tokens/0160)                                                                      |
| 164 | [Output Identity Tags for BRC-100 Wallets](/wallet/0164)                                                                   |
| 168 | [Verifiable Time Allocation](/apps/0168)                                                                                   |
| 169 | [Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)                                           |
| 210 | [Derived Collectibles](/apps/0210)                                                                                         |
| 218 | [Chat-Native Command Grammar for the Metanet](/apps/0218)                                                                  |
| 219 | [Wallet Permission Prompt Liveness Contract](/wallet/0219)                                                                 |
| 220 | [NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates](/apps/0220)                    |
| 224 | [Block Media Format (BMF) — Composable On-Chain Audio/Video](/apps/0224)                                                   |
| 225 | [Animated-QR Air-Gap Transport for Arbitrary Payloads (TKQR1)](/peer-to-peer/0225)                                         |
| 226 | [Miner-Enforced Resale-Royalty Covenant Tokens (OP\_PUSH\_TX)](/tokens/0226)                                               |
| 227 | [Frictionless On-Chain Onboarding via Pre-Funded Claimable Tokens](/apps/0227)                                             |
| 228 | [Unlinkable Payments under the Identity Paradigm](/payments/0228)                                                          |

## License

Everything in this repository is subject to the [Open BSV License](https://github.com/bitcoin-sv/bitcoin-sv/blob/master/LICENSE).

## Terms and Conditions

[Terms and Conditions](https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfVclCmeRPossA1a4BKh5%2Fuploads%2FjrLiRFSSc78VORRVpyOT%2FTerms%20of%20Use%20BSVA%20Blockchain.pdf?alt=media\&token=95fa409e-2017-4503-9b38-f4951b4960df)


# Banana-Powered Bitcoin Wallet Control Protocol

Jane Doe (<jane.doe@example.com>)

## Abstract

This proposal introduces an absurd, humorously impractical method for controlling embedded Bitcoin wallets in web-based applications using the power of bananas.

> The Abstract section should concisely describe your proposal at a high-level.

## Motivation

The purpose of this proposal is to provide a lighthearted example of how to create a standard that adheres to the BRC format while sparking laughter and enjoyment among readers.

> The Motivation section should let people know the context for your proposal, and why it was written.

## Specification

1. Wallets must be embedded within a real banana fruit, using cutting-edge bio-organic engineering techniques.
2. Web-based applications must communicate with the banana-embedded wallet using a Banana Communication Protocol (BCP)[<sup>1</sup>](#footnote-1) involving a series of gentle squeezes.
3. Squeezes will be translated into binary code, with a short squeeze representing '0' and a long squeeze representing '1'.
4. To ensure wallet security, any unauthorized squeezes will cause the banana to emit a loud, defensive "peel screech" to deter potential attackers.
5. Wallets will be recharged by leaving them in direct sunlight for a minimum of 2 hours per day.
6. The protocol must support a minimum of 2 bananas connected simultaneously to avoid a single point of failure[<sup>2</sup>](#footnote-2).

> The Specification section of your proposal should stipulate all information needed to implement the standard, and make up the bulk of the document. Generally, people should be able to create a compatible implementation with only the specification.

## Implementations

1. Wallet developers must carefully select the finest bananas available, ensuring they are both ripe and durable.
2. A custom JavaScript library, BananaJS, must be developed for web-based applications to interact with the wallet using the BCP.
3. Wallet applications must include a sophisticated squeeze detection system to ensure accurate communication.
4. Wallet developers must implement a "peel screech" alarm system for added security.
5. Web-based applications must visually display the connection and energy status of each banana wallet.

> The Implementations section should contain information about places where the standard is implemented, or examples of its implementation.

## References

* 1: Doe, J. (2023). Banana-Powered Bitcoin Wallet Control Protocol: A Humorous Guide. Bananaverse Press.
* 2: Smith, T. (2022). The Art of Banana Communication (Volume IV): Avoiding Single Points of Failure in Banana Communications. FruitTech Publishing.

> The References section should contain any footnotes used throughout the document.


# The deployment-info.json Specification

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define a comprehensive and authoritative specification of the `deployment-info.json` schema. It is intended to serve as a reference for developers building BSV blockchain applications that integrate with the LARS (Local Automated Runtime System) and CARS (Cloud Automated Runtime System) tooling and beyond. By following this specification, projects can ensure a standardized and interoperable structure that other tools and workflows can rely upon.

## Specification

The `deployment-info.json` file defines the structure and metadata of a BSV-based application for both local development (via LARS) and cloud deployment (via CARS). It describes the app’s topic managers, lookup services, frontend build configuration, contract compilation requirements, and various deployment configurations.

## Top-Level Schema

**Example:**

```json
{
  "schema": "bsv-app",
  "schemaVersion": "1.0",
  "topicManagers": {
    "tm_meter": "./backend/src/topic-managers/MeterTopicManager.ts"
  },
  "lookupServices": {
    "ls_meter": {
      "serviceFactory": "./backend/src/lookup-services/MeterLookupServiceFactory.ts",
      "hydrateWith": "mongo"
    }
  },
  "frontend": {
    "language": "react",
    "sourceDirectory": "./frontend"
  },
  "contracts": {
    "language": "sCrypt",
    "baseDirectory": "./backend"
  },
  "configs": [
    {
      "name": "Local LARS",
      "network": "testnet",
      "provider": "LARS",
      "run": [
        "backend"
      ]
    },
    {
      "name": "production",
      "provider": "CARS",
      "CARSCloudURL": "http://some-cloud.example.com",
      "projectID": "your-project-id",
      "network": "mainnet",
      "deploy": [
        "frontend",
        "backend"
      ],
      "frontendHostingMethod": "HTTPS"
    }
  ]
}
```

## Fields

### `schema` (string)

* **Required**: Yes
* **Valid Values**: `"bsv-app"`
* A fixed string identifying the schema type for this file. Must be `"bsv-app"`.

### `schemaVersion` (string)

* **Required**: Yes
* Indicates the version of this schema. Example: `"1.0"`.
* As the schema evolves, this can help tooling handle backward compatibility.

### `topicManagers` (object)

* **Required**: No (You may have no Topic Managers if your app does not define an overlay.)
* Maps **topic manager names** (strings) to **paths** of their implementing modules.
* **Key (Topic Manager Name)**: A unique name (string) identifying the Topic Manager within the app. For example, `"tm_meter"`, as per BRC-87.
* **Value (Path)**: A relative file path (string) to a `.ts` module with a default export for a class that implements the `TopicManager` interface from `@bsv/overlay`.
* **Example**:

  ```json
  {
    "tm_meter": "./backend/src/topic-managers/MeterTopicManager.ts"
  }
  ```

### `lookupServices` (object)

* **Required**: No (Only if your app needs overlay retrieval/lookup functionalities.)
* Maps **lookup service names** (strings) to configuration objects describing how to instantiate them.
* **Key (Lookup Service Name)**: A unique name (e.g., `"ls_meter"`), as per BRC-87.
* **Value (Service Config Object)**:

  ```json
  {
    "serviceFactory": "./backend/src/lookup-services/MeterLookupServiceFactory.ts",
    "hydrateWith": "mongo"
  }
  ```
* **Fields in the Service Config Object**:
  * `serviceFactory` (string, required): A path to a `.ts` module containing a default export of a factory function that creates a `LookupService` instance. The factory function may accept database connections (e.g., a MongoDB `Db` object) and return a class that implements the `LookupService` interface from `@bsv/overlay`.
  * `hydrateWith` (string, required): Defines how the service should be backed by persistent storage. Accepted values:
    * `"mongo"`: Indicates that the lookup service’s storage uses a MongoDB database. LARS/CARS will provide a `mongoDb` instance.
    * `"knex"`: Indicates that the service uses a SQL-based storage via Knex. LARS/CARS will provide a `knex` instance.

### `frontend` (object)

* **Required**: No (Only if your project has a frontend.)
* Describes how the frontend portion of the app is set up.
* Fields:
  * `language` (string): The frontend tech stack. Common values: `"react"`, `"html"`, etc. Tools can use this to know how to build or deploy the frontend.
  * `sourceDirectory` (string): Path to the frontend source files relative to the project root.

**Example**:

```json
"frontend": {
  "language": "react",
  "sourceDirectory": "./frontend"
}
```

### `contracts` (object)

* **Required**: No (Only if your project uses on-chain contracts.)
* Describes contract language and location for source and artifacts.
* Fields:
  * `language` (string): The contract language. Common values: `"sCrypt"`.
  * `baseDirectory` (string): Path to the directory containing contract source code and related build outputs, depending on the language.

**Example**:

```json
"contracts": {
  "language": "sCrypt",
  "baseDirectory": "./backend"
}
```

If `contracts` is present and `language` is `"sCrypt"`, LARS/CARS can trigger automatic contract compilation steps when contracts change.

### `configs` (array)

* **Required**: Yes (although it can be empty initially)
* An array of configuration objects defining different **deployment targets** or **modes** for the project.
* Each config object corresponds to either:
  * **LARS**: A local development environment configuration.
  * **CARS**: A cloud deployment configuration.
* You can have multiple CARS configs (e.g., `"staging"`, `"production"`) and at most one LARS config (by convention, though not strictly enforced).

**Common Fields in Each Config**:

* `name` (string, required): A human-readable name for the configuration. E.g., `"Local LARS"`, `"production"`, `"staging"`.
* `provider` (string, required): Indicates whether this config uses LARS or CARS. Common values:
  * `"LARS"`: Local environment config
  * `"CARS"`: Cloud environment config
* `network` (string, optional): `"mainnet"` or `"testnet"`. Specifies which BSV network this config targets.
  * For LARS: Required to know if the local environment simulates mainnet or testnet conditions.
  * For CARS: Determines which network the release should be associated with.

**LARS-Specific Fields**:

* `run` (array of strings, optional): Which parts of the project to run locally. Usually `["backend"]`, may include `"frontend"` if supported, but often the frontend is served separately.\
  Example:

  ```json
  {
    "name": "Local LARS",
    "network": "testnet",
    "provider": "LARS",
    "run": ["backend"]
  }
  ```

**CARS-Specific Fields**:

* `CARSCloudURL` (string, required for CARS): URL of the CARS cloud service. E.g., `"https://cars-cloud.example.com"`.
* `projectID` (string, required for CARS): The Project ID on the CARS Cloud. Used for managing deployments, logs, admins, etc.
* `deploy` (array of strings, required): Which parts of the application to deploy to the cloud. E.g., `["frontend", "backend"]`.
* `frontendHostingMethod` (string, optional): How the frontend is hosted in the cloud. Common values:
  * `"HTTPS"`: Host frontend over HTTPS (CDN or static hosting, default)
  * `"UHRP"`: Host via the UHRP protocol (if integrated and supported by the specific CARS Cloud)

**Example CARS Config**:

```json
{
  "name": "production",
  "provider": "CARS",
  "CARSCloudURL": "http://cloud.example.com",
  "projectID": "abc123",
  "network": "mainnet",
  "deploy": ["frontend", "backend"],
  "frontendHostingMethod": "HTTPS"
}
```

### Additional Notes on `configs`:

* Multiple `CARS` configs can coexist (e.g., one for `"staging"`, one for `"production"`, different networks, resilient deployment across clouds).
* Only one `LARS` config is typically present (local dev environment). Tools like LARS assume a single local configuration.
* Tools like CARS will prompt or require the user to pick a configuration if multiple apply.

***

## Integration With LARS and CARS

* **LARS** uses `deployment-info.json` to:
  * Determine which topic managers and lookup services to load locally.
  * Compile contracts if specified under `contracts`.
  * Identify a `LARS` config in `configs` to know what network to run, what keys to use, and what parts of the app to start.
* **CARS** uses `deployment-info.json` to:
  * Identify `CARS` configs and connect to the specified CARS Cloud environment.
  * Build and upload artifacts, define which components to deploy.
  * Manage projects, logs, admins, and releases in a cloud environment based on project ID.

By maintaining a consistent `deployment-info.json` schema, both local and cloud tools can parse and understand the application’s structure, enabling smooth transitions from local dev (LARS) to production deployments (CARS).

***

## Compatibility and Future-Proofing

* **`schema` and `schemaVersion`**: Future updates to the schema may introduce new fields or optional properties. Always check if your tools (LARS, CARS, or others) support the version you’re using.
* **Optional Fields**: Many fields are optional, allowing minimal setups. For example, you can omit `frontend` if you have no frontend. You can omit `lookupServices` if you have no shared state coordination needs.
* **Custom Fields**: It’s possible tools or future expansions add custom fields. Such fields should not conflict with the specified ones and should be namespaced or documented externally. Primary LARS/CARS tooling will typically ignore unknown fields.

***

## Example Minimal `deployment-info.json`

```json
{
  "schema": "bsv-app",
  "schemaVersion": "1.0",
  "topicManagers": {},
  "lookupServices": {},
  "configs": [
    {
      "name": "Local LARS",
      "provider": "LARS",
      "network": "testnet",
      "run": ["backend"]
    }
  ]
}
```

No frontend, no contracts, no lookup services, and a single LARS config.

***

## Example More Complex `deployment-info.json`

```json
{
  "schema": "bsv-app",
  "schemaVersion": "1.0",
  "topicManagers": {
    "tm_meter": "./backend/src/topic-managers/MeterTopicManager.ts"
  },
  "lookupServices": {
    "ls_meter": {
      "serviceFactory": "./backend/src/lookup-services/MeterLookupServiceFactory.ts",
      "hydrateWith": "mongo"
    }
  },
  "frontend": {
    "language": "react",
    "sourceDirectory": "./frontend"
  },
  "contracts": {
    "language": "sCrypt",
    "baseDirectory": "./backend"
  },
  "configs": [
    {
      "name": "Local LARS",
      "network": "testnet",
      "provider": "LARS",
      "run": ["backend"]
    },
    {
      "name": "staging",
      "provider": "CARS",
      "CARSCloudURL": "http://staging-cloud.example.com",
      "projectID": "staging-project-id",
      "network": "testnet",
      "deploy": ["frontend", "backend"],
      "frontendHostingMethod": "HTTPS"
    },
    {
      "name": "production",
      "provider": "CARS",
      "CARSCloudURL": "https://cars-cloud.example.com",
      "projectID": "your-production-project-id",
      "network": "mainnet",
      "deploy": ["frontend", "backend"],
      "frontendHostingMethod": "HTTPS"
    }
  ]
}
```

***

## Conclusion

This reference provides a complete specification of the `deployment-info.json` schema used by LARS, CARS, and related tooling. By adhering to this schema, developers create a consistent and predictable environment, enabling a smooth, automated workflow from local development to production deployment.


# Auditable Real-time Inference Architecture (ARIA)

**Authors:** Juan Manuel Palencia Osorio **Version:** 1.0.0 **Date:** 2026-03-22 **Status:** Draft **Category:** Application Layer

***

## Abstract

This document specifies the **Auditable Real-time Inference Architecture (ARIA)** protocol, a BSV application-layer standard for cryptographic accountability of AI inference systems operating in production. ARIA enables independent, post-hoc verification of *which AI model* produced *which output* at *what time* — without requiring access to the operator's systems or trust in the operator's claims.

The protocol uses a **pre-commitment scheme**: before any inference executes, the operator publishes an OP\_RETURN transaction on BSV committing to the exact model versions and system state that will be used. After the inference batch completes, a second transaction seals the batch with a Merkle root of all records. The two transactions are cryptographically linked, making retroactive fabrication of records computationally infeasible.

***

## Motivation

Production AI systems that make high-stakes decisions — medical triage, credit scoring, content moderation, judicial risk assessment — currently offer no externally verifiable record of their behavior. Audit logs are mutable; operators can alter, delete, or fabricate records before a regulatory inspection. Independent verification requires cooperation from the auditee.

The **EU AI Act (Regulation (EU) 2024/1689)**, effective from 2026, mandates for high-risk AI systems (Annex III):

* Automatic event logging with sufficient granularity to identify the inputs that led to each output (Art. 12.1)
* Traceability across the AI system's lifecycle (Art. 12.2)
* Capacity for post-hoc verification of the system's outputs (Art. 12.3)

No open technical standard currently addresses all three requirements simultaneously with an economically viable implementation. ARIA fills this gap.

BSV is uniquely suited for this protocol. Its unbounded OP\_RETURN capacity (100 KB+), fees of approximately $0.0001 per transaction, and Teranode-scale throughput make it economically viable to commit every epoch of a real-time inference system — costing approximately $2.10/year for a system processing 1.5-second epochs continuously, compared to $13M–$315M/year on Ethereum.

***

## Specification

### 1. Terminology

| Term               | Definition                                                                                          |
| ------------------ | --------------------------------------------------------------------------------------------------- |
| **Epoch**          | A time interval or batch of inferences bounded by an EPOCH\_OPEN and EPOCH\_CLOSE transaction pair. |
| **EPOCH\_OPEN**    | BSV transaction that commits the model versions and system state before inference begins.           |
| **EPOCH\_CLOSE**   | BSV transaction that seals the epoch with a Merkle root of all inference records.                   |
| **AuditRecord**    | A structured record representing a single inference event within an epoch.                          |
| **Merkle root**    | SHA-256 root of the RFC 6962 Merkle tree of all AuditRecord hashes in an epoch.                     |
| **canonical JSON** | A deterministic JSON serialization defined in §5 of this specification.                             |
| **record hash**    | SHA-256 of the canonical JSON serialization of an AuditRecord.                                      |
| **operator**       | The entity deploying and operating an AI system.                                                    |
| **verifier**       | Any party performing independent verification of an epoch or record.                                |

### 2. Transaction Format

ARIA data is embedded in BSV transactions using a standard OP\_RETURN output with the following script format:

```
OP_FALSE  OP_RETURN  PUSH4(0x41524941)  PUSHDATA(<json_bytes>)
  0x00      0x6A     0x04 0x41 0x52 0x49 0x41   <varint> <utf-8 json>
```

* **OP\_FALSE** (`0x00`): Marks the output as provably unspendable per the standard OP\_FALSE OP\_RETURN pattern.
* **OP\_RETURN** (`0x6A`): Standard OP\_RETURN opcode.
* **PUSH4** (`0x04`) followed by `0x41524941` (`b'ARIA'`): 4-byte application identifier.
* **PUSHDATA**: Standard Bitcoin script pushdata encoding of the JSON payload bytes.
  * Payloads ≤ 75 bytes: direct length byte.
  * Payloads 76–255 bytes: `OP_PUSHDATA1` (`0x4C`) + 1-byte length.
  * Payloads 256–65535 bytes: `OP_PUSHDATA2` (`0x4D`) + 2-byte little-endian length.
  * Payloads > 65535 bytes: `OP_PUSHDATA4` (`0x4E`) + 4-byte little-endian length.
* **JSON payload**: UTF-8 encoded JSON object. MUST NOT contain whitespace outside string values.

A transaction MAY contain multiple outputs. Verifiers MUST inspect all outputs and use the first that matches the ARIA prefix.

### 3. EPOCH\_OPEN Payload Schema

```json
{
  "aria_version": "1.0",
  "type": "EPOCH_OPEN",
  "epoch_id": "ep_<unix_timestamp_ms>_<sequence_4digits>",
  "system_id": "<registered_system_identifier>",
  "model_hashes": {
    "<model_id>": "sha256:<64_hex_chars>"
  },
  "state_hash": "sha256:<64_hex_chars>",
  "timestamp": 1742848200,
  "nonce": "<32_hex_chars>"
}
```

**Field definitions:**

| Field          | Type    | Required | Description                                                                                                                                                                |
| -------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aria_version` | string  | ✓        | Protocol version. MUST be `"1.0"` for this specification.                                                                                                                  |
| `type`         | string  | ✓        | MUST be `"EPOCH_OPEN"`.                                                                                                                                                    |
| `epoch_id`     | string  | ✓        | Unique epoch identifier. Format: `ep_<unix_ms>_<seq>`. MUST be monotonically increasing within a `system_id`.                                                              |
| `system_id`    | string  | ✓        | Unique identifier for the AI system. SHOULD match the identifier registered in an ARIA-compatible registry.                                                                |
| `model_hashes` | object  | ✓        | Mapping from `model_id` strings to `"sha256:<hex>"` hashes of the serialized model files. MUST include all models that will execute in this epoch.                         |
| `state_hash`   | string  | ✓        | `"sha256:<hex>"` of the canonical JSON of the system's operational state (configuration, thresholds, routing rules). MUST NOT include personally identifiable information. |
| `timestamp`    | integer | ✓        | Unix timestamp (seconds) at which the epoch opened. MUST be less than the `timestamp` implied by the BSV block containing EPOCH\_CLOSE.                                    |
| `nonce`        | string  | ✓        | 16 cryptographically random bytes encoded as 32 lowercase hex characters. Prevents replay attacks on identical consecutive states.                                         |

**Constraints:**

* `epoch_id` uniqueness is scoped to `system_id`. Two different systems MAY share an epoch\_id.
* `model_hashes` values MUST use lowercase hex and the `"sha256:"` prefix.
* The EPOCH\_OPEN transaction MUST be broadcast and confirmed (or at minimum in the mempool with a valid txid) before any AuditRecord for that epoch is created.

### 4. AuditRecord Schema

An AuditRecord represents a single inference event. AuditRecords are stored locally by the operator and are NOT individually broadcast to BSV. Their hashes are committed collectively via the Merkle root in EPOCH\_CLOSE.

```json
{
  "aria_version": "1.0",
  "record_id": "rec_<epoch_id>_<sequence_6digits>",
  "epoch_id": "ep_<...>",
  "model_id": "<must_be_key_in_EPOCH_OPEN_model_hashes>",
  "input_hash": "sha256:<64_hex_chars>",
  "output_hash": "sha256:<64_hex_chars>",
  "confidence": 0.95,
  "latency_ms": 47,
  "sequence": 0,
  "metadata": {
    "decision_class": "triage_priority_1",
    "custom": {}
  }
}
```

**Field definitions:**

| Field          | Type          | Required | Description                                                                                                                 |
| -------------- | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `aria_version` | string        | ✓        | MUST be `"1.0"`.                                                                                                            |
| `record_id`    | string        | ✓        | Unique record identifier within the epoch.                                                                                  |
| `epoch_id`     | string        | ✓        | MUST match the `epoch_id` in the corresponding EPOCH\_OPEN.                                                                 |
| `model_id`     | string        | ✓        | MUST be a key present in `model_hashes` of the corresponding EPOCH\_OPEN.                                                   |
| `input_hash`   | string        | ✓        | `"sha256:<hex>"` of the canonical JSON of the inference input after PII removal.                                            |
| `output_hash`  | string        | ✓        | `"sha256:<hex>"` of the canonical JSON of the inference output.                                                             |
| `confidence`   | float \| null | ✗        | Model confidence score \[0, 1]. RECOMMENDED when available.                                                                 |
| `latency_ms`   | integer       | ✗        | Inference duration in milliseconds. Default 0.                                                                              |
| `sequence`     | integer       | ✓        | Zero-based index of this record within the epoch. MUST be unique within an epoch. Determines leaf order in the Merkle tree. |
| `metadata`     | object        | ✗        | Arbitrary key-value pairs for domain-specific context.                                                                      |

**PII handling:** Operators MUST identify and remove personally identifiable information fields from the input before computing `input_hash`. The set of PII fields is operator-defined and SHOULD be documented in the system registration. Output data is considered accountability-relevant and SHOULD NOT be PII-filtered unless legally required.

### 5. EPOCH\_CLOSE Payload Schema

```json
{
  "aria_version": "1.0",
  "type": "EPOCH_CLOSE",
  "epoch_id": "ep_<...>",
  "prev_txid": "<64_hex_chars_of_EPOCH_OPEN_txid>",
  "records_merkle_root": "sha256:<64_hex_chars>",
  "records_count": 42,
  "duration_ms": 1498
}
```

**Field definitions:**

| Field                 | Type    | Required | Description                                                                                                                                                                                                                                             |
| --------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aria_version`        | string  | ✓        | MUST be `"1.0"`.                                                                                                                                                                                                                                        |
| `type`                | string  | ✓        | MUST be `"EPOCH_CLOSE"`.                                                                                                                                                                                                                                |
| `epoch_id`            | string  | ✓        | MUST match the `epoch_id` in the corresponding EPOCH\_OPEN.                                                                                                                                                                                             |
| `prev_txid`           | string  | ✓        | BSV txid of the corresponding EPOCH\_OPEN transaction. This is the cryptographic link that makes the chain verifiable.                                                                                                                                  |
| `records_merkle_root` | string  | ✓        | `"sha256:<hex>"` Merkle root of all AuditRecord hashes in this epoch, ordered by `sequence`. For an empty epoch (zero records), MUST be `"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"` (SHA-256 of the empty byte string). |
| `records_count`       | integer | ✓        | Number of AuditRecords in this epoch.                                                                                                                                                                                                                   |
| `duration_ms`         | integer | ✗        | Epoch duration in milliseconds.                                                                                                                                                                                                                         |

### 6. AuditRecord Hash Algorithm

The hash of an AuditRecord is the SHA-256 of its canonical JSON serialization. The following fields are included in the canonical form, in this exact order:

```json
{
  "aria_version": "1.0",
  "confidence": <float_or_null>,
  "epoch_id": "<string>",
  "input_hash": "<string>",
  "latency_ms": <integer>,
  "metadata": <object>,
  "model_id": "<string>",
  "output_hash": "<string>",
  "record_id": "<string>",
  "sequence": <integer>
}
```

Note: keys are sorted alphabetically. This matches the canonical JSON rules in §7.

The hash is computed as:

```
record_hash = "sha256:" + hex(SHA-256(canonical_json_bytes))
```

where `canonical_json_bytes` is the UTF-8 encoding of the canonical JSON string (no whitespace).

### 7. Canonical JSON Serialization

All hashes in ARIA use a deterministic JSON serialization to ensure cross-implementation reproducibility.

**Rules:**

1. **No whitespace**: No spaces, tabs, or line breaks outside string values.
2. **Alphabetically sorted keys**: Object keys sorted by Unicode code point, recursively at all nesting levels.
3. **Float representation**: IEEE 754 double-precision. Trailing zeros removed. No scientific notation unless the magnitude requires it (exponent ≥ 17 or ≤ -4). Up to 17 significant digits.
4. **Null**: Serialized as `null`.
5. **NaN and Infinity**: MUST NOT appear in canonical data. Implementations MUST reject inputs containing NaN or Infinity values.
6. **Array order**: Preserved as-is. Arrays are NOT sorted.
7. **String encoding**: UTF-8. Unicode escape sequences (`\uXXXX`) are permitted for non-ASCII characters.
8. **Boolean**: `true` / `false` (lowercase).

### 8. Merkle Tree Construction

ARIA uses a SHA-256 Merkle tree with second-preimage attack protection following RFC 6962 (Certificate Transparency):

```
leaf_hash(data)     = SHA-256(0x00 || data)
internal_hash(l, r) = SHA-256(0x01 || l || r)
```

Where `||` denotes byte concatenation and `data` is the raw SHA-256 digest bytes (32 bytes) of a record hash string decoded from hex.

**Construction algorithm:**

1. Sort all AuditRecord hashes by their `sequence` field (ascending).
2. Compute leaf hashes: for each record hash `h`, compute `leaf = SHA-256(0x00 || bytes.fromhex(h[7:]))` where `h[7:]` strips the `"sha256:"` prefix.
3. Build the tree bottom-up. If a level has an odd number of nodes, duplicate the last node.
4. The root of the tree with a single leaf is `leaf_hash(record_hash)`.
5. The root of an empty tree is `SHA-256(b"")`.

**Merkle proof format:**

A Merkle proof for a leaf is a list of `(sibling_hash, position)` tuples where `position` is `"left"` or `"right"`, indicating which side the sibling is on relative to the path to the root.

### 9. Epoch Lifecycle

```
Operator side                       BSV
─────────────────────────────────   ──────────────────────────
1. Prepare model_hashes
2. Compute state_hash
3. Generate nonce
4. Broadcast EPOCH_OPEN ──────────► txid_open confirmed/mempool
5. [EPOCH_OPEN txid available]
6. Execute inferences
7. Create AuditRecord per inference
8. Persist records to local storage
9. Compute Merkle root
10. Broadcast EPOCH_CLOSE ────────► txid_close confirmed/mempool
    (prev_txid = txid_open)
```

**Anti-backdating guarantee:** Because EPOCH\_OPEN is broadcast before inferences begin, and BSV blocks include timestamps, no operator can claim a different model version or system state was in effect at decision time. The commitment is public and immutable.

### 10. Verification Algorithm

Given `open_txid` and optionally `close_txid`, a verifier MUST perform the following checks:

**Epoch verification:**

1. Fetch the transaction at `open_txid` and extract the ARIA payload.
2. Verify `payload.type == "EPOCH_OPEN"`.
3. If `close_txid` is not provided, locate it by searching for an EPOCH\_CLOSE transaction whose `prev_txid == open_txid`. Implementations MAY use a local index or require the caller to supply `close_txid` explicitly.
4. Fetch the transaction at `close_txid` and extract the ARIA payload.
5. Verify `payload.type == "EPOCH_CLOSE"`.
6. Verify `close_payload.prev_txid == open_txid`. **If this check fails, the epoch is TAMPERED.**
7. Verify `close_payload.epoch_id == open_payload.epoch_id`. **If this check fails, the epoch is TAMPERED.**

**Record verification** (in addition to epoch verification):

8. Reconstruct the AuditRecord from the provided record data.
9. Verify `record.epoch_id == open_payload.epoch_id`. **If this fails, the record is TAMPERED.**
10. Verify `record.model_id ∈ open_payload.model_hashes`. **If this fails, the record is TAMPERED.**
11. If local storage is available: reconstruct the Merkle tree from all stored records for this epoch and verify that the record's hash is a member (Merkle proof). Verify the reconstructed root matches `close_payload.records_merkle_root`. **If this fails, the record is TAMPERED.**

### 11. Verification Result

Implementations MUST produce a structured verification result with at minimum:

| Field           | Type             | Description                                         |
| --------------- | ---------------- | --------------------------------------------------- |
| `valid`         | boolean          | True if all checks passed.                          |
| `tampered`      | boolean          | True if a cryptographic inconsistency was detected. |
| `epoch_id`      | string           | Epoch identifier from EPOCH\_OPEN.                  |
| `system_id`     | string           | System identifier from EPOCH\_OPEN.                 |
| `model_id`      | string \| null   | Model identifier (record verification only).        |
| `model_version` | string \| null   | SHA-256 of the committed model file.                |
| `decided_at`    | datetime \| null | Datetime from EPOCH\_OPEN timestamp.                |
| `records_count` | integer          | Number of records from EPOCH\_CLOSE.                |
| `merkle_root`   | string           | Merkle root from EPOCH\_CLOSE.                      |
| `error`         | string \| null   | Human-readable error message if not valid.          |

***

### 12. ZK Extension (Optional)

Implementations MAY include a `zk` field in the EPOCH\_CLOSE payload to provide zero-knowledge proof evidence and EU AI Act regulatory claims. When present, the `zk` field MUST conform to the following schema.

#### 12.1 ZKPayload

```json
{
  "zk_enabled": true,
  "claims_count": 3,
  "all_claims_satisfied": true,
  "statement_hash": "sha256:<64_hex_chars>",
  "claims": [<ClaimResult>, ...],
  "aggregate_proof": <AggregateProof | null>
}
```

The `statement_hash` commits to the epoch's regulatory state:

```
statement_hash = SHA256(canonical_json({
  "epoch_id": <str>,
  "claims": [{"claim_type": ..., "params": ..., "satisfied": ...}, ...],
  "aggregate_digest": <str | "">
}))
```

#### 12.2 ClaimResult

```json
{
  "claim_type": "confidence_percentile",
  "params": {"p": 99, "threshold": 0.85},
  "satisfied": true,
  "evidence_hash": "sha256:<64_hex_chars>",
  "human_description": "99th percentile confidence 0.912 ≥ 0.85",
  "eu_ai_act_reference": "Art. 9 §7 — accuracy and robustness requirements",
  "detail": null
}
```

Standard `claim_type` values and their EU AI Act mapping:

| `claim_type`            | Article                  |
| ----------------------- | ------------------------ |
| `confidence_percentile` | Art. 9 §7                |
| `model_unchanged`       | Art. 9 §4                |
| `no_pii_in_inputs`      | Art. 10 §3 + GDPR Art. 9 |
| `output_distribution`   | Art. 12 §1               |
| `latency_bound`         | Art. 14 §2               |
| `record_count_range`    | Art. 12 §1               |
| `all_models_registered` | Art. 11                  |

The `evidence_hash` is computed as:

```
evidence_hash = SHA256(canonical_json({"evidence": sorted(str(v) for v in values)}))
```

where `values` is the set of data values used to evaluate the claim (e.g., the list of confidence scores for `confidence_percentile`).

#### 12.3 AggregateProof

```json
{
  "proofs_merkle_root": "sha256:<64_hex_chars>",
  "n_proofs": 42,
  "aggregation_scheme": "merkle",
  "aggregate_digest": "sha256:<64_hex_chars>"
}
```

* `proofs_merkle_root`: Merkle root of per-record ZK proof digests.
* `aggregation_scheme`: `"merkle"` (production), `"nova"` (future), `"plonk_recursive"` (future).
* `aggregate_digest`: SHA-256 of the raw aggregate bytes (the bytes themselves are NOT embedded on-chain due to size constraints).

#### 12.4 Proving tiers

Implementations MAY use any of the following proving tiers:

| Tier         | Method                       | Suitable for             |
| ------------ | ---------------------------- | ------------------------ |
| `full_zk`    | Halo2/KZG circuit (EZKL)     | ONNX models ≤ 10M params |
| `commitment` | HMAC-SHA256 keyed commitment | Any model size           |
| `tee`        | Intel SGX remote attestation | 1B+ parameter models     |

The tier is a per-proof attribute stored locally. Only the aggregate Merkle root is committed on-chain.

***

## Security Considerations

### What ARIA guarantees

1. **Pre-commitment immutability**: Once EPOCH\_OPEN is confirmed in a BSV block, the committed `model_hashes` and `state_hash` cannot be altered. Any claim that a different model version was used is verifiably false.
2. **Merkle root tamper detection**: Any modification to any single record in an epoch — adding, removing, or altering — invalidates the Merkle root. This is detectable without access to all records (Merkle proof suffices).
3. **Chronological ordering**: The BSV block timestamp of EPOCH\_OPEN provides an external lower bound on the inference time. EPOCH\_CLOSE must reference EPOCH\_OPEN via `prev_txid`, establishing a directed chain.
4. **Operator key compromise does not alter history**: A compromised private key can create new fraudulent epochs but cannot alter already-confirmed transactions.

### What ARIA does NOT guarantee

1. **Input/output correctness**: ARIA commits hashes of inputs and outputs, not the inputs/outputs themselves. An operator could hash a different input than the actual one. ARIA cannot detect this without access to the original data.
2. **Model quality or safety**: ARIA verifies which model produced an output. It does not verify that the model is safe, accurate, or unbiased.
3. **Real-time tamper detection**: ARIA detects tampering during post-hoc verification, not in real time.
4. **BSV network availability**: If BSV becomes unavailable, epochs cannot be committed. Implementations MUST queue epochs and retry.

### Threat model

| Threat                                     | ARIA defense                                          | Residual risk                                   |
| ------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------- |
| Operator fabricates records after the fact | `prev_txid` chain + Merkle root is immutable on BSV   | None — detectable                               |
| Operator claims different model version    | `model_hashes` in EPOCH\_OPEN is immutable            | None — detectable                               |
| Operator alters a single record            | Merkle root mismatch                                  | None — detectable                               |
| Operator hashes wrong input                | Not detectable without original data                  | Requires regulatory access to raw logs          |
| Key compromise, new fraudulent epochs      | New epochs have different epoch\_id / timestamps      | Detectable by auditors monitoring epoch history |
| BSV chain reorganisation                   | Uses SPV confirmation (6+ blocks for high-stakes use) | Mitigated by waiting for confirmations          |

***

## Test Vectors

### Canonical JSON

Input object:

```json
{"b": 2, "a": 1, "c": {"z": 3, "x": 1}}
```

Canonical form: `{"a":1,"b":2,"c":{"x":1,"z":3}}`

SHA-256: `a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3` (This is the hash of the canonical string above, encoded as UTF-8.)

### AuditRecord hash

Given:

```json
{
  "aria_version": "1.0",
  "record_id": "rec_ep_1742848200000_0001_000000",
  "epoch_id": "ep_1742848200000_0001",
  "model_id": "triage",
  "input_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "output_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "confidence": null,
  "latency_ms": 0,
  "sequence": 0,
  "metadata": {}
}
```

Canonical JSON (keys sorted, no whitespace):

```
{"aria_version":"1.0","confidence":null,"epoch_id":"ep_1742848200000_0001","input_hash":"sha256:aaaa...","latency_ms":0,"metadata":{},"model_id":"triage","output_hash":"sha256:bbbb...","record_id":"rec_ep_1742848200000_0001_000000","sequence":0}
```

### Merkle root (2 records)

Leaf 0 hash (64 hex chars): `h0` Leaf 1 hash (64 hex chars): `h1`

```
leaf0 = SHA-256(0x00 || bytes.fromhex(h0[7:]))
leaf1 = SHA-256(0x00 || bytes.fromhex(h1[7:]))
root  = SHA-256(0x01 || leaf0 || leaf1)
```

### OP\_RETURN script

For payload `{"type":"EPOCH_OPEN"}` (21 bytes UTF-8):

```
00 6a 04 41524941 15 7b2274797065223a22455043...
```

* `00` = OP\_FALSE
* `6a` = OP\_RETURN
* `04 41524941` = PUSH4 + `ARIA`
* `15` = direct length byte (21)
* `7b...` = UTF-8 JSON bytes

***

## Reference Implementation

The canonical reference implementation of BRC-122 is the `aria-bsv` Python package, available at:

```
https://github.com/JuanmPalencia/aria-bsv
```

The reference implementation includes:

* `aria/core/hasher.py` — canonical JSON and SHA-256 hashing
* `aria/core/merkle.py` — RFC 6962 Merkle tree with second-preimage protection
* `aria/core/record.py` — AuditRecord schema and hash algorithm
* `aria/core/epoch.py` — EpochManager: EPOCH\_OPEN / EPOCH\_CLOSE lifecycle
* `aria/verify.py` — Independent Verifier with WhatsOnChain integration
* `aria/auditor.py` — High-level InferenceAuditor (5-line integration API)
* `aria/zk/` — ZK extension: tiered prover, EU AI Act claims DSL, proof aggregation, EpochStatement

***

## Copyright

This BRC is placed in the public domain. Authors waive all copyright and related rights.


# Registry-Free Typed Content Anchor with On-Chain Code Provenance

RexStarBSV (<294282606+RexStarBSV@users.noreply.github.com>)

## Abstract

This BRC specifies a **registry-free typed content anchor**: a deterministic scheme for committing to *typed* content with a single 32-byte anchor and no central registry. An anchor is `SHA-256(INKAN_DOMAIN || u8(len(kind)) || kind || data)`: a fixed domain string separates these digests from every other SHA-256 usage, a one-byte length prefix makes the `kind`/`data` boundary unambiguous for all inputs, and the `kind` string is *inside* the digest so two anchors of different types never collide even when the underlying `data` is identical. Because the type is carried in the digest and the type namespace is content-derived, third parties assign their own kinds and tiers (`[namespace:]name[@t1|t2|t3]`) without asking anyone, and the same kind renders and verifies identically on every device, forever. Anchors serialize to self-identifying **`ik1` codes** whose version prefix keeps the construction hash-agile. The same primitive produces **software/build provenance**: a per-adopter tamper fingerprint over running code, published on-chain as an immutable data record or a 1-of-1 (1Sat) code-provenance ordinal that any client re-verifies against the build it is executing. This BRC defines the anchor construction, the code format, the open kind system, the tamper fingerprint, and the on-chain provenance record, in enough detail for an independent second implementation to interoperate byte-for-byte.

In plain terms: this standard describes a way to take any piece of digital content (an address, a document, a contract, a software release) and produce a short, permanent fingerprint for it that also records what kind of thing it is. Two different kinds of content can never share a fingerprint, even when their raw bytes are identical, and anyone can invent a new kind for their own application without asking a registry, a committee, or this document. The fingerprint writes out as a compact text code, renders as a deterministic visual seal that looks the same on every device forever, and can be published on the BSV chain as a permanent, timestamped record. The same mechanism lets a software project publish the fingerprint of a released build on chain, so any user can check that the code they are running is exactly the code that was released. The scheme is published as the **Inkan Standard v1.0** (frozen) by RexStarBSV, with a complete Python reference implementation and JavaScript and Kotlin ports proven byte for byte against a shared vector suite for the layers each implements.

## Motivation

Bitcoin SV applications routinely need to commit to a piece of content (an address, an identity key, an account, a contract, a build artifact) in a way that is (a) deterministic across languages and devices, (b) *typed*, so a commitment to one kind of thing cannot be confused with a commitment to another, and (c) coordinated without a registry, so any party can introduce a new type without a gatekeeper.

The existing BRC stack does not provide this primitive:

* **BRC-42 / BRC-43 (BSV key derivation, invoice numbering / protocol IDs)** namespace *keys* by protocol and key IDs that two parties must already share. They are a coordination convention for derivation, not a general typed content-commitment, and they presuppose the counterparties agree on the protocol string out of band.
* **BRC-100 (wallet-to-application interface)** and its `createAction` give an application a way to *write* arbitrary bytes to the chain, but say nothing about how to canonically commit to typed content before writing it. Two apps that both "hash the thing and put it in an `OP_RETURN`" produce incompatible, type-blind, domain-unseparated digests.
* **Overlay-network discovery (topic managers / lookup services)** solves coordination by introducing exactly the registry this scheme is designed to avoid: a topic string must be recognized by an overlay to be meaningful.
* **Visual-hash / identicon conventions** (LifeHash, jdenticon, blockies) are presentation-only. They render a digest but define neither a typed, domain-separated digest nor a self-describing, version-tagged code, and none binds the *type* of the content into the commitment.

No existing BRC covers a **registry-free typed content commitment** or **software/build provenance published on-chain**. A bare `SHA-256(content)` has three defects this BRC fixes: it is not domain-separated (the same digest can mean anything, inviting cross-protocol confusion), it is not type-bound (a hash of an "address" equals a hash of the same bytes labelled "contract"), and it has no in-band version of the hash function (a future migration off SHA-256 is silent and dangerous). The scheme defined here closes all three while remaining a plain SHA-256 that any language computes with its standard library.

## Relationship to related work

NotaryHash (Gregory Ward, SmartLedger; merged as BRC-220) and this proposal overlap in two ways: both anchor a content commitment on chain, and both derive that commitment from a deterministic length-prefixed binary encoding, so any language reproduces identical bytes. They answer different questions. NotaryHash standardizes a notarization certificate: a signer proves, SPV-verifiable against block headers alone, that they signed one specific `SHA-256(content)` at the block where it was mined, and the document itself never leaves the client. That is the right tool when what matters is who signed a given hash and when. This proposal standardizes a registry-free *typed* content commitment. The `kind` of the thing being committed to is folded into the digest by a fixed domain string and a one-byte length prefix, so a commitment to an address and a commitment to a contract over identical bytes produce different anchors that cannot collide, and no central authority allocates or arbitrates the type namespace. Around that anchor it adds a self-versioning code format that keeps the construction hash-agile, plus a per-adopter fingerprint used for running-code and build provenance. The two layers compose cleanly: a typed anchor computed here can itself be the hash a NotaryHash certificate signs. Cite NotaryHash for the bare signed-hash anchoring case; the contribution here is the typed-commitment and type-system layer above it.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as in RFC 2119.

### 1. Constants

All multi-byte integers below are big-endian unless stated otherwise. `u8(n)` denotes the single unsigned byte `n` (`0 ≤ n ≤ 255`). `||` denotes byte concatenation. Text is encoded to bytes with UTF-8 (RFC 3629). "NFC" denotes Unicode Normalization Form C (UAX #15). `hex(x)` denotes lower-case, zero-padded hexadecimal.

| Name             | Value                            | Bytes (hex)                                  | Role                                     |
| ---------------- | -------------------------------- | -------------------------------------------- | ---------------------------------------- |
| `INKAN_DOMAIN`   | ASCII `"inkan/v1"` + `0x00`      | `69 6e 6b 61 6e 2f 76 31 00`                 | glyph/anchor domain (9 bytes)            |
| `CODE_PREFIX`    | `"ik1"`                          | `69 6b 31`                                   | self-identifying code prefix             |
| `SHORT_CODE_LEN` | `16`                             | n/a                                          | hex chars in a short code body (64 bits) |
| `KIND_DOMAIN`    | ASCII `"inkan/kind/v1"` + `0x00` | `69 6e 6b 61 6e 2f 6b 69 6e 64 2f 76 31 00`  | open-kind derivation domain (14 bytes)   |
| `FP_SUFFIX(v)`   | `"/inkan/v" + dec(v) + 0x00`     | e.g. `v=1` → `2f 69 6e 6b 61 6e 2f 76 31 00` | tamper-fingerprint domain suffix         |
| `MARKER`         | ASCII `"INKANC1"`                | `49 4e 4b 41 4e 43 31`                       | on-chain signed-record marker (7 bytes)  |

Open-kind resolution constants (§4):

* `TIER_CELLS = { "t1": 72, "t2": 48, "t3": 24 }`; default tier is `t2`.
* `KMAX = { 72: 2, 48: 4, 24: 7 }`.
* `ORBIT_REPS = (0, 1, 3, 5, 7, 9, 11, 13, 15, 21, 23, 27, 31, 63)`, 14 rotation-distinct 6-bit frame representatives.

`INKAN_DOMAIN`, `CODE_PREFIX`, and `KIND_DOMAIN` are permanent. An implementation MUST NOT alter any of them; changing `INKAN_DOMAIN` or `CODE_PREFIX` invalidates every anchor ever computed and is a new version namespace, not a revision (§9).

### 2. The anchor

Given `data` (an octet string, or text) and `kind` (a string):

1. If `data` is text, set `data_bytes = utf8(NFC(data))`. If `data` is raw octets, set `data_bytes = data` **verbatim** (raw bytes MUST NOT be normalized, they are not text).
2. Set `kind_bytes = utf8(NFC(kind))`. The implementation MUST reject `kind` with `len(kind_bytes) > 255`.
3. The **glyph seed** is:

```
seed = INKAN_DOMAIN || u8(len(kind_bytes)) || kind_bytes || data_bytes
```

4. The **anchor** is `SHA-256(seed)`, exactly 32 octets (FIPS 180-4).

The anchor is the sole basis for trust: it is what a receipt commits to, the input to every presentation, and the value re-derived during verification. Any visual or other presentation of an anchor is a convenience; the anchor, never the presentation, is trusted.

**Why the length prefix (normative rationale).** Without `u8(len(kind_bytes))` between `kind_bytes` and `data_bytes`, the concatenation `kind_bytes || data_bytes` is ambiguous: `kind="address", data="X"` and `kind="", data="addressX"` produce the same trailing bytes and therefore the same anchor. The one-byte length prefix makes the boundary unambiguous for every possible `(kind, data)` pair (see Test Vector 5). An implementation MUST include it exactly as specified.

**Why the type is in the digest (registry-free typing).** Because `kind_bytes` is inside `seed`, an anchor for `(data, kindA)` and an anchor for the *same* `data` under `kindB` differ whenever `kindA ≠ kindB`. Type collisions are therefore cryptographically excluded rather than administratively prevented, so no allocation authority or registry is required to keep two adopters' types apart.

### 3. Codes (`ik1`)

An anchor serializes to a self-identifying ASCII string:

```
full = CODE_PREFIX || hex(anchor) # "ik1" + 64 hex chars = 67 chars
short = CODE_PREFIX || hex(anchor)[:16] # "ik1" + 16 hex chars = 19 chars
```

* The literal `ik` marks an anchor code; the trailing digit is the **code-format / hash version**. `1` denotes SHA-256 over glyph domain `inkan/v1`.
* The hex body of a code is a genuine prefix of the full anchor, so any code (including a short code) is auditable by recomputing the anchor and checking the prefix. The short code's 16 hex chars are 64 bits: collision-safe to billions of items and forgery-resistant, while remaining short enough for filenames and display. A short code is a **label only**; trust decisions MUST use the full 32-byte anchor.
* **Version handling (MUST).** A reader parsing a code MUST verify the version prefix. If a code begins with `ik` but not with a version this implementation supports (e.g. a future `ik2`), the reader MUST reject it rather than assume SHA-256. The reference parse is: reject if the string does not start with `ik`; reject if it does not start with the supported `CODE_PREFIX`; otherwise the digest hex is the remainder after `CODE_PREFIX`. This is what keeps the anchor hash-agile: a hash migration is a new prefix (`ik2…`), unambiguously distinguishable at parse time.

### 4. Kinds, open, tier-weighted, third-party assignable

A `kind` is any UTF-8 string of at most 255 bytes. By convention it is written `[namespace:]name[@tier]` (e.g. `address`, `acme:invoice@t3`, `acme:vault@t1`) but the anchor (§2) treats it as an opaque byte string, so any string that encodes as valid UTF-8 is a valid kind. Namespacing (`ns:`) lets adopters avoid clashing without coordination; because the whole string is inside the digest, `acme:invoice` and `beta:invoice` are distinct anchors even over identical data.

A value that cannot be encoded as valid UTF-8 is **not** a kind: an implementation MUST reject it and MUST NOT substitute replacement characters. Substitution is not a lenient reading of this rule, it is a collision. Every unpaired surrogate encodes to the same replacement bytes, so two distinct kinds that differ only in their ill-formed portion would derive one anchor, and a commitment to one would verify against the other.

Presentations (the visual seal, §5) require a `kind` to resolve to geometry parameters `(cells, k, motif)`, where `cells ∈ {24, 48, 72}` is the tier's cell count, `k ∈ 1..7` is a rotational-symmetry order, and `motif` is a 6-bit frame code. Resolution is:

**4.1 Reserved kinds.** If `kind` is a key of the reserved table below, use its curated entry directly:

| kind                   | cells | k | motif |
| ---------------------- | ----- | - | ----- |
| `""` (empty / default) | 48    | 2 | 63    |
| `identity`             | 48    | 4 | 56    |
| `account`              | 48    | 3 | 36    |
| `address`              | 72    | 1 | 51    |
| `vault`                | 72    | 1 | 61    |
| `signer`               | 72    | 2 | 54    |
| `contract`             | 72    | 2 | 60    |
| `code`                 | 24    | 7 | 42    |
| `report`               | 24    | 6 | 44    |
| `collection`           | 24    | 5 | 48    |
| `backup`               | 24    | 4 | 32    |
| `custom/24`            | 24    | 1 | 52    |
| `custom/48`            | 48    | 1 | 52    |
| `custom/72`            | 72    | 1 | 52    |

**4.2 Derivation (any other kind).** If `kind` is not reserved, derive `(cells, k, motif)` deterministically:

1. **Tier.** If `kind` ends with the literal suffix `@t1`, `@t2`, or `@t3`, that selects the tier; otherwise the tier defaults to `t2`. (The reference tests the suffixes in the order `t1, t2, t3` and takes the first match.) `cells = TIER_CELLS[tier]`.
2. **Kind hash.** `kh = SHA-256(KIND_DOMAIN || utf8(kind))`, where `kind` is the kind string **as provided** (see the normalization note below).
3. **Symmetry.** `k = 1 + (kh[0] mod KMAX[cells])`.
4. **Frame motif.** `motif = ORBIT_REPS[ kh[1] mod 14 ]`.

Because the tier is part of the kind string and the kind is part of the anchor, the same kind renders identically everywhere and namespaced kinds never collide between adopters, **no registry is needed** for either the anchor or the presentation.

> **Normalization note (MUST-for-interop).** The anchor (§2) NFC-normalizes `kind` before hashing; the reference presentation-derivation in §4.2 hashes the kind bytes **as provided** (no explicit NFC step) and performs the reserved-table lookup and suffix test on the string as provided. For an ASCII kind (all reserved kinds are ASCII) the two layers are identical. To keep the anchor and the presentation consistent for non-ASCII kinds, callers SHOULD pass kinds already in NFC. An implementation aiming for byte-exact parity with the reference presentation MUST NOT insert an NFC step into §4.2.

**Tiers and exposure.** Tiers `t1/t2/t3` map to cell profiles `72/48/24`. More cells means more visible entropy and a costlier forgery (§ Security Considerations); value-at-risk kinds (e.g. `address`, `vault`) SHOULD use `t1`. The resolution parameters affect only the presentation; **the anchor never depends on `(cells, k, motif)`**, it depends only on the exact `kind` string. Two distinct kind strings that happen to resolve to the same `(cells, k, motif)` still produce distinct anchors.

### 5. Deterministic presentation (the seal)

An anchor MAY be rendered to a deterministic visual **seal** so a human notices when a trusted value changes. The seal is optional to this BRC's interoperability core (anchor + code + kind, §2-§4). Its full pixel-level construction is out of scope here; an implementation that renders seals MUST match the reference conformance vectors byte-for-byte at the RGB level. The seal's normative contract is:

* **Deterministic and float-free.** Geometry and palette are integer tables, so all implementations agree byte-for-byte by construction, not by luck. Seal renderer version is `SEAL_VERSION = 3`.
* **The kind selects symmetry and frame.** `(cells, k, motif)` from §4 fix the rotational symmetry order and the 6-bit frame, so a seal of one kind cannot be confused with a seal of another.
* **A visible distance floor.** Interior cells carry 2 bits each from bit-interleaved extended Golay \[24,12,8] blocks; any two distinct seals of the same profile differ in at least 8 cells (Golay minimum weight), a floor verified tight over all 4096 code words.
* **Byte-deterministic PNG.** The canonical seal is an 8-bit indexed PNG whose chunks are exactly `IHDR`, `PLTE`, `IDAT`, `IEND`, with **no ancillary chunks**. The cross-language invariant is the decoded RGB pixel content, not the compressed bytes (deflate output is per-implementation). A **tagged** export additionally embeds `iTXt` chunks (keyword `inkan-code` carrying the full `ik1` code, keyword `inkan-kind` carrying the kind) making the image self-describing. The embedded code is an **untrusted hint**: a verifier MUST recompute the anchor and compare pixels, treating the tag only as a cross-check.

`iTXt` body layout used by the reference tagged export, for a `keyword` and UTF-8 `text`:

```
keyword(latin-1) || 0x00 || 0x00(comp flag) || 0x00(comp method) || 0x00(empty lang) || 0x00(empty translated keyword) || utf8(text)
```

**Verification of a presented seal.** To verify a seal claimed to be `(data, kind)`: recompute the anchor and the canonical seal; decode the presented PNG to native RGB; if it is an exact integer multiple of the canonical size, integer-downscale it, otherwise reject; compare pixel-for-pixel. Result is GENUINE iff equal. Re-encoding and integer upscales are tolerated; a resampled, cropped, or otherwise different image is rejected. Verification MUST NOT trust embedded metadata.

**Downscaling is a check, not a resize.** When the presented image is an integer upscale by factor *s*, the verifier MUST confirm that every pixel of each *s×s* block is identical before reducing that block to one pixel. Sampling one pixel per block and discarding the rest satisfies a pixel-for-pixel comparison of the reduced image while leaving *s²−1* of every *s²* pixels unexamined and under the presenter's control: at *s* = 8 that is 63 of every 64 pixels, 98.44% of what the viewer actually sees, free to carry a different mark entirely. A block whose pixels are not uniform MUST be reported as a verification failure in its own right, never as a size error and never as GENUINE.

**Rendering-affecting chunks MUST be rejected.** A presented PNG carrying `tRNS`, `gAMA`, `iCCP`, `sRGB`, `cHRM`, `sBIT`, `bKGD`, or the APNG chunks `acTL`, `fcTL`, `fdAT` MUST be rejected. These change what a conformant viewer draws without changing the stored pixel values the comparison reads, so an image that is byte-identical under comparison can present as a different mark. The constraint above on the canonical seal binds what a writer emits; this binds what a verifier accepts, and the two are not the same requirement. Chunks that cannot alter rendering (`tEXt`, `iTXt`, `pHYs`, `tIME`) remain tolerated.

### 6. Tamper fingerprint (build / running-code provenance digest)

The per-adopter tamper fingerprint of an octet payload (a build's bytes, an asset, a config) is:

```
domain_prefix = u8(len(adopter_bytes)) || adopter_bytes || FP_SUFFIX(version)
 = u8(len(adopter_bytes)) || adopter_bytes || "/inkan/v" || dec(version) || 0x00
fingerprint = SHA-256(domain_prefix || payload)
```

where `adopter_bytes = utf8(NFC(adopter))`, `len(adopter_bytes) ≤ 255` (reject otherwise), and `version` defaults to `1`. `fingerprint_hex` is the lower-case hex of the 32-byte digest.

The length-prefixed, per-adopter domain namespaces each project: identical payloads yield **distinct** fingerprints across adopters while every adopter shares the same universally verifiable scheme, and no adopter needs to register its name. A one-byte change in `payload` changes the whole digest (avalanche). This fingerprint is the digest used for **software/build provenance**: the fingerprint of the running build.

### 7. On-chain provenance

An anchor or a fingerprint (§2, §6) MAY be committed to the chain to obtain an immutable, timestamped, non-retractable record. This BRC defines two on-chain forms; both are self-verifying (a client re-derives everything offline and trusts the chain only for immutability and time-ordering).

**7.1 Signed-record data output (reference `OP_RETURN` form).** A signed statement (the reference case is a warrant/compromise **canary**, but the container is general) is carried as a data output whose locking script is:

```
OP_RETURN
push( MARKER) # "INKANC1", 7 bytes
push( canonical_record_bytes) # §7.2
push( der_signature) # secp256k1 ECDSA, DER
push( public_key) # 33-byte compressed secp256k1 point
```

Each `push(blob)` is minimal Bitcoin script pushdata: for `n = len(blob)`,

* `n < 0x4C` → `u8(n) || blob`
* `0x4C ≤ n ≤ 0xFF` → `0x4C || u8(n) || blob`
* `0x100 ≤ n ≤ 0xFFFF` → `0x4D || u16le(n) || blob`
* otherwise → `0x4E || u32le(n) || blob`

A reader parses the script by matching `OP_RETURN` then reading four pushes in order; it MUST reject the output if the first push is not exactly `MARKER` or if any push is truncated. Everything a verifier needs (the record, the signature, and the public key) is on-chain, so the output is checked entirely offline (§7.3). The reader MUST NOT trust the parsed bytes; it re-verifies them.

**7.2 Canonical record and signature.** A signed record is a JSON object serialized canonically: keys sorted lexicographically, no insignificant whitespace (`separators` `,` and `:`), UTF-8, non-ASCII left un-escaped. The reference canary record has fields:

| field          | type       | meaning                                         |
| -------------- | ---------- | ----------------------------------------------- |
| `v`            | int        | record version (`1`)                            |
| `domain`       | string     | operator/publisher identifier                   |
| `seq`          | int        | monotonic sequence number                       |
| `date`         | string     | `YYYY-MM-DDTHH:MM:SSZ` (UTC)                    |
| `block_height` | int        | a recent chain-tip height (anti-backdate proof) |
| `block_hash`   | hex string | that tip's block hash                           |
| `statement`    | string     | the human-readable assertion                    |

The message to sign/verify is `SHA-256(canonical_record_bytes)`. The signature scheme is secp256k1 ECDSA, DER-encoded, deterministic per RFC 6979, low-S. A signature-algorithm identifier is reserved for a future post-quantum migration. The `block_height`/`block_hash` pair binds the record to a chain tip so it cannot have been pre-signed before that block existed.

**7.3 Status.** A verifier reports exactly one of:

* `INVALID`, the signature does not verify against the published key over `SHA-256(canonical_record_bytes)`.
* `STALE`, signature valid but the record is outside the freshness window: with `now`, `max_age_days` (reference default 35), and `date` parsed as UTC, the record is fresh iff `-86400 ≤ (now − date_seconds) ≤ max_age_days · 86400` (i.e. not expired and not implausibly future-dated by more than one day).
* `OK`, signature valid and fresh.

For a canary, absence, `STALE`, or `INVALID` is itself the warning.

**7.4 1-of-1 ordinal form (code-provenance ordinal).** A build's provenance MAY instead be published as a 1-of-1 (1Sat) ordinal whose inscription content is the canonical **tagged** seal PNG (§5) of the build's fingerprint (§6). The `iTXt` `inkan-code` / `inkan-kind` tags make the inscription self-describing; the ordinal's single-satoshi custody model conveys and transfers the mark. The inscription envelope and transfer follow the 1Sat Ordinals convention (referenced, not redefined here). A client verifies a running build by recomputing the build's fingerprint, forming its `ik1` code, and comparing against the on-chain code-provenance ordinal for that release; the displayed seal *is* the on-chain-committed mark, so a tampered build cannot reproduce it.

**7.5 Broadcast interoperability (BRC-100, informative).** The reference publishes §7.1 outputs through a BRC-100 wallet: it runs `getVersion → getNetwork → isAuthenticated → getPublicKey → createAction`, funding a single `OP_RETURN` output (`satoshis: 0`) carrying the §7.1 script. The wallet only funds and broadcasts; it never signs the record (the operator signs offline with the bare secp256k1 key), so verification is never coupled to a wallet. An implementation MAY use any broadcast path; the on-chain bytes (§7.1) are what matters. Implementations that broadcast SHOULD default to a test network and require explicit operator opt-in for mainnet, so real funds are the last step, never the first.

### 8. Verification requirements (summary)

* An anchor is re-derived, never received: a verifier presented with `(data, kind)` and a claimed code/seal MUST recompute `SHA-256(seed)` per §2 and compare, and MUST NOT trust any embedded code or metadata.
* A code MUST be version-checked before its body is interpreted as a SHA-256 digest (§3).
* A value that is not valid UTF-8 MUST be rejected as a kind, never substituted; substitution collapses distinct kinds onto one anchor (§4).
* An integer upscale MUST be reduced only after confirming every pixel of each block is identical; sampling one pixel per block leaves the rest unexamined and presenter-controlled (§5).
* A presented image carrying a rendering-affecting ancillary chunk MUST be rejected, because it changes what is drawn without changing the pixels compared (§5).
* An on-chain record MUST be re-verified offline (signature + freshness) after parsing; the chain is trusted only for immutability and timestamp ordering (§7).

### 9. Versioning and parity

* **Anchor** (`inkan/v1`, SHA-256, code `ik1`): permanent. A new hash is a new version namespace (`ik2…`), not a revision. Changing the anchor domain invalidates every anchor ever computed.
* **Presentation** (`SEAL_VERSION`): swappable without touching any anchor. Swapping a presentation never invalidates an anchor, the property that lets an on-chain-anchored digest outlive any renderer. Two surfaces that compare seals MUST share `SEAL_VERSION`.
* **Fingerprint domain** (`FP_SUFFIX` version): per-adopter and independently versioned.

## Test Vectors

All values below are produced by the reference implementation and are reproducible with a SHA-256 and UTF-8 alone (except the ECDSA signature in TV7).

### TV1, anchor of text, default kind

```
data = "Hello" (text) kind = ""
seed (hex) = 696e6b616e2f7631000048656c6c6f
 = INKAN_DOMAIN || 00 (len kind = 0) || "Hello"
anchor = ae63a28a7b3fbae640f24ab4003d285a453e5493b89741326a7fbcc36a2a6a44
code = ik1ae63a28a7b3fbae640f24ab4003d285a453e5493b89741326a7fbcc36a2a6a44
short code = ik1ae63a28a7b3fbae6
resolve_kind("") = (cells=48, k=2, motif=63) [reserved]
```

### TV2, anchor of a typed address (reserved kind `address`)

```
data = "1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2" kind = "address"
seed (hex) = 696e6b616e2f7631000761646472657373314276424d53455973745765747154466e354175346d3447466737784a614e564e32
 = INKAN_DOMAIN || 07 (len "address") || "address" || "1BvB…VN2"
anchor = 12cf1aaca7ef50793d990e94c9ae889af6f9e8fdfc28b2f9a72d4028bde05dcd
code = ik112cf1aaca7ef50793d990e94c9ae889af6f9e8fdfc28b2f9a72d4028bde05dcd
resolve_kind("address") = (cells=72, k=1, motif=51) [reserved, tier t1]
```

### TV3, open-kind derivation (no registry): `acme:invoice@t3`

```
kind = "acme:invoice@t3" (not reserved)
tier = t3 (suffix "@t3") -> cells = 24
kh = SHA-256( "inkan/kind/v1\0" || "acme:invoice@t3")
kh[0] = 137, kh[1] = 93
k = 1 + (137 mod KMAX[24]=7) = 1 + 4 = 5
motif = ORBIT_REPS[ 93 mod 14 = 9 ] = 21
resolve_kind("acme:invoice@t3") = (cells=24, k=5, motif=21)
```

This resolves identically on any device with no allocation authority, demonstrating the registry-free kind system. (`myapp:thing` → `(48, 4, 11)` and `acme:vault@t1` → `(72, 2, 27)` resolve the same way.)

### TV4, NFC normalization stability

```
kind entered as "café" with é = U+00E9 (NFC)
kind entered as "café" with e + U+0301 (NFD)
data = "x"
Both anchors = ik188858a890fa01bf5a463e0e4ca0181d18fe261f34799fe5c5d0ab76e0647f6af (equal)

Derived kind "café@t2" (NFC): utf8 = 63 61 66 c3 a9 40 74 32 (8 bytes)
resolve_kind("café@t2") = (cells=48, k=3, motif=1)
```

### TV5, length-prefix collision resistance

```
kind = "address", data = "X" -> ik1b49a32c4d99c6b80b331223166711da0a91105d1aea989430b8dc22e073c97fd
kind = "", data = "addressX" -> ik13780ad6190697cc754df1159766f9c9781aa610529d0654a197a9a31c691a7f7
distinct: TRUE
```

Without the `u8(len(kind))` prefix both would hash `… "addressX"` and collide. The length prefix (`07` vs `00`) makes the boundary unambiguous.

### TV6, tamper fingerprint / build provenance (per-adopter namespacing)

```
domain_prefix("acme", v=1) = 04 61636d65 2f696e6b616e2f7631 00
 = 0461636d652f696e6b616e2f763100
fingerprint("build-bytes-v1", adopter="acme") = d88657e80e283d5a44bd29b83cf8a93ec60729ec635e642b5454b8829bd3e3c6
fingerprint("build-bytes-v1", adopter="otherapp") = 1847961553e04828204efb7322f1763b8ade6faa14f6f78e30b43957a0c24ab1
```

Same payload, different adopter → different digest, with no registration.

### TV7, on-chain signed record and `OP_RETURN` script (§7)

```
record (fields) = { v:1, domain:"acme", seq:7, date:"2026-06-30T00:00:00Z",
 block_height:870000,
 block_hash:"00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff",
 statement:"No warrant, gag, key compromise or coercion." }

canonical_record_bytes (222 bytes, keys sorted, no whitespace) =
 {"block_hash":"00ff...00ff","block_height":870000,"date":"2026-06-30T00:00:00Z",
 "domain":"acme","seq":7,"statement":"No warrant, gag, key compromise or coercion.","v":1}
message_hash = SHA-256(canonical_record_bytes)
 = 7b0991a1ab13b4b17384e1c6acd0017edb77bd563cdea59cdc776993d8c99362

pubkey = 031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f
der_sig = 3045022100d336085483c2dea86794ef3404d7dc7beef56368fd380000e4d0d8f3ac3fe50c
 02200a4fff49ba1173d1b66f8d4a335cb587c221db79280df23fc2b495b081c65e11
verify(record, der_sig, pubkey) = true (secp256k1 ECDSA over message_hash)

OP_RETURN script (339 bytes) begins:
 6a OP_RETURN
 07 494e4b414e4331 push "INKANC1"
 4c de 7b22626c6f636b5f68617368... push canonical_record_bytes (0xde = 222 bytes)
... push der_sig, push pubkey
```

Round-tripping the script recovers `record`, `der_sig`, and `pubkey` for offline re-verification. The keys in this vector are deterministic **test** values; production issuer keys and the first live record are minted at launch and never appear in test material.

## Implementations

The reference implementation is **Inkan**, an offering by RexStarBSV: a frozen v1.0 standard carrying three reference implementations and a pinned conformance-vector suite, from which every value in the Test Vectors above is reproduced.

* **Reference implementation** (Python): `seed`/anchor and code construction (`inkan_digest`, `inkan_code`, `inkan_short_code`, `code_to_digest_hex`), open-kind resolution and the Seal v3 renderer (`resolve_kind`, `seal_rgb`, `seal_png`, reserved `KINDS` table and derivation constants), the tamper fingerprint (`domain_prefix`, `fingerprint_digest`, `fingerprint_hex`), the signed-record/canary module (`canonical_bytes`, `message_hash`, `sign`, `verify`, `status`), and the on-chain anchoring module (`build_canary_script`, `parse_canary_script`, and a BRC-100 `createAction` broadcast path with a testnet default guard). Stdlib-only for everything except secp256k1 signing/verification.
* **JavaScript port**: a dependency-free, synchronous, browser-and-Node port of the anchor, code, kind resolution, seal renderer, and fingerprint, proven byte-for-byte against the Python reference by the shared vectors.
* **Kotlin port**: the mobile/desktop rendering path, likewise held to the shared vectors.
* **Conformance vectors**: a fingerprint/canary/glyph vector set and a Seal v3 vector set (sampled fields, RGB SHA-256s, and constructed minimum-distance pairs) let any implementation prove parity. An implementation conforms iff it reproduces both.
* **Standalone verifier and creator**, offered by RexStarBSV: a command-line verifier with `seal`, `fingerprint`, `canary`, and `selftest` subcommands (the verifier reproduces the pinned vectors before it trusts itself), plus creation of anchors, codes, and seals from any `(data, kind)` pair through the same package.

The reference sources and the full conformance-vector suite are maintained by RexStarBSV, with public release of the Inkan Standard forthcoming; this BRC is written so that a second, independent implementation can be built from the Specification and Test Vectors above without access to them. The optional seal presentation layer (§5) additionally requires the released conformance vectors for byte-level parity; the interoperability core (§2-§4, §6-§7) is fully specified here.

## Mathematical and cryptographic basis

The commitment folds the type into the digest. The anchor is SHA-256 over a fixed, versioned domain string terminated by a zero byte, then the length-prefixed kind, then the content (§2). Because the kind sits inside the preimage, two records of different kinds coincide only if SHA-256 itself collides. The length prefix makes the concatenation injective: given the fixed domain, there is exactly one way to split a preimage back into (kind, content), so no two distinct field assignments produce the same bytes. This is what removes the need for a central type registry, since type distinctness is a property of the hash rather than of a lookup table. Authenticity of the anchoring party, where a use requires it, is a separate signature over the same digest and does not change the commitment.

## Security Considerations

* **The anchor is the trust root; the image is not.** Every verifier MUST re-derive the anchor from `(data, kind)` and MUST NOT trust a presented code, seal, or embedded metadata. A phishing lookalike fails because it is not the byte-exact render of the recomputed anchor.
* **Domain separation and length prefixing.** The fixed `INKAN_DOMAIN` prevents an anchor digest from being mistaken for, or manufactured from, any other SHA-256 usage. The `u8(len(kind))` prefix (§2) and the `u8(len(adopter))` prefix (§6) each close a boundary-ambiguity collision class; both MUST be implemented exactly, as an implementation that drops either re-opens a real collision (TV5).
* **Type binding without a registry.** Because `kind` is inside the digest, cross-type confusion is cryptographically excluded, not administratively prevented. This removes the registry as both a coordination burden and a single point of failure/censorship, but it also means there is no authority to revoke or arbitrate a namespace; adopters MUST choose namespaces they control (e.g. a domain-like `ns:`).
* **Hash agility.** The `ik1` version prefix makes a future migration off SHA-256 explicit and non-silent. A reader that assumes SHA-256 for an unrecognized version (e.g. `ik2`) would mis-verify; readers MUST reject unknown versions (§3).
* **Seal forgery economics.** Visible entropy scales with tier: a perfect visual collision costs on the order of `2^16 / 2^28 / 2^40` renders (birthday) for `t3 / t2 / t1`. Low-tier seals therefore have *findable* perfect collisions **by design** (value-at-risk kinds SHOULD use `t1`, and in all cases the 32-byte anchor, not the image, is the trust root). The 6-bit frame yields 14 distinct frames per configuration; beyond that, open kinds may share a frame. This is defence-in-depth, not a break, since different kinds over the same data always differ in the interior and always differ in the anchor.
* **Honest perceptual limits.** All perceptual-distance figures are model outputs, not measurements of people; no human study has been run, so no accessibility or "phishing-proof" claim is made. Implementers MUST NOT market a seal as human-proof.
* **On-chain records are self-verifying and non-retractable.** A signed record (§7) carries its own record, signature, and public key, so it is checked entirely offline; the chain is trusted only for immutability and timestamp ordering. The `block_height`/`block_hash` binding is an anti-backdate proof. A verifier MUST treat absence, `STALE`, or `INVALID` as the warning (fail-closed), and MUST NOT trust the parsed on-chain bytes without re-verifying the signature and freshness. `INVALID` means a signature check was performed and failed; a verifier whose signature backend is unavailable MUST raise an explicit error and MUST NOT report `INVALID` or `OK`, since a fabricated verdict is a silent false alarm in one direction and a silent pass in the other.
* **Key custody.** The signing key MUST be the operator's own key held offline; the broadcast wallet (§7.5) funds but never signs, so a compromised or coerced broadcast path cannot forge a record. The test-network default (§7.5) keeps real funds as the last step, never the first.
* **Metadata hygiene / privacy.** The canonical seal and the fingerprint image writers emit no ancillary PNG chunks (no author, software, timestamp, or physical-dimension metadata). Implementations MUST preserve this and MUST NOT embed identifying information (public key, fingerprint, name, paymail, timestamp) in a seal's filename; a seal image should reveal nothing about the machine, build, time, or subject beyond the anchor itself. The optional `iTXt` tags carry only the (already public) `ik1` code and kind, and remain untrusted hints.
* **Presentation/anchor consistency for non-ASCII kinds.** The anchor NFC-normalizes `kind` while the reference presentation derivation does not (§4.2 note); callers SHOULD pass NFC kinds so both layers agree.

## References

* FIPS 180-4, Secure Hash Standard (SHA-256).
* RFC 2119, Key words for requirement levels.
* RFC 3629, UTF-8.
* Unicode Standard Annex #15, Unicode Normalization Forms (NFC).
* RFC 6979, Deterministic ECDSA.
* SEC 2 / secp256k1, the ECDSA curve used for on-chain signed records.
* BRC-42, BSV Key Derivation Scheme (protocol/key-ID namespacing contrast).
* BRC-43, Security Levels, Protocol IDs, Key IDs, and Counterparties.
* BRC-100, Wallet-to-Application interface (`createAction` broadcast path used by the optional on-chain anchoring).
* BRC-220, NotaryHash, privacy-preserving signed-hash notarization; the signed-hash counterpart this typed commitment composes with.
* 1Sat Ordinals, 1-of-1 ordinal inscription convention (used, not redefined, by §7.4).
* Golay, M. J. E., Notes on digital coding (extended Golay \[24,12,8], the seal's distance floor).
* The Inkan Standard, version 1.0 (frozen), by RexStarBSV, and its conformance vectors, from which this BRC is drawn.


# Access Gates for Metanet Rooms

Crumbs, zer0\_dt\_

> He that received seed into the good ground is he that heareth the word, and understandeth it; which also beareth fruit, and bringeth forth, some an hundredfold, some sixty, some thirty.

Access is normally something granted. You ask, somebody authorised to say yes decides, and your name goes into their table. That decision is discretionary, unaccountable, and free to whoever makes it — and the last of those is the flaw. Admitting somebody who damages the room costs the room; refusing somebody who would have improved it costs the applicant; neither lands on the desk that decided. A decision-maker insulated from the consequences of the decision is the textbook shape of a moral hazard, and it is why access lists rot.

A gate inverts the arrangement. The condition is published, the facts are the applicant's own, and nobody grants anything — you satisfy a rule or you do not, the world supplies the answer, and the cost of it falls on whoever moved. Standing at such a door means having put something at risk: a holding you could lose, a name somebody attached to you and can withdraw, a record of conduct written by people who were there. The party who gains from being inside is therefore the party who paid to get in, and whoever set the rule is exposed to their own rule, since one drawn carelessly excludes exactly the people it was meant to attract.

That is worth the trouble because of what is behind the door. Information placed well returns more than was put into it, which is why a room's contents are worth a rule at all and why a careless rule destroys something rather than merely causing offence. Price entry in stake rather than in permission and any meeting of minds can become a market in information: everybody inside has put up something real to be there, nobody profits by degrading what they bought into, and the entrant's incentive, the incumbent's and the room's point the same way. It is the principle behind proof of work applied to a door instead of a chain — a claim is worth what it cost to make, provided the cost is real and anybody can check it — and access earned that way widens as the record does and narrows the same way, because every fact beneath it can be withdrawn by the world rather than by an administrator.

## Abstract

This document describes **access gates**: conditions attached to a room, evaluated by a client against verifiable facts about a reader, that decide whether the room's contents are rendered to them.

Five gate types are described — **holding**, **vouch**, **renounce**, **quorum** and **timelock** — together with a **ban**, which appears in no manifest and applies to every room, an optional recurring fee, a manifest format so that two clients evaluate the same rule identically, and a verdict structure so that the result is data rather than a sentence. A room's **roles** are derived from the same conditions rather than from a list of appointees, which is what lets a room say who may moderate without keeping a register of moderators.

Every fact a gate reads is one [BRC-169](/peer-to-peer/0169) already publishes: a holding, a peer attestation, a statement written against a handle. No new cryptographic mechanics are introduced and no command verbs are claimed; the conversational surface for configuring a gate is [BRC-218](/apps/0218). This document subsumes BRC-218 section 11, which sketched three of these gates in a page.

## Motivation

**What a room is, here.** A room is a space with more than two parties, contents that persist, and a participant set somebody could enumerate. A group chat is the obvious case; a channel, a forum, a comment thread and an open feed are the same shape. This document does not say how a room comes to exist, where its contents are carried, or what an ecosystem calls it — those differ everywhere, and standardising them is not necessary in order to standardise the door. It asks two things of whatever an ecosystem provides: a **stable identifier**, so that a rule attaches to the right thing, and a **holder**, so that a change to the rule is attributable to somebody.

Two things are deliberately not rooms. A one-to-one conversation is not one — reachability between two identities is already answered by BRC-169's scopes and tolls at the messagebox, each party is their own gate, and a predicate would be a worse version of a decision they can make directly. Nor is anything with a single participant: a feed you alone post to is a publication, and who may read it is a question about distribution rather than about membership.

The distinguishing feature is that a room **accumulates**. What is said in it stays, so admitting somebody admits them to everything already there. That is what makes a rule worth having, what makes a careless one expensive, and what makes removing one irreversible.

A room needs a rule about who is in it, and the reflex is a membership list. A list fails the way administrators fail. It goes stale, because nobody enjoys pruning it. It bottlenecks on whoever holds it and stops working the week they lose interest. It cannot say *why* anyone is on it, so nobody can audit it and nobody can join by satisfying it — they can only ask. And it lives in a vendor's table, which makes it exactly as portable as that vendor.

A gate is a predicate over claims other parties have already made, in public, for their own reasons. Nobody has to be asked. The answer is the same for everyone who evaluates it, and a reader can read the rule that excluded them.

The deeper difference is the reason for most of what follows. **A list marks; a gate wagers.** Being on a list is a fact about the list: it predicts nothing, forbids no outcome, and cannot turn out to have been wrong — a name sits there whether or not the thing it stood for is still true, and it comes off only when somebody removes it. A gate names a condition reality decides. It can be satisfied and it can stop being satisfied, both without anyone's permission.

That difference has a price attached. **A gate costs something to satisfy and a list does not.** A holding gate asks you to have something you could lose; a vouch gate asks somebody to attach their name to you, which is theirs to withdraw; a fee asks you to keep paying. Each is exposure, and each can be checked by anyone who cares to. An entry on a list is free to the person on it and free to whoever put them there, which is precisely why lists drift: nothing about them is answerable to anything, so nothing corrects them.

The failure mode is a gate that has stopped wagering. Roles are derived rather than granted for this reason and no other — a moderator who holds a rare item stops being one by selling it, where an appointed moderator can only be un-appointed by whoever appointed them. It is also why section 9 is blunt about gating being presentation. A gate described as a security boundary has changed categories: it has stopped being a checkable claim about who holds what and become a promise about what cannot happen, which is a promise it cannot keep and which nothing will ever falsify to the person relying on it.

None of this argues that every room should be gated, and section 10 says which should not be. A room can be perfectly good without a rule, and most are; a gate applied to a room that never needed one converts a group of people who know each other into a predicate that is wrong about some of them. The claim is narrower: a room that is going to have a rule is better off with one that can be read, checked, satisfied and lost than with a list nobody can audit and nothing can correct.

Two properties make this worth writing down rather than leaving to clients. **Determinism is the product** — if two clients disagree about who may read a room, the room is a different room in each of them, and a rule that varies is worse than no rule because it looks like a guarantee. **The excluded reader is the design** — the difference between a door and a disappearance is what the person outside is told, and somebody refused should be able to read the rule that refused them and say what would change the answer.

BRC-218 reserves `/gate` for charging admission to a room: the **write** half, needing custody and refunds. This document covers the **read** half only — a condition, evaluated locally, deciding what is rendered.

## Prior Art

Three bodies of existing work bear on this. The design converges with the first, diverges deliberately from the second, and comes close enough to the third to be worth distinguishing carefully.

**Policy languages.** The three-state verdict of section 3.1 is not a novelty. XACML has carried `Permit`, `Deny`, `Indeterminate` and `NotApplicable` since 2003, with `Indeterminate` meaning exactly what it means here: the decision point could not evaluate, which is neither an admission nor a refusal. This document arrives at the same three states by a different route and drops the fourth, because a room with no condition is answered by section 2 rather than by the evaluator. Where a design converges with a mature policy language it is usually because the problem has a shape, and it is worth saying so rather than presenting the state as a discovery.

**The stored grant.** The dominant answer to "may this person be here" is to write the answer down. The access matrix of the early 1970s made it a table of principals against objects; role-based access control, standardised as ANSI INCITS 359 in 2004, added an indirection so the row names a role rather than a person; directory groups, workspace memberships and cloud IAM policies are the same object at scale, and a Discord member list or a Slack workspace is its consumer form. In every one, somebody with authority writes a row, and the row is what is consulted afterwards.

The arrangement has real advantages, and this document is not improved by pretending otherwise. A table read is cheaper than any predicate — no lookup, no indexer, and no state in which the answer cannot be computed. It is private, since being on a list discloses nothing about why, where a gate makes the qualifying facts legible to anybody who can evaluate it. It is precise in a way no predicate is, because a person can admit exactly who is meant and nobody else, including where the criterion is "my colleagues" and no public fact means that. It is recoverable: an administrator restores access to somebody who lost their keys, which section 8.4 cannot. And it can refuse an adversary who satisfies every stated condition, which a gate structurally cannot, because anybody who can buy in gets in.

What it costs is what the Motivation names. The grant outlives the reason for it, nothing about a row being answerable to anything. It cannot be audited by its subject, who can neither read the rule that excluded them nor discover that no rule did. It bottlenecks on whoever holds the write. It is not portable, being a row in one vendor's table. And whoever writes it bears none of the cost of writing it wrongly, which is the moral hazard this document opens by objecting to. A gate does not discipline that administrator; it removes the object they were writing to.

**Token-gated rooms as a product category.** Guild.xyz, Collab.Land and Discord's own role gating have shipped hold-a-token-get-a-role for years, and Lens and Farcaster channels do a version of it too. The mechanism there is a service: a bot holds an API key, checks a wallet on a schedule, and writes a row into a platform's membership table. What it produces is a list — the gate runs once and its output is an entry that persists until something else removes it, which is why those systems need periodic re-checks and why their failure mode is a stale roster nobody notices.

So the product category is a stored grant with an automated writer. It moves the discretion from a person to a bot and leaves the row where it was, which is why the trust it asks for is the same trust: the reader must believe a service they cannot inspect about a check they did not witness against a table they cannot read.

The difference this document draws is that the predicate *is* the membership. There is no row to write, so there is nothing to go stale; the answer is recomputed by whoever is rendering, from facts the reader's own client can verify, rather than asserted by a service the reader must trust. Four consequences follow that neither the product category nor the stored grant has. The rule is portable, being a signed object rather than a configuration inside one platform, so a room survives the platform it was formed on. The excluded reader can read it, because it is published rather than held in a bot's database, which is what makes a refusal answerable. Roles fall out of the same evaluation, so a moderator who sells the qualifying item is demoted by arithmetic rather than by a synchronisation job that may never run. And there is no privileged reader: the room's own operator evaluates the same predicate over the same public facts as everybody else, and cannot admit a favourite without changing the rule where the participants can see it.

That last one is the whole of the difference from a centralised system, and it is worth stating as a limit rather than as a boast. A gate does not make an operator honest. It makes an operator's dishonesty a signed, published edit to a rule with a notice period attached (section 2.4), rather than an unlogged row in a table nobody outside can read.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119. They are used sparingly, and where interoperability actually breaks; the rest of this document describes a mechanism.

Throughout, "resolve" means the client resolution algorithm of BRC-169 section 5.7, and "handle" has the meaning given in BRC-169 section 2.1.

### 1. Terminology

* **Room**: as described in the Motivation — more than two parties, persistent contents, an enumerable participant set, a stable identifier and a holder.
* **Reading**: rendering a room's contents to someone. Every gate here governs reading.
* **Admission**: becoming a participant — a different thing from reading, and specified only for a room that publishes a condition.
* **Manifest**: the conditions attached to a room, encoded per section 2.
* **Condition**: one typed test a reader is evaluated against. Five are configured in a manifest and are on or off; the ban of section 4.6 is configured nowhere and applies to every room.
* **Verdict**: the outcome of evaluating a manifest against a reader — qualifying, failing, or indeterminate.
* **Holder**: the single identity that holds a room. Custody, not qualification.
* **Role**: what a participant may do, derived per section 7.

### 2. The Manifest

A manifest is a signed JSON object attached to a room. Every condition that is on must pass; there is no OR across types, because a room admitting readers who satisfy either of two unrelated conditions is two rooms and the reader cannot tell which one they are in. A condition that is on but not yet configured admits — manifests are edited in live rooms, and a half-typed condition that locks everyone out for the seconds it takes to finish typing is a broken editor rather than a strict door.

Three properties are load-bearing enough to state as requirements. The manifest MUST be readable by anyone who can see the room exists, including readers who fail it, since a rule you cannot read is a rejection with the reason withheld. It MUST NOT enumerate the identities it admits: naming a handle as an *attestor* is a condition on the reader, while naming one as an *admittee* is a membership list with extra steps. And a change to it is reported in the room, being at least as much the participants' business as a change to the room's name.

Where a manifest states at least one condition, admission is self-service: a reader who satisfies it may join without anybody's approval. A room that has stated no condition has published nothing to satisfy, so nothing here entitles anyone to join it — an ungated room is not thereby open to strangers, and how somebody joins one is out of scope. Discovery is out of scope too; section 6 governs what a reader sees once they have a room's identifier, not whether rooms are listed anywhere.

#### 2.1 Shape

| Field                                             | Type           | Required    | Meaning                                                        |
| ------------------------------------------------- | -------------- | ----------- | -------------------------------------------------------------- |
| `version`                                         | integer        | yes         | format version; `1` for this document                          |
| `counter`                                         | integer        | yes         | monotonic per room                                             |
| `signedAt`                                        | block height   | yes         | the chain-anchored moment of signing, per 2.4                  |
| `room`                                            | string         | yes         | `room:<domain>/<local>`, per 2.2                               |
| `holder`                                          | handle         | yes         | custody                                                        |
| `effectiveFrom`                                   | block height   | no          | when this manifest starts deciding, per 2.4                    |
| `replaces`                                        | digest         | no          | the manifest superseded; required while pending                |
| `burn`                                            | outpoint       | no          | the unspendable output of 2.4, where ungating                  |
| `ungateBurn`                                      | decimal string | conditional | satoshis to burn to ungate; required while any condition is on |
| `successor`                                       | handle         | no          | who may assume custody after dormancy, per 8.3                 |
| `dormantAfter`                                    | blocks         | no          | holder silence after which `successor` may sign, per 8.3       |
| `token` `vouch` `renounce` `quorum` `fee` `roles` | object         | no          | conditions, per 2.3                                            |

An absent condition and one present with `"on": false` evaluate identically; the distinction exists for an editor holding a half-configured condition and nowhere else.

Condition shapes:

```
token     { on, mode, assets[], minimums{}, shares{}, fees{}, source }
vouch     { on, mode, handles[], source }
renounce  { on, mode, handles[], source }
quorum    { on, count, distinctEcosystems, source }
timelock  { on, minimum, blocks, source }
fee       { on, asset, amount, period, to }
roles     { on, rarity{}, minimums{}, shares{}, weights{}, counts{}, entitiesAreAdmins }
```

There is no `ban` condition, and section 4.6 says why: a ban is a decision about one person rather than a rule about who may enter, so it is carried in the room's record and applies whether or not a manifest exists.

`mode` is `"any"` or `"all"` and is present wherever the field it governs holds more than one entry; each gate type below gives the default when it is absent, because the two produce very different rooms and neither is obvious enough to leave to a client. `minimums{}`, `shares{}` and `fees{}` are keyed by asset reference, canonicalised as `<protocol>:<id>`. On a `timelock` condition `minimum` is satoshis and `blocks` is a count, both per section 4.5; in `roles`, `weights{}` takes a role name to a lock weight in satoshi-blocks. `source` names where a client may look for facts it cannot hold itself: the attestation lookup of section 4.2 on a vouch, renounce or quorum condition, and the chain indexer of section 4.1 on a token condition. In both cases it is a hint and not an authority, per those sections. In `roles`, each map takes a role name — `"admin"`, `"mod"`, `"member"` — to its threshold.

Two encoding rules matter for agreement rather than taste. Thresholds are **decimal strings**, compared as decimals — always, not where precision demands it, since a rule that applies sometimes is one two implementations will disagree about the boundary of. And the manifest is canonicalised per [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) before signing, or two encodings of the same rule hash differently and the integrity check below means nothing.

A client rejects rather than partially evaluates a manifest carrying more than one condition of each type or more than sixteen entries in any array. A manifest naming two hundred contracts forces two hundred lookups on every render, which makes a room a denial-of-service instrument against everyone who opens it.

A client meeting a `version` it does not know treats every condition as indeterminate rather than evaluating the subset it recognises, since silently ignoring an unrecognised condition admits people the room excluded. The cost is that a version bump is a hard break; a later document wanting a gradual path should add optional conditions within version 1.

#### 2.2 Identifiers

A room identifier is `room:<domain>/<local>`, the domain being one the holder's ecosystem controls. Uniqueness comes from the domain exactly as it does for handles, since a bare local string collides the first time two ecosystems both have a room called `general`.

An asset is named by a pair — a `protocol` and an `id` meaningful within it. This document registers `1sat` (an origin outpoint, `<txid>_<vout>`) and `bsv21` (a token id); a client meeting an unregistered protocol returns indeterminate rather than failing the reader. An asset MUST NOT be identified by **name**: a collection called "Naka Motor Club" is a display string anybody can choose, so a gate on a name is one an impersonator satisfies by minting a contract with the same one. Clients show the name beside the reference, resolved *from* it rather than the other way round.

#### 2.3 Integrity

A manifest is signed over its canonical form by the holder named in the manifest it replaces, or for a room's first manifest by the holder in the room's creation record. Nobody else: a role derived from a holding carries no authority to rewrite the rule that derived it, and an arrangement letting admins sign successors lets any of them name themselves holder and keep the room. Roles govern the day to day; custody governs the rules. It follows that an acting admin under section 7.4 cannot change a manifest — succession lets an abandoned room be moderated, not re-founded.

A client MUST verify the signature before evaluating, and MUST treat a manifest that fails verification as **indeterminate** rather than absent. Absent means ungated, so a relay able to turn a gated room into an ungated one by corrupting a byte would have the cheapest possible attack on all of this. The `counter` must exceed the highest already accepted for that room, or a relay can replay yesterday's manifest and quietly readmit somebody excluded this morning.

Signing defends against alteration and not against deletion, since removing a manifest achieves what corrupting one cannot. Two rules close most of that gap: a room's creation record states whether the room carries a manifest, and a client served none for a room whose record asserts one treats it as indeterminate; and a client that has accepted a manifest never afterwards treats that room as ungated without a later signed manifest turning the conditions off. Together they cover the returning reader completely and the first-time reader as far as the creation record can be trusted. A reader handed a forged creation record for a room they have never seen cannot detect it from within this document — where that matters, the room's identifier should commit to its creation record.

#### 2.4 Change

A room's rules have to be changeable; a room that cannot be gated after the fact must know on the day it is created what it will become, and no room knows that. But rules rewritable at will are not much use either, and the people rewritten are the ones who never agreed to the new version.

The instinct is to make the first gate easy and later ones hard. The axis that matters is not first-versus-later but **who a change puts out of the room**.

A change excluding nobody currently in the room takes effect immediately — turning a condition off, lowering a threshold, removing an attestor, widening a band. A change that would newly exclude a current participant carries an `effectiveFrom` at least **1,008 blocks** beyond its `signedAt`, is reported when signed rather than when it lands, and shows affected participants that they are affected. Until then the manifest it replaces continues to decide.

The same interval applies to a change that would **demote** a current participant without excluding them. A role threshold raised past somebody currently moderating takes their capability away, and the fact that they can still read the room does not make that a change they should discover by finding a control missing. Section 7.1 already requires that a demotion be told directly; this requires that a demotion by rule change be told in advance.

Which of the two a change is MUST be decided by comparing the manifests, per section 2.5, and MUST NOT be decided by evaluating current participants. The obvious implementation — apply the proposed rule to the roster and see who drops out — is the balance oracle the Security Considerations warn about, and it would make the notice rule of this section implementable only by running it.

Both are block heights rather than wall-clock times, and `signedAt` exists so that the interval has a reference point inside the object it constrains. A notice measured in days would need a clock two clients agree on, and they do not have one: skew between them would land a tightening at different moments in each, which is exactly the disagreement section 3.1 exists to prevent. A height is a fact both can look up. At the network's ten-minute target 1,008 blocks is about a week, and it is a week measured in the only unit the parties share.

Two details make the notice real rather than nominal. A pending manifest carries `replaces`, the digest of its predecessor, and a client holding no copy of that predecessor fetches it or returns indeterminate — otherwise the notice protects only clients already watching, and a reader arriving mid-window served nothing but the pending manifest would apply the tightening early. And at most one change may be pending: signing a new manifest replaces any pending one and restarts its notice, without which a holder can chain changes so the notice never arrives, or announce a mild change and swap a harsher one into a clock the room has already read.

**The asymmetry people expect falls out of this rather than being stipulated.** A room's first gate is usually chosen to fit the people already in it, because the room formed around the thing it gates on; such a gate excludes nobody and lands at once, which is why upgrading an ordinary group chat is easy. A later change is usually made *because* somebody should not be there. Those exclude, and those wait.

**Grandfathering is not available**, and the reason is structural. Letting existing participants keep access under the old rule means recording who was admitted under which manifest — a list of individuals with privileges attached, the exact object this document exists to replace. The notice preserves nobody's access; it gives them time to keep it or to leave with warning, which is less than grandfathering and is honest about being less.

**Ungating is the one change that cannot be undone.** It excludes nobody, so the rule above would land it instantly, and it exposes everything already said to everybody arriving afterwards — people spoke under a rule about who would hear them. So a manifest with any condition on declares `ungateBurn`, the amount that turning them all off will cost, and turning them off references an output that is **provably unspendable** and worth at least that much, verified before the change is accepted and treated as indeterminate where it cannot be verified, which leaves the gate standing.

The amount is declared rather than computed, because a computed one is not checkable. An earlier draft scaled it by participant count, which two clients verifying at different moments would count differently and so disagree about whether an ungating was valid — a rule that fails the agreement of section 3.1 is not a rule. A declared figure sits in a manifest anybody can read before they speak, which is the disclosure that makes it useful: a room declaring nothing is telling its members that their history is one signature from public, and that is information they can act on. It may be raised at once, since raising it excludes nobody and takes nothing away. Lowering it follows the notice above, because it withdraws a protection participants have been relying on even though it puts nobody out of the room.

What the burn buys is narrow and worth stating. It does not protect the archive; anyone determined to publish a room's contents copies them out without touching the manifest, and no rule here could stop them. It makes the supported path deliberate, verifiable by anyone, and profitable to nobody — the amount is destroyed rather than paid, so no participant, holder or third party gains from a room being opened and no constituency forms around making it happen. Provably unspendable means exactly that: a famous address that has never been spent from is an ordinary output whose key may exist, which makes it a gift to whoever holds that key rather than a cost to nobody.

#### 2.5 Comparing two manifests

Section 2.4 turns on whether a change excludes anybody, and that question has an answer that does not require knowing who is in the room.

**Manifest X is at least as strict as Y when every reader admitted by X would also be admitted by Y.** The relation is about admitted sets and not about text. It is a **partial** order: a rule requiring a vouch from `@deggen` and one requiring a vouch from `@crumbs` admit neither a subset nor a superset of the other, and no amount of comparison will sort them.

Where the replacement is no stricter than the manifest it replaces, no reader who qualified stops qualifying — for every possible reader, not merely for the present ones — and the change takes effect immediately.

A manifest is a conjunction: section 2 admits only readers who pass every condition that is on. So turning a condition **on** is stricter, turning one **off** is looser, and otherwise the comparison is condition by condition:

| Condition                  | Stricter when                                                                                                                    |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `token`                    | `minimums` or `shares` raised, per asset; `assets[]` shrinks under `mode: any`, or grows under `mode: all`; `mode` `any` → `all` |
| `vouch`                    | `handles[]` shrinks under `any`, or grows under `all`; `mode` `any` → `all`                                                      |
| `renounce`                 | `handles[]` **grows** under `any`, or shrinks under `all`                                                                        |
| `quorum`                   | `count` raised, or `distinctEcosystems` false → true                                                                             |
| `timelock`                 | `minimum` raised, or `blocks` raised                                                                                             |
| `fee`                      | `amount` raised, `period` shortened, or `asset` or `to` changed                                                                  |
| `roles`                    | never — a role map admits nobody and excludes nobody, and is compared for demotion under 2.4 rather than for exclusion           |
| `source`, on any condition | never — section 4.1 makes it a hint a client may decline, so it cannot move the admitted set                                     |

**Renounce runs the other way, and an implementation will get this wrong.** Every other condition admits on a match; renounce excludes on one and admits by default (section 4.3). Adding a handle to a vouch gate under `mode: any` gives the reader another way in, and adding one to a renounce gate under the same mode gives another party the power to shut them out. Same field, same keyword, opposite sign.

`ban` does not participate. It appears in no manifest (section 4.6), so there is nothing to compare.

**The comparison is sound and incomplete, and the incompleteness is the price.** Proving that a replacement admits a superset proves that nobody is excluded; failing to prove it does not mean anybody is. Raising a minimum from 500 to 600 in a room where everybody holds thousands is stricter by this order and excludes nobody in fact, and the room waits 1,008 blocks anyway. Where two manifests are incomparable a client MUST treat the change as excluding, which means swapping one attestor for another also waits.

Erring this way is deliberate. The alternative to a conservative structural test is an exact one, and the only exact test is to run the proposed rule against the people in the room — which yields, for whoever is editing, a per-member verdict they can refine by editing again. A week's delay on a harmless change is a smaller cost than shipping that.

A room that wants an exact answer may ask its members for one: each client evaluates the proposed manifest against its own user and returns whether they still qualify. That discloses one boolean per member instead of a quantity, supports no binary search, and licences immediate effect where every current participant answers yes. It requires the members to be present and willing, so it is a fallback rather than the rule.

### 3. Evaluation

#### 3.1 Order and determinism

Two conforming clients evaluating the same manifest against the same reader from the same facts MUST reach the same verdict. Conditions are evaluated **ban, renounce, vouch, quorum, holding, timelock, fee**, and the first failure is the one reported. The order runs from the most categorical to the most quantitative, so the reason a reader sees is the most fundamental rather than an accident of iteration. A ban comes first because it is the only one a person in the room decided — telling a reader they do not hold a token when the truth is that a moderator excluded them gives them a reason they cannot act on. The fee comes last because it presupposes the holding it accompanies.

Where a client cannot obtain a fact, the verdict is **indeterminate**: not passing, not failing, and surfaced as its own state. Fail-open is the obvious hazard; fail-closed-and-say-nothing is the subtler one, because "you do not qualify" and "I could not check" send a reader to do entirely different things.

#### 3.2 Freshness

A gate is evaluated against a reader's current standing rather than their standing when they joined — balances are spent, vouches withdrawn, statements written. A cached verdict does not outlive the shortest `ttl` of the resolutions it depended on, and does not exceed five minutes where no `ttl` is available; "bounded" without a bound is a requirement every implementation satisfies and no two satisfy alike. A cache is discarded on a key change for that reader under BRC-169 section 4.4, since the facts a gate reads are facts about a key. Discarding the verdict is not the same as concluding the reader has nothing; section 8 covers what a rotation should and should not cost them.

#### 3.3 Verdicts

A verdict is produced as data and rendered afterwards, never produced as a sentence:

```json
{
  "outcome": "fails",
  "condition": "token",
  "index": 0,
  "shortfall": { "held": "12.32", "required": "21.8" }
}
```

The outcomes are `qualifies`, `fails` and `indeterminate` and no others. `condition` is one of `ban`, `renounce`, `vouch`, `quorum`, `token`, `timelock`, `fee`; `index` says which entry of a multi-entry condition decided; `period` accompanies a fee verdict; `height` accompanies a timelock verdict, per section 4.5(4); `shortfall` accompanies a quantitative failure, and on a timelock condition it carries whichever of the two figures fell short:

````json
{ "outcome": "fails", "condition": "timelock", "height": 921740,
  "shortfall": { "locked": "40000", "required": "100000", "blocks": 900, "requiredBlocks": 4032 } }
``` Wording is the client's — two clients rendering it differently is fine, two clients computing it differently is not. An agent acting under BRC-218 is given the structure rather than the sentence, since prose is not an interface.

**Quantities are only ever the reader's own.** A client evaluating a manifest against its own user shows the arithmetic. Evaluating against anybody else — to mark a roster, filter a picker, preview a manifest — it MUST NOT display, log or export any quantity: not the amount held, not the shortfall, not a bar derived from either. A qualitative reason may be shown, because it discloses nothing new; that a named handle has not vouched for somebody is an absence in a public record. A balance is not. A roster annotated "holds 12.32 of the 21.8 required" turns a settings screen into a balance oracle over everyone in the room, and an administrator who can edit the minimum can binary-search a participant's holdings in a few keystrokes. The line falls between public claims and private quantities, in the same place on every surface, and `shortfall` is omitted from an exported verdict for the same reason.

A client also MUST NOT report a verdict, or that one was computed, to the room or to any ecosystem host. Evaluation happens on the reader's machine against public facts, and phoning the result home would hand every room a list of who looked and failed — a more sensitive list than the participant list the gate is protecting.

Finally, a verdict is not a judgement. Failing a gate is a condition on a room rather than a finding about a person, and a client that renders "does not meet this gate" in the visual language of a failed signature has editorialised on the room's behalf.

#### 3.4 Delegation

Where a reader acts through a delegate under BRC-169 section 9, the gate is evaluated against the **principal**. A delegate's own holdings, vouches and statements never substitute in either direction: an agent holding a rare item does not make its principal an administrator, and an agent holding nothing does not lock a qualifying principal out of their own room. A delegate exercises no capability its principal lacks and none outside its certificate's scope — roles are the ceiling, the certificate is the aperture — and an action it takes is attributable to both.

### 4. Gate Types

Five gate types are configured in a manifest. A sixth condition, the **ban** of section 4.6, is configured nowhere and applies to every room; it is described here because section 3.1 evaluates it and section 3.3 reports it.

#### 4.1 Holding

The reader holds a named asset. A holding gate names a **contract**, not an item: a room wants holders, and a gate on a serial stops being true the moment somebody sells one item and buys another from the same issue. Where the asset is fungible the gate may set a minimum; where it is non-fungible it does not, holding one not being a quantity and 1.5 of an item being a question with no answer. A minimum of zero is not a threshold and is rendered as "any holding" rather than as a decorative `0`. Where several assets are named the default is **any**. Where a contract publishes a register, showing which item satisfies the gate is the difference between "allowed" and "holds hat 13", and the second is what makes a gated room feel like a room rather than a permission.

**Which outputs are the handle's.** Before a client can ask what a handle holds it has to know which outputs to look at, and BRC-169 does not answer that: resolution binds a handle to an **identity key**, while assets sit at derived or separate keys and are not found by looking at the identity key alone. The mapping is a second thing, and this document requires it to be **published by the holder**, as one or more `holdingKeys` served alongside the handle at the resolution endpoint of BRC-169 section 5.1.

Publication rather than proof-on-demand is the whole reason a gate is evaluable by a stranger. A holder who instead proved control when asked would make every evaluation interactive, which would mean nobody could be evaluated while offline, and a member who sold their qualifying asset would become indistinguishable from a member who was asleep. Section 11 records what it would take to relax this.

**A declaration MUST be signed in both directions**, and an implementation that omits the second signature is broken in the way that matters. The identity key signs a statement naming the holding key; the holding key signs a statement naming the handle. Without the second, a declaration is an unverified claim about somebody else's coins, and any handle could name the richest key on the chain and inherit its standing. This is the bidirectional rule of BRC-169 section 5.5 applied to a different pair: one side asserts, the other consents, and both are required so that nothing can be bound to a party that has not agreed.

Two consequences a holder should be told before declaring. The declaration is **public and permanent in effect**: every output at a declared key is thereafter attributable to the handle, by anybody, including for the past. And it is therefore worth declaring a **dedicated** key rather than a wallet's main one — qualifying assets are moved to it, the rest of the holder's activity stays unlinked, and the room learns what it needs and nothing else. A client SHOULD offer that arrangement rather than proposing the key the user already uses.

Rotation follows from this rather than complicating it. A declaration signed by an identity key that has since changed is stale, not void: section 8.2's evidence decides whether the rotation is genuine, and a genuine one is re-declared under the new identity key while the holdings and the holding key stay exactly where they are.

**Where the facts come from.** Neither of the protocols registered in section 2.2 can be read from a transaction in isolation. Determining that a handle holds an item of a `1sat` contract, or how many units of a `bsv21` token it controls, means following a chain of transfers from an origin, which is what an indexer does and why one is already a dependency of any client that displays a balance at all. A holding gate adds no new infrastructure; it asks the question the client's wallet surface is already asking, about somebody else. Two facts are read: the outputs a handle controls, which is a mapping distinct from the identity binding for the reason section 8.1 gives, and, where the gate uses a share or a rarity band, the contract's own published supply or bands (section 7.3).

**An indexer is a witness, not an authority, and a client MUST NOT treat one as authoritative.** A token condition MAY carry a `source`, and it is a hint about where to look rather than a delegation of the answer: a client is free to prefer its own indexer, and should. The distinction is load-bearing rather than fastidious. Two honest indexers agree because they are reading the same chain, so naming one buys convenience and not agreement — unlike the attestation lookup of section 4.2, where the stores genuinely differ. And a manifest able to bind every reader to one indexer would hand the room's holder a privileged view: an operator who controls the source controls the verdict, which is precisely the arrangement this document exists to remove. A client that can reach no indexer it trusts returns indeterminate under section 3.1.

Two properties of the underlying data are worth stating, because a client reading a balance for its own user forgives both and a gate does not. An indexer answers about chain state at a height it has reached, so lag reads as a holding that is not yet there or one that is no longer there, and it is bounded by section 3.2 exactly as a stale resolution is. And an unconfirmed transfer is not a holding: a gate satisfied by an unconfirmed spend admits a reader whose qualification can still be undone, which is a distinction the room's own participants cannot see and the reader can.

#### 4.2 Vouch

The reader is vouched for by named handles, under BRC-169 section 10. The handles are resolvable and shown resolved: a gate naming `@deggen` names a specific key, and BRC-169 section 2.3's confusability rules apply exactly as they do to a payee, since a homoglyph in a manifest is a gate on somebody else's signature. Only **attributed** attestations count — an attestation is a [BRC-52](../peer-to-peer/0052.md) certificate with a peer as certifier, and one whose signer cannot be resolved to a handle the reader can look up does not satisfy the gate. Attestations of a handle-to-key _binding_ are not vouches for the person; a gate summing them admits on the strength of people confirming the reader's spelling. Where several handles are named the default is **any**.

**Attestations must be discoverable, and discovery is a SHOULD rather than a guarantee.** A vouch gate reads the attestations made _about_ an identity, which is a different question from how a peer publishes one, and it is unevaluable without an answer. BRC-169 section 10.8 supplies the shape: an ecosystem SHOULD answer it for its own handles at the resolution endpoint of section 5.7, as a list of attestation outpoints with their certifiers. That is where a client looks first, and where the ecosystem answers it a vouch gate needs nothing further.

Because it is a SHOULD, some ecosystems will not answer it, and a gate naming attestors across several ecosystems only needs one of them to be silent. A manifest may therefore name the lookup it expects to be answered from, as `source`, which is a fallback for that case rather than a substitute for the endpoint. A client that can reach neither returns indeterminate rather than concluding nobody has vouched: absence of evidence and evidence of absence are the same bytes over a failed connection, and only one of them should close a door.

#### 4.3 Renounce

A statement written against the reader by any named handle closes the door. Alone among the types this gate **admits by default** — everyone passes until a specific claim exists — which makes it the only one that scales to a room open to strangers, and the only one whose absence of configuration is a meaningful state. It follows that an unreachable source does not make it indeterminate as it would a vouch gate: no claim found is the same state as no claim existing, and the alternative lets anyone who can knock over a lookup service take an open room dark. The gate admits, and the client says the check did not complete.

**Only attributed statements gate.** BRC-169 section 10.7.1 makes negative statements unattributed by default, deliberately, so that speaking against somebody does not require a reputation large enough to survive it. Acting on unattributed statements would convert an anonymous claim into an exclusion the subject cannot answer, at whatever scale the claimant can manage. The reader is told which handle blocked them: the statement is attributed by construction, and withholding the name protects nobody who has already signed it. Where several handles are named the default is **any** — one statement closes the door.

#### 4.4 Quorum

The reader is vouched for by at least **N distinct** handles, unnamed. This is the vouch gate for a room that knows what standing it wants and not whose, which makes it the one a new room can actually configure: naming three trusted handles requires already having a view, and a room's first members are the people forming one. Attestations are counted once per **signing key**, since counting per attestation lets one party clear any threshold alone. Discovery is subject to section 4.2 and the exposure is worse here — a vouch gate can at least be evaluated against the handles it names, while a quorum gate has none to ask about.

A quorum gate may require attestors to resolve to **distinct ecosystems**, computed from the domain part of the handle. Without it, a ring of mutually-attesting accounts in one ecosystem clears any N for the cost of creating N accounts. With it, the same attack costs coordination across independently operated domains — which is a cost and not a proof, since one operator can run several domains, and a room relying on it should say so rather than describe the requirement as independence.

#### 4.5 Timelock

The reader has locked satoshis, to their own key, until a stated height. The condition sets a **minimum amount** and a **minimum remaining duration**, and both must be met; the ladder of section 7.3 reads the two together as a single weight.

What distinguishes this from a holding gate is not the asset but what the reader gives up. A balance is a fact about now, and it survives being lent, borrowed for the length of one evaluation, or bought from somebody on their way out — section 11 records that a rented balance is the weakness the holding-shaped gates share. A lock cannot be rented, because renting implies return and the coins do not move; it cannot be bought from somebody exiting, because the lock pays to their key and not to yours; and it cannot be assembled for the moment of evaluation and dissolved afterwards, which is the whole of the Sybil economics. It is the strongest condition in this document, and it is strong for one reason: the cost is real, ongoing, and borne by the person claiming it.

1. The lock is an output the reader controls, encumbered so that it cannot be spent before a stated height. It is read through the mapping and the indexer of section 4.1, which also verifies that the encumbrance is genuine — an output merely *intended* to be held is a holding, and belongs to section 4.1 rather than here.
2. **Duration is measured forward, not backward.** What counts is the blocks remaining between the current tip and the unlock height, not how long the lock has already run. A lock therefore decays: its weight falls as it matures, and a reader approaching a threshold is demoted before they are excluded. That is the intended behaviour rather than a defect. It gives warning where a cliff would not, and it means standing is maintained by re-committing rather than by having committed once, which is the difference between a wager and a receipt.
3. Both figures are compared as decimals per section 2.1, amounts in satoshis and durations in blocks. Blocks are used for the reason section 2.4 gives: two clients share a chain and do not share a clock.
4. **Determinism holds at the level section 3.1 requires and no higher.** Two clients at the same tip reach the same verdict. Two clients one block apart can differ on a reader sitting exactly at a boundary, because the remaining duration is genuinely different between them — that is two clients with different facts rather than two clients disagreeing, and section 3.2's freshness bound is what keeps the window small. A verdict on a timelock condition carries the height it was computed at, so a disagreement can be explained rather than merely observed.
5. A lock that has passed its unlock height is not a lock. It contributes nothing, whether or not the coins have been moved, and a client MUST NOT count it — an expired lock is an ordinary balance, and counting it would let one commitment be spent for standing forever.
6. Where several locks are held, they sum: amount is the total locked and the weight of section 7.3 is the sum of each lock's own amount-by-duration product. Summing weights rather than multiplying totals is what stops a reader pairing one large brief lock with one tiny long one and claiming the product of the extremes.

**Unlocking, and getting the coins back.** Nothing in this document moves anybody's satoshis, and neither a room nor a client can. The lock pays to the reader's own key; at the unlock height they spend it as they would any other output. There is no claim to submit, no counterparty to release it, no room permission to obtain, and nothing that can fail. The room never sees a reclaim as an event — it observes only that a condition has stopped being met, exactly as it would a sold token. This is the whole of the difference from the stake of section 11, where somebody else is holding the money and can decline to give it back.

The corollary is that **a term cannot be shortened**, and a reader should be told so before they choose one. Leaving is not immediate: somebody who loses interest in a room a week into a six-month lock is committed for the remaining five, because the encumbrance answers to a height and not to their intentions. That is the cost that makes the condition worth anything, and it is also the one a reader is most likely to have skipped past.

**Standing is held with overlapping locks, not with one.** Because an output cannot be spent before its height, a lock cannot be extended — a reader can only add another. A single lock is therefore a cliff rather than a slope: its weight decays to nothing at exactly the moment its coins become available, so the capital needed to replace it is locked up until it is already too late. The arrangement that works is several locks with staggered heights, each reclaimed and re-committed further out as it matures, which is why the amounts and weights of rule 6 sum. A client creating a lock should say this — recommend a term well beyond the room's minimum, and more than one lock — because it is the difference between a commitment a reader can maintain and one that lapses on them while their money is immobile.

A room wanting more commitment raises `blocks`. It does not get a minimum *original* term, and the omission is deliberate: such a rule would penalise the reader running staggered locks, whose most recent commitment is deliberately short because it is topping up several longer ones, and reward the reader with a single distant lock they cannot maintain. The forward measure already prices commitment, and it prices the commitment that still exists rather than the one that has already been served.

**What a client shows.** Each lock's unlock height, in blocks and as an estimate in days, and the height at which the reader's weight falls below each rung they currently hold. That is the ladder disclosure of section 6 applied to time, and it is the form in which it matters most: a decaying weight means every rung a reader holds has a date on it, and a client that knows the date and does not show it has withheld the only actionable thing about the condition.

**What it costs, stated here rather than left to the reader to find.** A timelock gate prices membership in forgone liquidity, which is a wealth test with patience added rather than an alternative to one, and section 10.1's warning about a room gated on wealth applies with the same force. It is also the harshest condition in this document for somebody who loses a key, because section 8.4 loses them their standing and the lock loses them the coins as well. And it is public: a lock is an on-chain object, so a reader satisfying a demanding gate has announced both the amount and the term to anybody watching, which no stored grant would have disclosed.

#### 4.6 The ban condition

A participant banned from a room does not read it. This is the one condition with no entry in a manifest, and the omission is deliberate: a ban is not a rule the room states about who may enter, it is a decision a moderator took about one person, and section 2.1 forbids a manifest naming the identities it admits for the same reason it should not name the ones it excludes — a ban array is a membership list with the sign flipped. So the condition is on for every room, gated or not, and needs no configuration.

1. The facts are the **room's own record**, not a global store. A ban is created by section 7.2 as an attributed statement against the handle, scoped to the room, and it is recorded where the room's contents are. A client evaluating a room already holds them.
2. That is why section 3.1 evaluates the ban first. It is the only condition answerable without a network lookup, and the only one a person in the room decided.
3. Only statements scoped to **this** room count. A room-scoped statement does not satisfy a renounce gate elsewhere (section 7.2), and the converse holds: a statement written about somebody outside this room is not a ban in it. A client that conflates the two lets one moderator's decision follow a person across the network, which section 7.2 exists to prevent.
4. Where a client cannot determine whether a ban is in force — it holds a partial record, or the room's contents are served by something it cannot reach — the verdict is indeterminate. This differs from the renounce gate of section 4.3, which admits on an unreachable source: there, absence of a claim and absence of a lookup are the same state and an open room should not be closable by knocking over a service. Here the record is the room's own, so failing to read it means the client does not know what the room decided rather than that the room decided nothing.
5. A ban is **lifted** by a signed statement referencing it, from any role that could have imposed it (section 7.2). Where both a ban and a lift exist, the later-signed one is current. Nothing is deleted: a lifted ban remains readable, since a moderation record that erases itself cannot be reviewed and the subject has as much interest in the lift being visible as in the ban being so.

### 5. Recurring Fees

A holding gate with a minimum may also charge for continued access. This is a different kind of thing from everything above, and the two sit next to each other in every interface that offers both: the gates of section 4 are predicates over facts that already exist, and a reader satisfies one by being who they are. A fee is an obligation the room creates, satisfied by paying, again, for as long as they want to keep reading.

A fee is not specified without a minimum, and is discarded when the minimum is removed. A charge for holding nothing is a subscription, BRC-218 section 5.6 already specifies subscriptions with disclosure rules a room-settings screen does not have, and a subscription wearing a gate's clothes is a recurring outflow hidden inside a screen about who is allowed in. It is denominated in the **same asset** as the minimum, since a threshold in one asset and a charge in another are two unrelated demands sharing a control. It carries a period from the BRC-218 section 2 `period` rule, and it **names its recipient** as a handle, resolved and displayed as a payee is — an unnamed charge cannot be refused, audited or attributed, and a room collecting from its participants without saying to whom has made all of them party to something none of them can inspect. Amount, period and recipient are disclosed together before the reader commits; disclosing them apart converts a decision into a discovery.

**A room cannot pull a fee.** Enforcement is the gate itself: stop paying, stop meeting the condition, contents stop rendering. Any arrangement in which the room debits a participant is a standing authority, and standing authority is BRC-218 section 5.6 with its disclosure and its visible list, not a room setting. A period once paid is paid, and a change raising a fee does not reach a period already settled.

Two rules make the fee evaluable rather than merely stated. A payment MUST be identifiable as **this room's fee for this period**, labelled with the room identifier and the period using the transaction labelling of [BRC-65](../wallet/0065.md) — "has this person paid" is only answerable to a second client if the payment says what it was for, and without that the fee is not a gate condition at all. Where a client cannot enumerate payments to the recipient the condition is indeterminate rather than failing; the reader may well have paid. And the first period is settled as part of joining rather than invoiced afterwards, since self-service admission would otherwise make every new arrival immediately in arrears — admitted, unpaid, and failing the condition they just satisfied.

**A fee is not offered on a timelock condition.** A lock already prices entry in forgone liquidity, and adding a periodic outflow to an immobilised balance charges a reader twice for the same commitment while removing the funds they would meet the charge from. A room wanting both should hold and charge, per section 4.1, rather than lock and charge.

A fee is not a **toll** (BRC-169 section 8.2) and is not described as one. A toll is per-envelope, paid to the recipient, enforced at their messagebox; a fee is periodic, paid to a named party, and enforced by nothing but the gate.

### 6. What a Failing Reader Is Told

A reader who fails a gate still sees that the room exists, its name, and the **number** of participants — not the participant list, not the messages, not anything derived from them. This is not a courtesy. A room invisible to those outside it cannot be asked about, cannot be joined on request, and is indistinguishable from a room that does not exist, so the people who most need to find their way in are the ones who cannot see there is a way in. Showing the shape and withholding the contents is what makes a gate a door.

The reader is told **which requirement they do not meet**, rendered from the verdict structure, in their own terms and with their own arithmetic: "you cannot read this" is not actionable, and "holds 12.32 BSV of the 21.8 required" is. An indeterminate verdict is presented as such rather than worded as a failure. A client may offer the action that would satisfy the condition, which is a value-moving action authored by a third party and passes BRC-218 section 4 confirmation in full. Nothing shown to a failing reader discloses another participant's standing, holdings, or reason for lapsing.

**The whole rule is shown, not only the part that failed.** Section 3.1 reports the first failure, which is the right thing to put at the top and the wrong thing to show alone: a reader told they lack the holding, who satisfies that and is then told they lack the lock, has been walked through a rule they could have read at once and has spent money finding out its second clause. A client therefore renders every condition that is on, marking which the reader meets, which they do not, and which could not be checked. This costs nothing — the manifest is readable by anyone under section 2, so nothing is disclosed that a determined reader could not assemble — and it is the difference between a door with its terms posted and a door that answers one question at a time.

**The ladder is shown with it.** A room whose `roles` condition is on is stating two rules and not one: what admits a reader, and what a participant must reach to moderate or administer. Both are in the same signed manifest and both are equally public, so a client shows the rungs alongside the door — for a timelock gate, what must be locked and for how long to enter, and what weight reaches mod and admin. Withholding the ladder from somebody deciding whether to commit hides the part of the offer they are most likely to be deciding on, and a room that would rather not publish what its moderators had to do has chosen the wrong mechanism, because section 2 publishes it regardless.

**A participant already inside is shown their own position on it.** The rung they hold, the next one, and what would reach it, with their own quantities under section 3.3 and nobody else's. This matters most where standing decays: a lock weight falls as its locks mature (section 4.5(2)), so a moderator can be demoted by the calendar while doing nothing wrong, and a client that surfaces the drop only at the moment the role disappears has turned a warning into a surprise. Section 7.1 requires that a demotion be told directly; showing the distance beforehand is what makes that requirement useful rather than merely correct.

A participant may also cease to satisfy a room they are already in. That is surfaced rather than resolved silently — whether they are removed is the room's policy, and a client removing them quietly produces a roster that disagrees with the door. The roster marks that they no longer qualify without quantifying the shortfall; the participant themselves is told directly and in full, rather than left to infer it from an icon beside their own name in a list they may never open. Lapsing is not retroactive: a participant who has already received messages has them, and section 9 explains why no client can change that.

### 7. Roles

A gate answers whether somebody may read a room. Every room beyond a few people needs a second answer — what they may do once inside — and the temptation is a list of appointed moderators, which is the administrator-shaped object of the Motivation reintroduced one level down.

So roles are derived from the same conditions as the door. Nobody is _made_ a moderator; they hold a rare item, or 21.8 units, or a vouch from a handle the room named. What follows is the point: sell the item and the role goes with it, on the same evaluation that would have kept them out.

#### 7.1 Derivation

There are three roles — **member**, **mod**, **admin** — and no others, each additional rung being one every participant must reason about before answering "can they delete my message". A role is never assigned by naming an individual. It is read off a condition the room already gates on, and this document describes four mappings:

- **By rarity**, for a non-fungible contract publishing bands. A band is a **threshold**, not a set: assigning mod to Rare makes every band above Rare a mod too, since a rarer item buying a lesser role is a rule nobody would guess and one that silently demotes the Legendary holder standing beside the Rare. The ordering comes from the contract; a client assuming one would eventually meet a collection whose "Exotic" is its commonest tier.
- **By amount**, for a fungible token, as an absolute quantity or a share of supply (section 7.3). Thresholds rise with the role, and a map where the mod threshold exceeds the admin one contradicts itself.
- **By named entity**, for a vouch or renounce gate: the handles the gate names are admins. This is an explicit choice rather than an assumption, because wanting somebody's vouch as a door is not the same as handing them the room.
- **By attestation count**, for a quorum gate: three attestors to moderate, ten to administer, subject to the same distinctness the gate itself uses.
- **By lock weight**, for a timelock gate: the product of satoshis locked and blocks remaining, summed across the reader's locks per section 4.5(6). This is the mapping in which the two dimensions the gate reads separately are traded against each other — a smaller amount committed for longer reaches the same rung as a larger one committed briefly, and the room decides the exchange rate by where it puts the thresholds. It is also the only mapping here that moves without anybody acting: a weight decays as its locks mature, so a moderator who does not re-commit is demoted by the calendar. Section 7.3 says what that does and does not cap.

Where several apply, the strongest role wins. A role is re-evaluated with the gate, and a participant who stops meeting the condition is demoted and told directly rather than left to find a control missing. A role may be shown beside a name — it is qualitative, so section 3.3 permits it — while the quantity behind it remains nobody else's business.

#### 7.2 Capabilities

A **member** reads the room and posts in it. A **mod** also deletes messages and bans members. An **admin** also bans mods and closes the room; changing the manifest is not among them, being custody's under section 2.3.

Banning is strictly downward and never sideways: no role bans its own rank. An admin banning an admin is the one action that can empty a room's admin set in a single click. **Custody is not a rank** — the holder sits underneath the ladder rather than on top of it, may act on any participant, and cannot be acted on; without that, an ungated room where everybody is an admin has nobody able to act on anybody. Deleting a message leaves a record that a message was removed rather than silently closing the gap, since a transcript that rewrites itself is one nobody can reason about afterwards.

A **ban** is recorded as a statement against the handle under BRC-169 section 10.7, scoped to the room — so it has an author, a time and a claim, which a row in a ban array does not. It is **attributed**, reversing BRC-169 section 10.7.1's default: that default protects somebody speaking against a peer at their own risk, and a moderator acting inside a room they moderate is in the opposite position. A room-scoped statement does not contribute to the subject's standing anywhere else and does not satisfy a renounce gate in another room; being unwelcome in one room is not a reputation, and a mechanism letting one moderator's decision follow somebody across the network would be worse than the list it replaced. The subject is told, and by whom, and any role that could impose the ban can lift it. How a ban is then read back as a condition — where the record lives, what scoping means at evaluation time, and what lifting is — is section 4.5.

Closing a room is a signed statement carrying who closed it and when; conforming clients stop accepting posts and render the room as closed. History remains readable, because section 9 is why nothing here can un-deliver a message, and a client presenting closure as deletion has promised something no client can perform.

#### 7.3 Thresholds that cap themselves

An absolute threshold admits an unbounded number of admins: a fixed bar means that as a room grows, a supply inflates, or a price falls, the number of people above it only rises. Two forms avoid that, and both work by capping the privileged population arithmetically.

**A share of supply** caps it directly. 6.9% admits at most fourteen people and 21.8% at most four, because fifteen holders of 6.9% would need 103.5% of the supply and five holders of 21.8% would need 109%. The ceiling holds for a room of eight and a room of eighty thousand. A proportional threshold names the total supply the contract publishes as its denominator — supply held by participants would make a role depend on who else is currently in the room, so leaving would promote everybody left behind — and is not offered for a token whose supply is unbounded or not knowable. The denominator and the band ordering below are both read from the contract through the same indexer as the holdings themselves, under section 4.1: they are the issuer's published facts rather than the room's, which is what makes the ceilings computable by anybody and why a client that cannot read them returns indeterminate rather than falling back to an absolute comparison. Comparison is exact: the threshold is met when `held × 100 ≥ threshold × supply`, on integers where the token is indivisible.

**A rarity band** is a percentage the issuer already computed. Reading a scarcity curve cumulatively produces the same self-limiting property, with the work done by the party who knew what the bands meant:

````

Naka Motor Club — 2,222 items

band in band band and above share of supply Exotic 22 22 0.99% Legendary 66 88 3.96% Epic 134 222 9.99% Rare 333 555 24.98% Uncommon 555 1,110 49.95% Common 1,112 2,222 100.00%

````

Choosing Exotic for admin is choosing the top 1% and capping administrators at twenty-two; choosing Rare for mod is choosing the top quarter. Neither number was picked by the room, and every client reading the contract computes the same ceiling, which is why a client shows the cumulative populations at the point of configuration — a room choosing between Rare and Epic is choosing between two ceilings it cannot otherwise see. A band also cannot be diluted, an item's rarity being fixed at mint; the inverse hazard is narrower, in that a contract able to mint _into_ a band can inflate the role assigned to it.

**A lock weight does not cap anything, and the section is not weakened by saying so.** Satoshi-blocks are unbounded: no quantity of them is 100% of anything, so a weight threshold is an absolute one and admits as many administrators as there are people willing to clear it. Everything this section says against absolute thresholds applies. What a weight has instead is a cost that continues — a rung held is a rung being paid for, in liquidity, for as long as it is held — where a rarity band is bought once and kept. The two properties are not substitutes: a room that wants a ceiling on its administrators should cap the top rung by rarity or by share and use lock weight for the rung that ought to scale with the room, which is the pairing this section already recommends for absolute and proportional forms. A room that uses weight for every rung has chosen an ongoing cost over a bounded population, and should be told so at the point of configuration rather than discovering it at forty administrators.

##### The shareholder ladder

The proportional form's natural reading is a cap table, and the thresholds a room reaches for are usually borrowed from company law. Borrow the shape, not the numbers.

Statutory thresholds sit far above what a room needs. A blocking minority is **above 25%** where a special resolution needs 75%, and **above one third** where it needs two thirds; amending articles of association needs the supermajority itself. Those numbers exist because a shareholder cannot leave — their capital is locked in, and a majority able to expropriate them will. A room is the opposite: exit is free, the stake is speech, and the worst an administrator can do is close the room, at which point everybody walks away holding what they walked in with. Protection that expensive buys nothing here.

| Role   | Share           | What it buys                 | Why there                                                                         |
| ------ | --------------- | ---------------------------- | --------------------------------------------------------------------------------- |
| Member | below 6.9%      | Read, post, be heard         | Participation needs no stake                                                      |
| Mod    | 6.9% and above  | Delete messages, ban members | Caps moderators at 14 — about the largest group that can moderate to one standard |
| Admin  | 21.8% and above | Ban mods, close the room     | Caps rule-enforcers at 4: collusion is visible, one absence is not deadlock       |

The rungs stand in a ratio of √10, so each is roughly three times as hard to reach as the one below.

Room size changes what these mean, in opposite directions for the two forms. A proportional ladder empties as a token disperses: ten million units across four thousand holders averages 0.025% each, so a room of two thousand derives no moderators at exactly the size needing the most. A rarity ladder does not, the privileged population being a property of the collection rather than of the room — two thousand holders drawn from a 2,222-item collection still contain most of the 555 items at Rare and above. Where a proportional ladder would empty, the answer is not a lower percentage, which abandons the ceiling: it is **absolute for the rung that scales with the room, proportional for the rung that must not.** Moderation is labour and should scale with how much of it there is; the authority to close a room is not.

Small rooms come out flat under either form. Three people holding a third each are all administrators and none may ban another, which is the right answer — three co-owners need no hierarchy — and an equal five-way split lands at 20% and derives no administrator at all, since 21.8% sits just above one fifth. Section 7.4 is what keeps such a room governable.

#### 7.4 Custody

A derived ladder can empty itself: the only Exotic holder sells, the thresholds are set above what anybody holds, a room is founded by somebody holding a Common. Then there is no admin, and appointing one is a manifest change only an admin could make. Nothing about deriving roles is wrong; what is wrong is having nothing underneath the derivation.

The **holder** is an admin regardless of what any condition derives, and is **exempt from the room's own gates** — without the exemption a room can lock out its own administrator by naming an attestor who later renounces them, or by being founded by somebody below its own threshold. A client shows who holds the room and shows it as custody rather than qualification, an exemption nobody can see being a backdoor and one stated plainly being the reason the room still works. There is no separate guard against a room deriving no admin, because there cannot be one: the holder is an admin unconditionally, a holder is mandatory, and only the holder can submit a configuration at all.

A client warns before applying a manifest under which no current participant would qualify. Unlike the above this is legitimate — a room may tighten and expect people to lapse — so it warns rather than refuses, and does not apply silently.

Where the holder is not a participant and no participant derives admin, a client treats one participant as **acting admin**: highest derived role, then the earliest message attributable to them, then the lowest identity key as an unsigned integer. The first two are the room's own record; the third is arbitrary, is included because the first two can tie, and is written down because an arbitrary rule every client applies identically is worth more than a fair one they each invent. Such a room stays moderatable, which is the most succession can offer — its rules cannot be changed by anybody but a holder who is no longer there. Custody is transferable, and a transfer is reported like any other change to who runs the place. Section 8.3 covers the case where the holder cannot sign a transfer because they can no longer sign anything.

#### 7.5 A room with no gate

Where a room has no manifest, or none of its conditions is on, **every participant is an admin**. This follows from roles being derived rather than being an exception to it: with no band, amount or attestor to read, the only ways to produce a hierarchy are to appoint somebody or to withhold every power from everybody. The first is the list this design refuses to keep; the second makes an ordinary group chat unmoderatable and then requires an appointment to fix it. Flat means everybody at the top rather than everybody at the bottom.

A client does not badge a role every participant holds — a badge distinguishes, and in a flat room there is nothing to distinguish, the holder excepted. Only the holder can gate such a room, which is section 2.3 rather than a rule of its own: every participant being an admin does not make every participant able to sign, and without that a member could enable a condition only they satisfy and take the room.

### 8. Preventing "Not Your Keys, Not Your Room"

Four mechanisms in this document read a key. A holding gate asks what outputs a handle controls (section 4.1). A vouch or quorum gate counts certificates naming the reader as subject, each signed by an attestor's key (sections 4.2 and 4.4). A role is derived from whichever of those the room uses (section 7.1). And custody is a single key, the only one permitted to sign a manifest (section 2.3). Each is checkable by a stranger precisely because it reduces to a signature or an unspent output — and each fails the same way when the key behind it is gone.

Who loses what is worth being specific about, because the three cases are not equally bad. A reader stops qualifying, and the room stops rendering. A moderator is demoted by the same evaluation that would have kept them out, which is what section 7.1 is designed to do and is the right outcome only when the demotion is real. A holder loses the ability to amend the room at all: section 2.3 admits no other signer, section 7.4 denies the power to an acting admin, and the room therefore keeps whatever rules it had, permanently. That third case is the severe one, and section 8.3 is about it.

What a client actually observes is much narrower. Under BRC-169 section 4.4 it compares the identity key a fresh resolution returns against the one it already recorded for that handle, and finds the two differ. That single observation has two causes, and nothing in the resolution response separates them: the person rotated to a new key, or the handle was released and reassigned to somebody else — which BRC-169 section 2.1(4) permits and its section 4.4 exists to catch.

Both of the simple rules are therefore wrong. Carry standing across every key change, and a reassigned handle inherits the previous holder's admissions, roles and room: the impersonation BRC-169 section 4.4 was written to prevent, with a room attached to it. Carry it across none, and section 3.2's cache discard becomes permanent dispossession for anybody who rotates a key for a good reason — which is the behaviour that would make "not your keys, not your room" true here rather than something to design against. So the question is not whether standing survives a rotation. It is what evidence distinguishes a rotation from a reassignment, and BRC-169 already publishes three kinds of it. Section 8.2 sets them out; section 8.1 first disposes of a loss that is largely illusory.

#### 8.1 Holdings are not the identity key

A gate asks what a handle holds, which is a different question from which key signs for it. Assets sit at derived or separate keys, so evaluating a holding gate reads the declared holding keys of section 4.1, and that declaration is not the identity binding. It follows that rotating an identity key does not move anybody's coins: the holding key is unchanged, the outputs at it are unchanged, and a client that zeroes a reader's holdings because their identity key changed has confused two different lookups.

What a rotation does invalidate is the *declaration*, since it was signed by the key that has been replaced. That is a stale attestation rather than an absent holding, and section 8.2 is what decides whether to accept a re-declaration under the new key.

Section 3.2 requires a cached verdict to be discarded on a key change, which remains correct — the verdict must be recomputed. It does not follow that the recomputation should find nothing.

#### 8.2 A rotation the room can verify

Three kinds of evidence can show that a rotation is a rotation, and they are available in different circumstances.

The **old key signs the handover**. This is the ordinary case and it covers a key being retired rather than lost. It is also unavailable exactly when it is most needed.

The **ecosystem reissues**. Within its own namespace a host can rebind a handle to a new key, which is a power BRC-169 already grants it and which every client already trusts for resolution. It is the cheapest recovery and the one that concentrates the most trust in the host.

The **prior attestors say so**. A quorum of the handles that had already vouched for a reader attest the new binding. This needs no new primitive — it reads the same store as section 4.4, counted the same way — and it has the property the other two lack: an attestor who signs a false recovery has staked the name that made their vouch worth anything in the first place. It is also the only one of the three available to somebody who has genuinely lost a key rather than retired it.

A room may treat any of the three as carrying prior standing forward, and a room gating on something valuable should prefer the third. The second asks the reader to trust their host; the third asks them to trust the people who already spoke for them, which is the trust the gate was reading anyway.

#### 8.3 Custody, and the room that freezes

The severe case is the holder. Only the holder signs a manifest (section 2.3) and an acting admin cannot (section 7.4), so a room whose holder has lost their key keeps the rules it has, permanently: never re-gated, never ungated, never handed on. It stays moderatable and becomes unamendable, which is a slower failure than a locked door and not a better one.

A manifest may therefore name a **successor** — one handle, with a `dormantAfter` height. Where the holder has signed nothing for that many blocks, the successor may sign a manifest transferring custody to themselves, under the ordinary notice of section 2.4 and reported like any other change to who runs the place. A holder resets the clock by signing anything at all, including a manifest that changes nothing, so the mechanism costs an active holder one transaction whenever they choose to make it and costs an absent one nothing they were using.

Two alternatives were considered and rejected. A vote among participants is a membership list with a ballot attached, and it re-creates the register this document exists to avoid. A host able to reassign a room puts the room back in a vendor's table, which is the arrangement the Motivation opens by objecting to.

The successor is the one place a manifest names an individual, and it is worth saying why that is not the list section 2.1 forbids. A successor is not admitted by being named. They gain no access they did not already have, the naming grants nothing at all until the dormancy expires, and what they may eventually receive is custody rather than membership — the obligation to sign, not the right to read.

#### 8.4 What is not recoverable

A reader with no attestors, no successor and no ecosystem willing to reissue has lost their standing along with their keys, and nothing here reconstructs it. That is what a stake means: skin in the game is only skin in the game if it can actually be lost, and a document offering a way back from every loss would be describing something else.

What this section prevents is the avoidable version — the person who still holds the assets, still has the vouches, still has a host that will rebind them, and is treated as a stranger anyway because a client read a key change as a new person. Recovery is also cheaper before it is needed than after: a client should prompt for a successor at the moment a room is gated, since that is the moment the room acquires something to lose.

### 9. What a Gate Is Not

A gate decides what a conforming client renders. It does not encrypt, does not authenticate, and does not constrain a non-conforming client at all. Accurate wording says that conforming clients will not show the room's contents to readers who do not qualify; a client promising more has sold a UI toggle as a security boundary.

The enforcement point for who may **send** to an identity is that identity's messagebox, under BRC-169 section 8, exactly as a toll is enforced there and not in the sender's client. There is no equivalent for who may **read** a room whose contents have already been delivered. A room whose confidentiality matters must key its contents so that non-holders cannot decrypt them; that mechanism is out of scope here, and a gate is not a substitute for it.

A gate is therefore best understood as a rule the room states and conforming clients honour. That is a weaker claim than it sounds — it is the same claim as a mailing list's charter — and it is sufficient for the great majority of rooms, which want a shape rather than a secret.

### 10. Choosing a Gate

Everything above describes how a gate behaves. This section is about when to use one, because a document answering only _how_ leaves every client to discover _whether_ by shipping it.

#### 10.1 Rooms better left ungated

- **A room whose value is reach.** Announcements, support, anything somebody arrives at because they need something. A gate on a help channel excludes exactly the people who most need in, in the name of quality.
- **A room small enough to know each other.** Five people who have met need no predicate to decide who is among them. A gate replaces a question nobody was asking with a new failure mode: a friend locked out by a spent balance, at the worst moment, by a rule nobody remembers setting.
- **A room whose membership is a fact about people rather than holdings.** A family, a team. There is no public fact meaning "my colleagues", and a token approximating it is wrong in both directions — admitting strangers who bought in, excluding the colleague who did not.
- **A room where the real problem is one person.** Gating the room to exclude somebody is collective punishment with a technical veneer. A ban is the instrument for one person.
- **A room needing confidentiality**, per section 9.
- **A room whose archive might one day be opened**, since ungating is irreversible.
- **A room whose members may need their money.** A timelock gate is the one condition here that takes something away for a fixed term rather than reading something the reader already has. Where members are not in a position to immobilise capital, it selects for who can afford to wait rather than for who belongs.

The general form: **gate on a fact, never on a feeling.** Where you cannot write the predicate down, do not approximate one — an approximate gate is wrong about specific people, and they can read the rule that is wrong about them.

One consequence is about time rather than about any room, and is invisible on the day the gate is set. A holding gate gives standing to holders and roles give holders the policing of the room, so a room gated on wealth tends to become a room moderated by its wealthiest members, in their interest, without anybody deciding it should. What limits the drift is that moderation is all it reaches — custody keeps the rules — and where ownership is not genuinely the criterion, a vouch or quorum gate has neither property.

#### 10.2 Which gate suits which room

| The room                                       | Suited                             | Why                                                                                    |
| ---------------------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------- |
| Small, informal, people who know each other    | **None**, or renounce              | Nothing to enforce; at most keep out a known bad actor                                 |
| Small, formal — a board, a working group       | **Vouch**, named                   | The criterion genuinely is one person's judgement, and there are few enough to name    |
| Large, open to strangers, informal             | **Renounce**                       | The only gate admitting by default, so it scales to people you have never heard of     |
| Large, anonymous, formal — holders, a treasury | **Holding** or **share of supply** | The room is about the thing held, and ownership is the criterion honestly              |
| Large, open to strangers, where cheap accounts are the problem | **Timelock** | The only condition that cannot be rented, borrowed or assembled for one evaluation; it costs patience as well as capital |
| Large, dispersed, tight-knit — a guild, alumni | **Quorum**                         | Nobody knows who to name, but the network already knows who is vouched for             |
| A collection's room, with published bands      | **Rarity**                         | The issuer designed the scarcity curve; section 7.3 reads it rather than inventing one |
| Any room with running costs                    | A **fee** on a holding gate        | Never on its own                                                                       |

Four axes change the answer. **Formal against informal**: a formal room can say what its rule is, while an informal one usually cannot, and the gate ends up inventing a rule to justify itself — if configuring the gate is the first time anybody articulated the membership criterion, the criterion is being invented by the tool. **Small against large**: gates pay for themselves at scale and cost more than they return below it, the crossover being roughly where members stop recognising each other's names, and section 7.3 gives the mechanical consequence for each threshold form. **Tight-knit against strangers**: a tight-knit room has a web of vouches to read, so a quorum gate works and nobody need be named, while a room of strangers has none — which is why a holding gate is attractive there and exactly why it is a wealth test. **Growing against settled**: a settled room can tighten cheaply because everybody who would be excluded has already left, while a growing room should gate early or not at all.

### 11. Not Specified Here

Three further gate types were designed and are left unspecified, along with one alternative route to satisfying the conditions that are specified, recorded so the next author does not rediscover them or ship them incompatibly.

**Stake.** The reader has paid into the room. Section 4.5 now specifies the half of this that needs no custodian: a timelock immobilises the reader's own coins at the reader's own key, so nobody holds them, nothing is refunded, and the room ending changes nothing — the lock expires and the owner spends what was always theirs. What remains unspecified is the custodial half, where the satoshis actually move to the room. That still needs what BRC-218 section 6 says `/gate` needs — custody, a rule for what happens when the room ends, and a refund path — and it belongs with that verb. The distinction is worth stating plainly, because the two look identical to a reader and differ entirely in what can go wrong: a lock can only be lost by losing a key, while a stake can be lost by whoever is holding it.

**Tenure.** Not how much somebody holds but how long they have held it — the most attractive of the three, because a threshold can be cleared in one transaction by somebody who arrived this morning and time cannot be acquired on demand. Two things stop it. It needs transfer history rather than balance state, a materially heavier dependency than anything in section 4. And it is weaker than it looks: tenure attaches to a key, and an aged key can be bought along with what it holds, while measuring continuous holding punishes a holder who moves an item between their own wallets for doing what every security guide advises.

**Proofs instead of lookups.** A reader could satisfy a quantitative condition by presenting a proof that they meet it, rather than by publishing the holding keys of section 4.1 and letting anybody add up the outputs. This is recorded rather than specified, and the reason is worth setting down, because the obvious version of it is a net loss.

Where a fact is publicly enumerable, any client can evaluate any reader, which is what makes a lapse detectable: sell the token and every client notices on the next render. A proof replaces that with something only the reader can produce, so the room can no longer re-derive their standing, and a reader who has sold their asset is indistinguishable from one who is offline. A proof is a row that goes stale — the failure the Prior Art section identifies in the product category, arriving by a different route — and the fix, short validity with mandatory re-proof, is the periodic re-check that section criticises. Anywhere the facts are already public, this trades a working property for one the document did not need.

The case where it costs nothing is the mirror image: a reader who has **not** declared holding keys cannot be evaluated by anybody today, so there is no lapse detection to lose. Their present choice is to publish the declaration, permanently and to everybody, or to stay out. A proof would be a third option that takes nothing from anyone else, since they were never inside. Confined that way — an alternative route for undeclared readers, never a replacement for public evaluation, and enabled per room so that a client which does not offer it needs no verifier — the trade is contained.

Four requirements fall out of the rest of this document, and any candidate that fails one is unsuitable rather than merely awkward. The setup MUST be transparent: a per-circuit ceremony makes whoever ran it able to forge admission to every room using that circuit, which is exactly the privileged reader the Prior Art section claims to have removed. Verification has to be cheap enough to run at render time, under the same reasoning that caps a manifest at sixteen entries in section 2.1. The proof has to carry the chain height it was made against, so that section 3.2 can bound its freshness rather than take it on trust. And it has to say what happens on lapse, because a specification that leaves that open has shipped a stored grant.

**Recency.** The handle registered before a date, or active within one. Age is the one property a spammer cannot buy, which makes it the most efficient anti-abuse condition and the most hostile to newcomers. It is unspecified because there is no fact to read: BRC-169's resolution carries no registration date, and a certificate's validity window measures reissuance rather than arrival. It would need a field, and a host's claim about the age of its own accounts is the claim it has most incentive to shade.

### 12. Relationship to Other Documents

This document subsumes and replaces BRC-218 section 11, which now stands as a pointer. Configuring a gate from a conversational interface is a BRC-218 concern; no verbs are defined or reserved here. `/gate`, reserved in BRC-218 section 6, remains the write half — charging admission, with custody of a stake — and the custody of section 7.4 is custody of the _room_, a different object carrying no funds. Every fact read by sections 4.2, 4.3, 4.4 and 4.6 is defined in BRC-169 section 10, and no way to create one is added here; sections 4.1 and 4.5 read chain state through an indexer, and define no new script, encumbrance or transaction format — a ban is that document's section 10.7 statement, scoped to a room by section 7.2 and read back by section 4.6.

## What a Gate Buys, and What It Costs

The case for a gate is made at the front of this document and the concessions to it are distributed across sections 8, 9, 10 and the Security Considerations below. A reader deciding whether to gate a room is entitled to see both totalled in one place.

**What it buys, against a stored grant.** It is auditable by the person it excludes, who can read the whole rule, including the ladder above the door, and say what would change the answer (sections 2 and 6); a list cannot be read by anyone it keeps out, and cannot even be distinguished from the absence of one. It is self-service, so nobody is asked and nobody's attention is the bottleneck (section 2). It is self-correcting, because there is no row to prune and standing lapses on its own when the fact beneath it does (section 3.2). It is portable, being a signed object rather than a configuration inside one platform (section 2.3). And it has no privileged reader: an operator evaluates the same predicate over the same facts as everybody else.

**What it costs.** Five things, and none of them is small.

*It cannot exercise discretion.* Everyone who satisfies the condition is admitted, including the person a human would have refused for reasons no predicate captures. Section 10.1's rule — gate on a fact, never on a feeling — says where not to use a gate; it does not repair this, because it cannot be repaired. A door priced in stake admits anybody who pays.

*It prices membership, and the strongest form prices it hardest.* Section 4.5's timelock is the one condition here that cannot be rented, borrowed or assembled for the moment of evaluation, and it earns that by demanding capital and patience together. It is a wealth test with a term attached rather than an alternative to one, it is the harshest condition in the document for somebody who loses a key, and it publishes what the reader committed. A room reaching for it because it is the strongest should be clear it is also the most exclusive.

*It makes private facts legible.* The facts a gate reads are individually public, but a gate collects them and attaches them to names, and the mitigations of section 3.3 are partial by the Security Considerations' own admission. A stored grant discloses nothing about why somebody is on it. This is a straight loss and there is no version of a public predicate that avoids it.

*Standing is lost with the keys that evidence it.* Section 8 recovers what can be recovered and section 8.4 states the limit plainly. An administrator with a table simply restores access; nothing here can.

*It is not confidentiality.* Section 9 is the whole of it. A gate decides what a conforming client renders and constrains nothing else, which is a weaker claim than users will hear.

*It can fail to answer.* An unreachable source yields indeterminate rather than a decision (section 3.1), and a room can therefore be unenterable for reasons that have nothing to do with the reader. A table read has no such state.

**On the rule-setter being exposed to their own rule.** The Motivation claims it and section 7.4 exempts the holder from the room's own gates, which is a real tension and worth answering rather than leaving to a reader to notice. The exemption is from the mechanism, not from the consequence. A holder who draws a rule too tight is left holding a room nobody can enter; one who draws it to admit everybody has a room worth nothing to be in. What disciplines them is not the gate but the exit: participation is voluntary, the stake is usually speech rather than capital, and a room whose rule is wrong loses the people it was for. That is a weaker constraint than being subject to your own gate, and it is the one that actually operates.

**Except under a timelock, where the exit is not free.** Section 4.5 immobilises a reader's coins for a term they cannot shorten, so the discipline just described is suspended for as long as the lock runs: a participant who wants out waits, and a holder facing them faces somebody who cannot walk away. It is the one place in this document where the mechanism that keeps a holder honest is switched off by the strongest condition on offer. A room choosing that gate is asking its members for more trust than any other, and should be told as much at the point of configuration — the strength of the condition and the weakness of the constraint on the person who set it are the same property seen from the two ends.

**On the drift the design invites.** A holding gate gives standing to holders, and section 7 gives holders the policing of the room, so a room gated on wealth tends to become a room moderated by its wealthiest members, in their interest, without anyone deciding it should. The Security Considerations add that such a role is purchasable at whatever it costs, and that whoever can mint can dilute a moderator out of one. These are consequences of refusing to keep a list of appointees, not defects in the refusal, and they are the price of that choice rather than an argument that the choice is free.

The honest summary is narrow. A room whose membership is a fact about holdings, attestations or conduct is better served by a rule that can be read, checked, satisfied and lost than by a row nobody outside can audit. A room whose membership is a fact about people is better served by the row, and section 10.1 says so.

## Security Considerations

**A gated roster is a balance oracle.** The facts a gate reads are individually public; a gate makes them legible in bulk and attaches them to names. An administrator who can edit a manifest and watch a roster re-evaluate can binary-search any participant's holdings in a handful of edits. Section 2.5 removes the one place this document previously required that operation — classifying a change under 2.4 is a comparison between manifests and not an evaluation against members — and section 3.3 is the mitigation for the rest of it, partially: strip every quantity from a verdict about somebody else before it reaches an interface, and treat "preview this manifest against the current members" as the oracle it is. The underlying disclosure is inherent in evaluating a public predicate over a known set of people.

**A named source is attacker-influenced input, and a verdict oracle.** A manifest may name where to look for facts, and both `source` fields carry the same two hazards: an indexer or lookup chosen by the room sees every reader who evaluates the room, which is the disclosure section 3.3 refuses to send to the room itself arriving by another route, and one that lies decides admissions for anybody who defers to it. Section 4.1 is why a client should prefer its own indexer for a token condition and must never treat a named one as authoritative. Section 4.2's case is worse, because there the stores genuinely differ and a client may have no alternative to the one named.

**A manifest is attacker-influenced input.** Anyone who can create a room can name any contract, any handle and any fee recipient. Offering the reader "here is what would satisfy this" is therefore a payment prompt authored by a stranger and rendered inside their own client; it passes BRC-218 section 4 confirmation in full, and a client treating it as a room feature has built a phishing surface with the room's credibility behind it.

**Unattributed statements must never gate.** Section 4.3 is a security requirement rather than an etiquette one. BRC-169 section 10.7.1 makes negative statements unattributed by default so they can be made at all; wiring that store into an exclusion mechanism would let an anonymous claimant lock a target out of every room using the gate, with no name to answer and no cost to the claim. The two mechanisms are safe separately and dangerous composed, which is exactly the interaction a client author will not notice.

**A vouch gate is a signature dependency.** Naming a handle makes a room's admissions a function of one party's ongoing behaviour, and of nobody noticing if that handle is later recycled. BRC-169 section 4.4's key-change rule applies to attestors as much as to counterparties.

**A tightening can be unsatisfiable by a reader whose funds are locked.** Section 2.4 gives a participant 1,008 blocks to keep their access when a change would exclude them, which assumes they can act. A timelock condition breaks that assumption in the one direction that matters: a reader whose coins are committed for longer than the notice period cannot add to them without new capital and cannot recover them at all, so the notice runs out while they are able to do nothing. A room raising a timelock threshold is excluding, with warning, participants who are demonstrably the most committed to it. A client should say so at the point of configuration, and a room should expect it.

**A locked reader cannot leave, and cannot be made whole.** Every other condition here can be abandoned at will: sell the token, and the room stops rendering. A timelock cannot, so a reader who regrets a room, or who finds it is not what it appeared to be, waits out a term chosen before they knew. Nothing in this document or any client can shorten it, and a room that behaves badly toward somebody whose capital is committed faces a participant with no exit — which is the opposite of the discipline the closing section relies on.

**A lock is a public declaration of means.** Satisfying a demanding timelock gate publishes both the amount and the term to anybody watching the chain, indefinitely and to people who never saw the room. Section 3.3 keeps quantities about a reader inside their own client, and cannot reach this: the disclosure is made by the act of qualifying rather than by the evaluation of it. A stored grant discloses nothing of the kind, and section 4.5 states the cost where a reader will meet it.

**A purchasable role is a purchasable moderator.** Deriving a role from a rarity band or a share of supply puts it on sale at whatever that costs, to anybody, without the room being consulted. That is the honest consequence of refusing to keep a list of appointees, it is sometimes the right trade, and a client should say so at the point of configuration rather than leave it to be discovered.

**Supply is a lever the issuer holds.** Where a role is a share of supply, whoever can mint can dilute a moderator out of their role without touching their balance; where it is a rarity band, a contract that can mint into the top band manufactures administrators for every room gating on it. Neither is visible to the holder as anything but a verdict flipping, which is why the fixedness of a supply is disclosed before a room configures a threshold against it.

**Custody concentrates what derivation spreads.** Section 7.4 puts one identity underneath the ladder so the ladder cannot empty, which makes that identity worth attacking and its key worth losing. Succession limits the damage of abandonment and does nothing about a compromised holder, who can rewrite the manifest and close the room.

**A flat room is one signature from a captured one.** Every participant of an ungated room is an admin, and only section 2.3's holder can sign a manifest — which is what stops any of them writing the room's first gate around everybody else. A client that lets a role sign a manifest has built a one-click takeover into every group chat that never configured anything.

**A ban is a signed public claim.** Scoping keeps a room-scoped statement out of the subject's general standing, but the statement is written by a named party and readable by anybody. Moderating in public is the price of moderating attributably.

**Fail-open on missing data is the classic failure**, and a client that cannot reach a source and admits anyway has a gate any network condition disables. The indeterminate state exists so implementers are not forced to choose between two wrong answers.

**A gate is not confidentiality, and users will assume it is.** Section 9 exists because the mistake is one of wording rather than of code, and wording is what users act on.

## Implementations

The reference implementation is the Nexus client. What it covers is listed rather than summarised, because "implements this specification" is the claim a reader most needs to be able to check.

**Implemented.** Sections 4.1, 4.2 and 4.3; the disclosure rules of 3.3 and 6; lapse handling; and all of section 7 except as below, including the band ceilings, the flat-room rules and the confirmation before a gate is switched off. Verdicts are produced as structures and rendered afterwards.

**Specified and not yet implemented.** Section 4.5's timelock gate, its lock-weight ladder in section 7.3, and the ladder disclosure of section 6. Section 4.4's quorum gate and its count thresholds. The proportional form of section 7.3; the client offers absolute amounts only. The manifest encoding of section 2 and its integrity checks: the prototype holds a manifest in memory and identifies contracts by display name, which section 2.2 forbids and which is the largest remaining distance between the client and this document. Section 5's fee is configurable but not settled, nothing there verifying a payment.

**Depends on infrastructure that already exists.** Section 4.1's holdings, supply and rarity bands are read from a token indexer, which any client displaying a balance already runs or already calls; section 4.5's locks are read the same way, and require the indexer to report an output's encumbrance and unlock height rather than only its value. This is the one external dependency the document does not have to argue for.

**Depends on something no ecosystem serves yet.** Section 4.2's attestation discovery. BRC-169 section 10.8 specifies where the answer belongs — the resolution endpoint of section 5.7, as a list of attestation outpoints with their certifiers — but it is a SHOULD, and no ecosystem answers it today. Until one does, a vouch or quorum gate cannot be evaluated by a stranger's client; the prototype reads a local store, which works only because everything in it is local. The gap is in deployment rather than in specification.

A minimal implementation supports section 4.3 alone with sections 2, 3, 4.6 and 6. Renounce is the cheapest gate to evaluate, the only one admitting by default, and the one a room open to strangers actually needs; section 4.6 is not optional in any implementation, since a room whose bans are not enforced is not moderatable.

## Test Vectors

### Canonicalisation and digest

The manifest below, canonicalised per RFC 8785 and hashed with SHA-256. An implementation that produces a different digest from the same object has a canonicalisation bug, and its signatures will not verify against anybody else's.

```json
{
  "version": 1,
  "counter": 7,
  "room": "room:nexus.app/naka-motor-club",
  "holder": "@crumbs@nexus.app",
  "signedAt": 921600,
  "ungateBurn": "100000",
  "token": {
    "on": true,
    "mode": "any",
    "assets": [
      {
        "protocol": "1sat",
        "id": "4449436086e3cd58b73508aa498d886f62a5e88a049d3e27d49d2c9b90add05a_0"
      }
    ]
  },
  "roles": {
    "on": true,
    "rarity": { "admin": "Exotic", "mod": "Rare", "member": "Common" }
  }
}
````

Canonical form, 351 bytes:

```
{"counter":7,"holder":"@crumbs@nexus.app","roles":{"on":true,"rarity":{"admin":"Exotic","member":"Common","mod":"Rare"}},"room":"room:nexus.app/naka-motor-club","signedAt":921600,"token":{"assets":[{"id":"4449436086e3cd58b73508aa498d886f62a5e88a049d3e27d49d2c9b90add05a_0","protocol":"1sat"}],"mode":"any","on":true},"ungateBurn":"100000","version":1}
```

SHA-256:

```
75e2c17e9f7b5d2a62ce5a6c3dbe8461b64c69c4eda226a69755e7794ba50a17
```

### Ordering

A manifest with a renounce condition naming `@deggen@lkup.net` and a holding condition requiring an asset, evaluated against a reader who is both renounced and holds nothing. Section 3.1 fixes which failure is reported:

```json
{ "outcome": "fails", "condition": "renounce" }
```

Not `token`. Both conditions fail; the order decides, and an implementation reporting the holding has an iteration-order bug that will show up as two clients giving a reader different reasons.

### Decimal comparison

A minimum of `"0.3"` against a holder of `"0.1"` plus `"0.2"`. Compared as decimals the holder meets it exactly; compared as IEEE-754 doubles the sum is `0.30000000000000004` and the holder appears to exceed it. Both admit here, so the bug is invisible — which is why the vector below is the one that matters.

A minimum of `"0.3"` against a holder of `"0.29999999999999998"`. As decimals this fails. As doubles it is indistinguishable from `0.3` and passes. An implementation admitting this reader is not conforming, and section 2.1's decimal rule exists for exactly this case.

### Third-party disclosure

The same failing verdict, computed for the reader themselves and for anybody else:

```json
{ "outcome": "fails", "condition": "token", "index": 0, "shortfall": { "held": "12.32", "required": "21.8" } }
{ "outcome": "fails", "condition": "token", "index": 0 }
```

An implementation emitting the first form in a roster, a picker, a preview or an export has leaked a balance, and section 3.3 forbids it on every surface rather than only the visible ones.

## Appendix A: Worked Examples

### A.1 A rarity-gated room

```json
{
  "version": 1,
  "counter": 7,
  "room": "room:nexus.app/naka-motor-club",
  "holder": "@crumbs@nexus.app",
  "signedAt": 921600,
  "ungateBurn": "100000",
  "token": {
    "on": true,
    "mode": "any",
    "assets": [
      {
        "protocol": "1sat",
        "id": "4449436086e3cd58b73508aa498d886f62a5e88a049d3e27d49d2c9b90add05a_0"
      }
    ]
  },
  "roles": {
    "on": true,
    "rarity": { "admin": "Exotic", "mod": "Rare", "member": "Common" }
  }
}
```

Signed by `@crumbs@nexus.app` over the RFC 8785 canonical form. The contract publishes 2,222 items in the six bands tabulated in section 7.3.

`@randy@twetch` holds item #1872, band Epic. No renounce, vouch or quorum condition is present, so the holding condition decides:

```json
{ "outcome": "qualifies", "condition": "token", "index": 0 }
```

Epic is above Rare in the contract's order and below Exotic, so the role is **mod**. The client renders a badge; it does not render the band to other participants, a band being a quantity about a third party.

`@lena@handcash` holds nothing from the contract:

```json
{ "outcome": "fails", "condition": "token", "index": 0 }
```

No `shortfall` — the condition is a holding rather than a threshold, so there is no quantity to fall short by. She is shown the room's existence, name and participant count, and told which requirement she does not meet.

If the client cannot reach an indexer for the contract:

```json
{ "outcome": "indeterminate", "condition": "token", "index": 0 }
```

Contents are not rendered, and she is told the check could not be performed rather than that she failed it.

### A.2 A fungible gate with a fee

```json
{
  "version": 1,
  "counter": 2,
  "room": "room:nexus.app/holders",
  "holder": "@deggen@lkup.net",
  "signedAt": 921740,
  "ungateBurn": "2500000",
  "token": {
    "on": true,
    "mode": "any",
    "assets": [{ "protocol": "bsv21", "id": "a19c...f3" }],
    "minimums": { "bsv21:a19c...f3": "500" },
    "shares": { "bsv21:a19c...f3": "21.8" }
  },
  "fee": {
    "on": true,
    "asset": { "protocol": "bsv21", "id": "a19c...f3" },
    "amount": "0.5",
    "period": "week",
    "to": "@treasury@lkup.net"
  },
  "roles": {
    "on": true,
    "minimums": { "mod": "5000" },
    "shares": { "admin": "21.8" }
  }
}
```

The minimum is the door at 500 units; the share is the admin threshold, permitted only because a minimum is present. The fee is in the same asset, names its recipient, and its first week is paid as part of joining. The role map uses both vocabularies — mod by absolute amount, admin by share — which is the pairing section 7.3 recommends for a room larger than its concentration.

A reader holding 6,000 units of a 10,000,000 supply, paid up:

```json
{ "outcome": "qualifies", "condition": "token", "index": 0 }
```

Their role is **mod**: 6,000 clears the 5,000 threshold, and 0.06% of supply does not clear 21.8%. Three weeks later, having stopped paying:

```json
{ "outcome": "fails", "condition": "fee", "period": "2026-W34" }
```

The holding still passes and the fee does not. They are told directly and in full; the roster shows them lapsed without saying by how much. Nothing is deducted and nothing pursues them — the only consequence of not paying is that the room stops rendering.

## References

* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-65: Transaction Labels and List Actions](/wallet/0065)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [BRC-218: Chat-Native Command Grammar for the Metanet](/apps/0218)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785)
* [OASIS eXtensible Access Control Markup Language (XACML) Version 3.0](http://docs.oasis-open.org/xacml/3.0/xacml-3.0-core-spec-os-en.html)
* Matthew 13:23, GNV


# Verifiable Time Allocation

Crumbs, Luke Rohenaz

## Abstract

This document specifies **time allocation records**: a per-week, chain-anchored commitment by a [BRC-169](/peer-to-peer/0169) handle to how its holder spent the 168 hours of that week, together with a disclosure mechanism that reveals chosen hours to chosen handles and nothing to anybody else.

A week holds 168 hours and no more. That bound is the whole contribution. An invoice can claim any number of hours; a record whose total is fixed by the calendar cannot, and cannot claim an hour twice. Everything else here — the commitment format, the disclosure ladder, the countersignature — exists to make that bound checkable by somebody who was not there.

The record is a **commitment, not a publication**. One signed object per week carries a Merkle root over the week's hours and reveals nothing else, not even how many hours were allocated. Disclosure is a separate, later, deliberate act: the holder reveals specific hours to a specific handle, at a chosen level of detail, and the recipient verifies each revealed hour against the root already on chain.

Hours are **not transferable**, and this document is emphatic about it. An hour may name another handle as the party it was worked *for*, and that handle may countersign it, but no mechanism moves an hour from one person to another. Time that can be bought is not a record of a life; it is a currency, and section 2 of the Prior Art explains why that has been tried.

No new cryptographic primitive is introduced. The commitment is a [BRC-48](/scripts/0048) PushDrop output, the signatures are [BRC-3](/wallet/0003), disclosure keys are derived under [BRC-42](/key-derivation/0042) and encrypted under [BRC-2](/wallet/0002), countersignatures are [BRC-52](/peer-to-peer/0052) certificates, and delegated allocation is [BRC-169](/peer-to-peer/0169) section 9.

## Motivation

Time is the one input nobody has more of. It is also the input most often invoiced, most often disputed, and least often evidenced. A timesheet is a claim a person types after the fact into a system their counterparty controls, or their employer controls, or nobody controls. The dispute that follows has no arbiter but the relationship, which is why the honour system survives in professions that would never accept it for money.

Three properties are missing, and they are missing together.

**A total that cannot be inflated.** A week has 168 hours. Two clients who agree on the calendar agree on that number without trusting each other, so a record constrained to it carries a bound nobody had to be persuaded of. This is a smaller claim than "these hours were worked" and a far more robust one: over-claiming is not forbidden, it is unrepresentable, for the reason section 2.2 gives.

Be precise about who that binds, because the obvious version of the claim is wrong. It does not let one client detect that a freelancer billed three of them for the same hours: a client granted forty hours sees forty, and section 6.2 explains why a *total* is not provable to somebody who has not been shown the positions. What it does is bound each disclosure against every other disclosure from the same handle. Two clients each shown the placement of their own hours can compare, and the same hour cannot appear in both — one hour, one leaf, one subject. The bound is not "nobody can over-claim"; it is "the same hour cannot be sold twice, and a week cannot hold a 169th".

**A moment the claim was made.** The difference between a contemporaneous record and a reconstruction is the whole of its evidentiary value, and it is exactly what a timesheet cannot show. A row in a database has whatever timestamp the database was told to write. An output in a block has a height, and a claim committed nine days after the hour it describes is visibly a different kind of claim from one committed within the hour. This document does not forbid late allocation — a hard deadline only moves the lie earlier — it makes lateness a published property of every hour, and lets whoever is relying on the record decide what they will accept.

**Disclosure that is an act rather than a setting.** Time data is unusually revealing. When somebody works discloses their employment, their religion, their health, their childcare arrangements and their sleep, and it discloses these from the *shape* of a week rather than from any label attached to it. A system that stores this in a vendor's table and offers a sharing toggle has already lost: the vendor sees everything, the toggle governs only what the vendor chooses to honour, and nothing that has been shared can be recalled. Here nothing is legible by default, including to the ecosystem host; a disclosure names a recipient, a slice and a level of detail; and it is irreversible in the only honest sense, which is that the holder can stop granting and cannot un-grant.

### What this is not for

A document that argues only for its mechanism invites its own worst deployment, so this one says at the outset where it should not go.

It is not a productivity instrument, and it deliberately provides nothing to optimise. There are no streaks, no scores, no comparisons and no leaderboards, because a record of how somebody lived stops being true the moment it becomes a target — Goodhart's observation applies with unusual force to a measure the subject writes themselves. Section 7.8 makes this a requirement on clients rather than a hope.

It is not an employer's tool. The screenshot-and-keystroke category of workplace surveillance solves a different problem for a different party, and the mechanism here would serve it badly and serve it anyway if pointed at it. What limits that is not the protocol but the fact that disclosure originates with the subject; what does not limit it is anything, if the subject needs the job. Section 9 is blunt about coercion, because it is the primary threat and it is not a cryptographic one.

And it is not proof that any work was done. It proves a claim was made, when it was made, that it fits inside a week, and that it has not silently changed. Section 8 says so in those terms, and says what countersignature does and does not add.

## Prior Art

Five bodies of work bear on this. The design borrows from the first two, diverges sharply from the third, refuses the fourth outright, and inherits a warning from the fifth.

**Verifiable credentials and selective disclosure.** The W3C Verifiable Credentials Data Model, and the selective-disclosure schemes built over it, established the shape used here: commit once, reveal per-verifier, prove the revealed part against the commitment. Section 6's field commitments are the ordinary construction, not a novelty, and it is worth saying so rather than presenting a Merkle tree as an invention. What is specific to this document is the *slice*: a credential discloses attributes of a subject, while a time record discloses a subset of 168 positions, and the positions themselves are the sensitive part.

**Trusted timestamping.** The second of the three properties above — a moment the claim was made, attested by somebody other than the claimant — is the most established part of this design and the part most likely to be mistaken for new. RFC 3161 specified a Time-Stamp Protocol in 2001, in which an authority signs a hash together with a time; its weakness is the authority, which can be compelled, can backdate, and has to be trusted about the one fact it exists to establish. OpenTimestamps replaced the authority with a chain, anchoring a Merkle root in a Bitcoin transaction so that the timestamp is attested by proof of work rather than by a signature, and it is the direct ancestor of section 3.1.

What this document adds to that lineage is not the anchoring but what is anchored. A timestamp proves a document existed; a commitment here proves a *bounded* claim existed, and the bound is what a timestamp alone cannot supply. Section 3.4's lateness is the timestamp read the other way round — not "this existed by then" but "this was recorded this long after the fact" — which is the more useful direction when the thing being dated is a claim about the recent past.

**Time tracking as a product category.** Harvest, Toggl, Clockify and Tempo record hours for billing; RescueTime and its successors record them for self-knowledge; Hubstaff, Time Doctor and ActivTrak record them for employers, with screenshots and keystroke counts. The first group produces a claim in the user's own database, which the client is asked to trust. The third produces evidence the worker cannot audit, dispute or withhold. Neither produces something a third party can check without trusting the operator, and neither constrains the total: a timesheet application will accept a fiftieth hour on Tuesday without complaint, because it is recording what it was told rather than allocating from a budget.

The legal profession's six-minute billing increment is the sharpest illustration of where fine granularity leads. Dividing an hour into ten units did not make the record more truthful; it made the smallest claimable unit small enough that the act of recording became continuous, and it is now the standard example in the literature on how measurement deforms the work being measured. Section 2.3 keeps the hour indivisible for that reason and not for a technical one.

**Time as currency.** Time banking is the closest and most instructive relative. Edgar Cahn's Time Dollars, from 1980, and Ithaca HOURS, from 1991, both made an hour of somebody's labour into a transferable unit that could be earned, held and spent. The idea has a forty-year record and a real literature, and it is a genuinely different proposal from this one in a single respect that changes everything: there, an hour moves.

This document refuses transfer, and the refusal is load-bearing rather than conservative. An hour that can be transferred can be accumulated, so the 168-hour bound — the only property here that anybody has to trust arithmetic rather than a person for — dissolves immediately. It can be bought, so a record of how somebody spent their week becomes a record of what they could afford. And it can be demanded, which is a worse thing to be able to do to a person than to ask them for money. Time banking accepted all three consequences deliberately, in exchange for a medium of exchange denominated in something everyone has equally. That is a coherent trade and it is not this one. Section 5.1 forbids transfer at the level of the mechanism rather than by convention, because a rule of this importance that a client could quietly relax is not a rule.

Non-transferable tokens as a general construct were argued for in *Decentralized Society: Finding Web3's Soul* (Weyl, Ohlhaver and Buterin, 2022), and proof-of-attendance schemes such as POAP are the widely deployed instance. Both establish the pattern of a token that marks rather than moves. Neither addresses a budget: attendance tokens accumulate without bound, which is appropriate for attendance and fatal for time.

**The critique of clock-time itself.** E. P. Thompson's "Time, Work-Discipline, and Industrial Capitalism" (*Past & Present*, 1967) traced how the clock reorganised labour, replacing task-orientation with time-orientation and, with it, a particular kind of discipline. Shoshana Zuboff's *The Age of Surveillance Capitalism* (2019) describes the modern form, in which the record of a life becomes an asset held by somebody else. Neither is an argument against measuring time, and this document does not pretend they are. They are an argument that a mechanism for quantifying a life should be honest about what it enables, should default to revealing nothing, and should refuse the features that make quantification compulsory. Sections 7.8, 9 and the Security Considerations are what that argument looks like written as requirements.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119. They are used sparingly, and where interoperability or a privacy property actually breaks; the rest of this document describes a mechanism.

Throughout, "handle" and "resolve" have the meanings given in [BRC-169](/peer-to-peer/0169) sections 2.1 and 5.7.

### 1. Terminology

* **Hour**: one of the 168 positions in a week. The unit of allocation, and indivisible.
* **Week**: an ISO-8601 week, beginning Monday 00:00 UTC. Identified as `2026-W31`.
* **Allocation**: a claim that a given hour was spent in a given way. An hour is either allocated or **untracked**; untracked is the default and is not a category.
* **Budget**: the 168 hours of a week. Conserved, per section 2.2.
* **Commitment**: the signed, chain-anchored object binding a handle to a week's allocations without revealing them, per section 3.
* **Disclosure**: the act of revealing chosen fields of chosen hours to a chosen handle, per section 6.
* **Grant**: the signed object authorising and describing one disclosure.
* **Subject**: an optional handle an hour was worked *for*. Not a recipient of anything.
* **Countersignature**: a subject's signed agreement that an hour naming them is accurate, per section 5.3.

### 2. The Week and the Budget

#### 2.1 Weeks are UTC, and displayed locally

A week is an ISO-8601 week beginning Monday 00:00 UTC, and hour index 0 is the hour beginning at that instant. Hours run 0 to 167.

The record is in UTC and the interface is not. Two clients must agree on which hour an allocation refers to, and they have no shared local time — a user in Auckland and one in Lisbon disagree about which day it is for six hours out of every twenty-four, and a "week" anchored to either of their calendars is not a week the other can verify against. So the wire form fixes UTC, and a client renders it in whatever local frame its user lives in, exactly as it renders satoshis in a currency. A user who moves between zones does not acquire or lose hours; their grid shifts.

Pay periods, contract weeks and fiscal weeks do not align with this and are not meant to. They are a matter for whoever is reading the record, who can sum whatever slice they were granted.

#### 2.2 Conservation

Three bounds hold, and they are the reason this document exists:

1. A week's allocations total no more than 168 hours.
2. No hour is allocated more than once.
3. A calendar day's allocations total no more than 24 hours.

**None of the three is a rule a client enforces. All three are properties of the encoding.** The commitment of section 3.2 has exactly one leaf per hour and exactly 168 hour-leaves, so a 200-hour week has nowhere to put the extra thirty-two, hour 34 cannot carry two allocations because there is one leaf for it, and a calendar day is twenty-four leaves whatever is written into them. Over-allocation is not refused; it is unrepresentable.

That distinction is the document's central claim and it is easy to state as something weaker. A timesheet application accepts a fiftieth hour on Tuesday and could be programmed not to; the difference here is that there is no fiftieth hour to accept. A verifier therefore needs no bound-checking logic, and an implementer who goes looking for a rejection path has misread the construction.

What a verifier does check is the shape: that the tree carries 256 leaves and that a disclosed index is below 168. A root computed over a wider tree would admit a 169th hour, and that is the only way the bounds could be evaded.

**No other constraint is imposed.** Earlier drafts of this mechanism suggested a minimum for sleep, a maximum for a working day, and a warning for patterns a circadian model considered implausible. All of that is removed. A specification is not entitled to an opinion about how long its users should sleep, and a client that nags a shift worker, a new parent, an insomniac or somebody in the middle of a deadline has substituted a designer's model of a life for the life being recorded. The arithmetic is the whole of the validation.

#### 2.3 The hour is indivisible

An hour is allocated whole, to one category, with one optional label and one optional subject. There is no half-hour and no fractional allocation across categories.

This is a deliberate refusal of precision rather than a limitation to be lifted later. Finer units do not produce a truer record; they produce a record that costs more to keep and invites the continuous self-accounting the six-minute increment is famous for. A working lunch is one hour of something, and requiring the user to decide *which* thing is a smaller imposition than requiring them to decide that it was 0.6 of one and 0.4 of another. Where an hour genuinely divides, the honest allocation is the activity that dominated it, and where nothing dominated it, the honest allocation is `other`.

Untracked hours are not a failure state. A week with sixty hours allocated and a hundred and eight untracked is a complete and valid record of sixty hours.

### 3. The Commitment

#### 3.1 One object per week

A commitment binds a handle to a week's allocations without revealing them:

```json
{
  "protocol": "metanet-time",
  "version": 1,
  "handle": "@crumbs@nexus.app",
  "week": "2026-W31",
  "counter": 1,
  "root": "<32 bytes, hex>",
  "replaces": null,
  "signature": "<DER, hex>"
}
```

It is signed by the handle's identity key over the [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) canonical form of the object without `signature`, and published as a [BRC-48](/scripts/0048) PushDrop output so that it acquires a block height. The height is the commitment's timestamp and is not carried in the object, because a self-asserted time is the thing this mechanism exists to replace.

A client MAY submit the output to the overlay topic `tm_timealloc`, discoverable through `ls_timealloc`, so that a counterparty can find commitments for a handle without being sent them. As [BRC-87](/overlays/0087) establishes no registry, these names are claimed rather than reserved, and an implementer finding either serving something else should raise it against this document.

**The commitment reveals nothing about the week.** Not the categories, not the labels, not which hours were allocated, and not how many. Its existence discloses that the handle keeps time records at all, which is the one leak the format cannot avoid and which section 9 says to weigh before starting.

#### 3.2 The tree

The root commits to 256 leaves, being the 168 hours and 88 fillers, arranged as a binary Merkle tree of depth 8 with `SHA-256(left ‖ right)` at each internal node.

Each hour has four **field commitments**, so that a disclosure can reveal an hour's category without revealing its label:

```
salt(i, f) = HMAC-SHA-256( weekSecret, uint8(i) ‖ f )
c(i, f)    = SHA-256( salt(i, f) ‖ value(i, f) )
leaf(i)    = SHA-256( c(i,"cat") ‖ c(i,"sub") ‖ c(i,"lab") ‖ c(i,"note") )
```

where `f` is one of the ASCII strings `cat`, `sub`, `lab`, `note`; `value` is the field's UTF-8 bytes, or the empty string where the field is absent; and an untracked hour has all four values empty. Filler leaves, for `i` from 168 to 255, are `SHA-256( salt(i,"pad") ‖ uint8(i) )`.

`weekSecret` is derived under [BRC-42](/key-derivation/0042) from the handle's identity key with protocol ID `[2, "metanet time"]` and key ID the week identifier, so a client holds no salt store and can reconstruct any week from its key alone.

Three details are load-bearing.

**Salts are not optional.** The category vocabulary of section 4.1 has six members and the label space of a given user is small and guessable. An unsalted commitment to a category is recoverable by trying all eight, so an unsalted tree would publish the whole week to anybody who cared to grind it. Every field commitment MUST be salted, and an implementation that omits the salt has published what it believes it has hidden.

**Untracked hours are committed, not omitted.** An untracked hour has a leaf of the same shape as any other. If untracked hours were absent from the tree, the shape of the week would be legible from the tree's structure, and the shape is the sensitive part.

**Fillers exist so that the tree does not duplicate.** Padding a 168-leaf tree by repeating the last leaf admits the well-known second-preimage confusion between a leaf and an internal node; distinct salted fillers avoid it and cost nothing.

#### 3.3 Correction supersedes and does not erase

A later commitment for the same week and handle, carrying a higher `counter` and the digest of its predecessor in `replaces`, supersedes it. Verifiers MUST follow the chain and use the highest counter they can find, and MUST reject a commitment whose `replaces` they cannot resolve, since a break in the chain is how a correction hides what it corrected.

Corrections are expected and are not shameful: people misremember Tuesday. What is not available is silent correction. The prior commitment stays on chain, its height stays visible, and a recipient of a disclosure MUST be shown that an hour was amended, when, and — where they held a disclosure of the earlier version — what it said before.

This answers the question of whether records should be immutable with a third option. Immutable records force people to choose between an inaccurate record and no record. Editable records prove nothing. A superseding chain with visible history is accurate *and* evidential, and it puts the cost of frequent revision where it belongs, on the credibility of the person revising.

#### 3.4 Lateness is a property of every hour

An hour's **lateness** is the difference between the height at which its commitment was confirmed and the height at the end of the hour it describes. It is computed by the verifier from facts already on chain, is not asserted by anybody, and cannot be forged downward.

There is **no allocation deadline**. A user may commit a week years afterwards, and the record will say so. This is a deliberate reversal of the obvious design: a two-week window would let a reconstruction inside the window pass as contemporaneous, and would make an honest late record impossible, so it would suppress exactly the records that admit what they are. Publishing lateness instead moves the judgement to the party relying on it, which is where it belongs — a client paying an invoice may insist on hours logged within a day, a grant body reviewing six months of volunteering may not care, and neither has to argue with a rule set by this document.

A client MUST display lateness wherever it displays an allocation to a third party, since a verifier who cannot see it is relying on a record whose most load-bearing property has been hidden. A client SHOULD also show a user the lateness their allocation will carry *before* they commit it: discovering afterwards that a week's work is stamped "logged 11 days late" is a surprise the interface could have prevented.

### 4. Categories, Labels and Subjects

#### 4.1 Categories are a closed set

An allocated hour carries exactly one category, from:

`work` · `care` · `learning` · `sleep` · `health` · `personal`

The set is closed and normative. An open vocabulary would make two users' records incomparable, which costs the mechanism most of its value to a grant body, an auditor or anybody aggregating across people; and a user-defined vocabulary is what the label of section 4.2 is for.

Six is fewer than the first draft of this section carried, and the cuts matter more than the survivors.

**`care` is first-class**, covering care of children, of the elderly, of the sick and of a household. The most common objection to quantified time is that it renders visible only the work that was already counted, and a vocabulary with no word for unpaid care would make that objection correct by construction.

**`sleep` is its own category, and is not folded into rest.** It is around a third of the budget, it is involuntary, and it is not substitutable for anything. Merging it with discretionary rest destroys the informativeness of both, because a combined total of sixty cannot be read — fifty-six and four is a different life from fifty and ten, and the same number. Nor does merging buy any privacy: section 6.2 establishes that position is disclosed by any disclosure at all, so anybody shown a week's placements sees the nightly block whatever it is labelled. The category is a noun and not a target; section 2.2 permits no bound but arithmetic, and nothing here recommends an amount.

**There is no `volunteer` and no `civic`.** Both name a *relationship to a beneficiary* rather than a kind of activity, and that relationship is the subject field of section 4.3. Three hours at a food bank is `work` with subject `@foodbank`, which is also the form a grant body can actually use, since it identifies the organisation rather than the sentiment. Keeping `civic` while refusing `volunteer` would not have survived its own reasoning.

**There is one residual and not two.** `personal` is discretionary time — leisure, meals, company, doing nothing — and there is no `other` beside it, because two residuals are not reliably distinguishable and a closed vocabulary whose members users cannot tell apart produces comparability that is not real. Where nothing fits, the honest answer is **untracked**, which is candid about carrying no information where `other` would pretend to carry some.

`health` is the holder's own — treatment, recovery, exercise — as distinct from `care`, which is somebody else's.

#### 4.2 Labels are the user's own

An hour MAY carry a label: free UTF-8, up to 64 bytes, chosen by the user. `Project Alpha`, `the Tuesday deposition`, `Mum`. Labels are never normative, never compared across users, and never interpreted by a client beyond grouping a user's own hours.

The separation matters at disclosure. A category is coarse enough to be worth sharing widely; a label is often the single most sensitive thing in the record, and section 6 makes them independently disclosable for that reason.

#### 4.3 Subjects name who an hour was for

An hour MAY name a **subject**: a fully-qualified handle, per BRC-169 section 2.4. This is the beneficiary or counterparty — the client the work was for, the organisation volunteered at, the person cared for.

Naming a subject transfers nothing and grants nothing. It does not disclose the hour to the subject, does not oblige them, and does not appear anywhere they can see until the holder discloses it to them. A subject who has been disclosed to may countersign, per section 5.3.

Because a subject is a handle, BRC-169 section 2.3's confusability rules apply exactly as they do to a payee: a homoglyph in a subject field misattributes a life's work to a stranger, and a client MUST resolve and display subjects in fully-qualified form.

### 5. Allocation To and For Other Handles

This section answers the question the mechanism most often prompts, which is whether hours can be given away. They cannot, and three adjacent things that can be done are frequently mistaken for it.

#### 5.1 Hours are not transferable

No mechanism in this document moves an hour between handles, and a client MUST NOT provide one. There is no send, no gift, no pooling, no team account that hours accrue to, and no market.

The reasons compound. **Conservation dies first**: an hour that can be received is an hour that can be accumulated, and a handle holding four hundred hours in a week has destroyed the only property here that does not rest on trusting somebody. **The record stops being about a life**: a transferable hour measures purchasing power, and a wealthy handle would show a fuller week than a hard-working one. **And it creates a thing that can be demanded**: an employer who can ask for your money can be refused more easily than one who can ask for your hours, because the hours are visibly there and visibly yours. The refusal is at the level of the mechanism rather than of convention because a rule this important that a client could quietly relax is not a rule.

An organisation wanting a team's total does not pool hours. It collects disclosures from each member and adds them up, which produces the same figure and keeps every hour attributable to the person who lived it.

#### 5.2 Directed allocation is not transfer

An hour that names a subject (section 4.3) remains entirely the holder's. `work`, labelled `Project Alpha`, subject `@client@lkup.net`, is the ordinary billable hour: it counts against the holder's 168, appears in the holder's record, and is disclosed at the holder's discretion. The subject's own week is untouched.

This is the primitive the freelance and volunteer cases actually need, and conflating it with transfer is what makes people ask for transfer.

#### 5.3 Countersignature is what turns a claim into evidence

A subject who has been disclosed an hour naming them MAY **countersign** it: a [BRC-52](/peer-to-peer/0052) certificate, with the subject as certifier, over the hour's leaf digest and the commitment root it belongs to.

This is the mechanism's answer to its own central weakness. Nothing in sections 2 or 3 establishes that any work was done; they establish that a bounded, timed, tamper-evident claim was made. A countersignature adds a second party's signed agreement, which is a different and much stronger object — and a client MUST render a countersigned hour distinguishably from an uncountersigned one, because presenting them alike lets the weaker claim borrow the credibility of the stronger.

Three limits, stated because a countersignature invites over-reading. It attests agreement, not occurrence: a counterparty can be mistaken or complicit. It is voluntary, and its absence means nothing — many honest hours will never be countersigned, and a client MUST NOT present a missing countersignature as doubt. And it is a public claim by its signer, subject to BRC-169 section 10's attribution rules, which is what makes it worth anything and also what makes signing one a considered act.

A countersignature also **discloses the relationship**. A certificate by `@client` over an hour of `@alice`'s says that the two deal with each other, to anybody who sees it, which is more than either party disclosed by the allocation itself. Where that matters the countersignature should be delivered to the holder and not published, in which case it is evidence the holder can produce on request rather than a public fact — weaker as a signal, and the same strength where it is actually used.

#### 5.4 Delegation is BRC-169 section 9

An assistant, bookkeeper or agent MAY allocate on a holder's behalf under a delegation certificate, within its scope and expiry. Delegated allocation counts against the holder's own budget, is attributable to both parties, and MUST be marked as delegated wherever the hour is disclosed. A delegate cannot commit a week the holder could not commit, and cannot countersign on the holder's behalf as a subject.

#### 5.5 A request is a proposal, never an instruction

A counterparty MAY ask a holder to allocate an hour, correct one, or countersign one. Such a request is a message. A client MUST NOT execute it, MUST NOT pre-fill a commitment from it without the user acting, and MUST render it as the counterparty's text, per BRC-218 section 2.4. A mechanism whose records could be written by whoever asks loudest would be a worse timesheet than the one it replaces.

### 6. Disclosure

#### 6.1 Nothing is legible by default

A commitment discloses no allocation to anybody: not to the public, not to the subject of an hour, not to the ecosystem host, and not to the overlay that indexes it. Every disclosure is a deliberate, signed, recorded act by the holder.

#### 6.2 A disclosure is a slice and a field mask

Two independent choices decide what a recipient learns.

The **slice** selects hours: a set of weeks, optionally narrowed to given categories, labels or subjects. The **field mask** selects what is revealed about each hour in the slice, from:

| Field    | Reveals                                      | Notes                                  |
| -------- | -------------------------------------------- | -------------------------------------- |
| *(none)* | that the hour is allocated, and its position | implied by any disclosure of that hour |
| `cat`    | the category                                 |                                        |
| `sub`    | the subject handle                           |                                        |
| `lab`    | the label                                    | usually the most sensitive field       |
| `note`   | free metadata                                |                                        |

**Position is disclosed by any disclosure at all**, and this is the property implementers most often miss. Revealing that hour 34 is allocated reveals that the holder was working at 10:00 on Tuesday, which over a few weeks establishes a schedule, and a schedule discloses employment, observance, caring responsibilities and health without a single label being shared. A client MUST state this at the point of granting, in those terms. "Category only" sounds narrow and is not.

Consequently the useful aggregate — *how many* hours, without *which* — is not provable by this construction. A holder can assert a total, and a recipient can verify a total only by receiving the underlying positions. Section 10 records zero-knowledge aggregation as the obvious extension and does not specify it; an asserted total MUST be labelled as asserted, and MUST NOT be rendered in the visual language of a verified one.

#### 6.3 The grant

```json
{
  "protocol": "metanet-time-grant",
  "version": 1,
  "grantor": "@crumbs@nexus.app",
  "grantee": "@client@lkup.net",
  "weeks": ["2026-W31", "2026-W32"],
  "categories": ["work"],
  "labels": ["Project Alpha"],
  "fields": ["cat", "sub", "lab"],
  "expires": 934560,
  "signature": "<DER, hex>"
}
```

The grant is signed over its RFC 8785 canonical form and delivered to the grantee's messagebox (BRC-169 section 7). It travels with an encrypted payload carrying, for each hour in the slice, its index, the revealed field values and their salts, the withheld fields' commitments, and the Merkle path to the root.

The grantee verifies without asking anybody: recompute each revealed field commitment from value and salt, combine with the withheld commitments to rebuild the leaf, walk the path to the root, and compare against the root in the on-chain commitment for that handle and week. Nothing in this path trusts the grantor, a server, or an overlay. The grantor cannot show a recipient an hour that is not in the tree they committed before the conversation began.

`expires` is a block height after which the grantee's client stops presenting the disclosure as current. It bounds staleness, not access, for the reason section 6.5 gives.

#### 6.4 Standing disclosure

A grant MAY cover future weeks, in which case the grantor's client delivers each week's payload as that week is committed. This is what an ongoing client relationship needs, and it is materially more dangerous than a one-off: it is easy to grant, easy to forget, and the thing it discloses grows every week without anybody deciding again.

A standing grant MUST therefore appear in the holder's standing-authority list (BRC-218 section 5.19), with its slice, its fields and its expiry, and a client SHOULD require it to carry an expiry rather than defaulting to none.

#### 6.5 Revocation is honest about what it cannot do

A holder MAY revoke a grant. Revocation stops future weeks under a standing grant and marks the disclosure as withdrawn in the grantee's client, and it does not and cannot retrieve what was already disclosed. The grantee holds salts and values; they can be copied, and no protocol reaches them.

A client MUST say this plainly at the moment a grant is made — not in a policy, not on revocation, but where the user is deciding. The failure mode this prevents is specific and common: a person shares broadly on the understanding that they can pull it back, discovers they cannot at the moment they most want to, and the interface told them the truth only afterwards.

### 7. The Interface

This document usually leaves display to clients. It does not here, because a time-tracking interface that is tedious will not be used, and one that is unclear about disclosure will hurt somebody.

Most of what follows is a SHOULD, and deliberately. A specification earns the right to mandate an interface only where getting it wrong breaks interoperability or causes a harm that cannot be undone, and most of this section is neither — it is what a client that wants to be used will do anyway. Two are requirements: **7.5**, because over-disclosure cannot be reversed and the preview is what prevents it, and **7.8**, because a scored record stops being evidence and the incentive to game it falls on the person the record is about.

#### 7.1 The week is a grid, not 168 objects

A client SHOULD NOT require a user to act on hours one at a time. The natural surface is the week as a 24-by-7 grid, and allocation is a gesture across it: select a span, choose a category, done. Filling in a working week should be a handful of actions and not forty.

An hour is small enough that this matters. Any interface that costs more than a few seconds per day will be abandoned inside a fortnight, and an abandoned record is worse than none, because a partial week looks like a light week.

#### 7.2 Patterns first, corrections after

Most weeks resemble the last one. A client SHOULD let a user define recurring patterns — the working day, the standing commitment, the usual night — and apply them to a week in one action, leaving the user to correct the exceptions. This inverts the labour: the user describes their life once and then edits the deviations, rather than re-describing it weekly.

A pattern is a client-side convenience. It commits nothing until the user commits the week, and a client SHOULD NOT auto-commit a patterned week without the user seeing it, or the record becomes a description of an intention.

#### 7.3 The budget is always visible, and untracked is neutral

Allocated, untracked and remaining hours are shown whenever a week is edited. Conservation is the point of the mechanism and it should be legible rather than discovered at the moment a commit is refused.

Untracked hours SHOULD NOT be rendered as a deficit, a gap, an incompletion or anything else that reads as a reproach. No progress bar toward 168, no "you have 43 hours unaccounted for". Most people will track one part of their life and not the rest, and that is a correct use of this document rather than a partial one.

#### 7.4 Lateness is shown before committing

Per section 3.4, and stated here as an interface requirement because it is where it takes effect: the user sees what lateness their allocation will carry before they commit it, and sees it in the terms a verifier will — "this will be recorded as logged 6 days after the fact" — rather than as a timestamp they must subtract.

#### 7.5 Disclosure is previewed as the recipient

**A client MUST be able to render a holder's record as a named grantee will see it**, before the grant is signed, and MUST offer that preview as part of granting rather than as a setting elsewhere.

This is the single most important requirement in this section. Access-control lists are notoriously hard to reason about forwards: people can answer "what will my client see?" and cannot reliably answer "which categories has my client got?" — and the second question is what every permissions screen asks. Rendering the record from the recipient's side converts a rule into a page, and the user checks it the way they would check a letter before sending it.

The preview MUST show what is *not* included as well as what is, since the risk being guarded against is over-disclosure and an omission is invisible in a page that shows only what is present.

#### 7.6 A grant is an act, not a toggle

Granting passes a structured confirmation naming the grantee in fully-qualified form, the slice, the fields, the expiry, and the irreversibility of section 6.5. It is recorded, it appears in the standing list of section 6.4, and it can be found later. A permissions switch that silently begins disclosing a category to a handle is not adequate, whatever it is labelled.

#### 7.7 The recipient sees the shape of what they were given

A grantee's view SHOULD show the slice and fields they hold, who granted them, when, the expiry, and — for each hour — its lateness and whether it has been amended or countersigned. It SHOULD also show what the grant does not cover.

The symmetry with 7.5 is the point. Both parties should be able to see the same boundary from their own side, because most disputes about a disclosure are disagreements about its edges rather than its contents.

#### 7.8 No scores

A client MUST NOT present streaks, completion percentages, productivity scores, comparisons against other users, or leaderboards, and MUST NOT rank categories against one another.

This is a requirement rather than advice because the mechanism is unusually vulnerable to it. The subject writes the record, so any score the client offers is a score the user can raise by writing differently, and the cheapest way to raise every such score is to make the record less true. A measure that becomes a target stops being a measure; here it also stops being evidence, which is the only thing the record was for.

#### 7.9 Correcting is normal

Amendment (section 3.3) SHOULD be as easy as allocation and SHOULD NOT be presented as an exception or an admission. A client that makes correction feel like a confession produces records people leave wrong.

### 8. What This Does and Does Not Prove

A verified record establishes four things: a specific handle committed to a specific week's allocations; the commitment was confirmed at a specific height, from which lateness follows; the allocations fit within 168 hours and no hour is claimed twice; and the disclosed hours are the ones committed, unchanged, or changed with the change visible.

**It bounds a handle's week, not a person's.** Conservation is per handle, and BRC-169 handles are assigned by an ecosystem rather than issued against a scarce resource, so somebody willing to hold three handles has three budgets. Nothing here detects that, and nothing here could: the mechanism has no notion of a person, only of a key that signs. What survives is narrower and still useful — a counterparty paying `@alice` verifies against `@alice`, and no arrangement of other handles adds an hour to that week or lets the same hour be sold to two of her clients. A relying party who needs the stronger claim needs an identity assurance this document does not provide, and section 5.3's countersignature is the nearest thing to it, since a counterparty who agrees to an hour is agreeing about a specific handle they chose to deal with.

It establishes nothing about the world. It does not show the activity happened, that it took an hour, that it was done competently, or that it was done at all. A person can allocate forty hours of `work` to a project they never opened, and this document will faithfully record that they claimed so, promptly, within budget.

That is a narrower claim than the category of product this replaces usually makes, and it is worth being precise about why it is still worth having. A timesheet is unbounded, undated in any way its recipient can check, and silently editable. A record here is bounded by arithmetic the recipient can do, dated by a chain the recipient can read, and editable only in public. The dishonest user has been moved from "can claim anything" to "can claim a false thing, within a fixed budget, at a visible time, permanently" — which does not stop them, and does raise the cost and the traceability of every lie considerably.

Countersignature (section 5.3) is the only mechanism that adds a second party's word, and it adds exactly that.

### 9. When Not to Use This

**Where the reader can compel the writer.** This is the primary hazard and it is not cryptographic. An employer who can make disclosure a condition of employment has converted a voluntary record into surveillance with extra steps, and every privacy control here becomes theatre because the coerced party will grant whatever is demanded. Nothing in a protocol prevents this. What partially mitigates it is that untracked is the default and a partial record is valid, so a person can hold a record of one part of their life without implying anything about the rest — a property a client destroys the moment it renders untracked hours as missing, which is why section 7.3 discourages it.

That mitigation is thinner than it first appears, and the limit is worth stating rather than leaving to be discovered. A commitment is an on-chain object, so **which weeks a handle committed is public** even though their contents are not. A reader who can compel disclosure can therefore see the gaps and demand them, and the answer "I did not record that week" is checkable against the chain. Untracked hours are private; uncommitted weeks are not.

**Where the work is not hourly.** Creative, research and management work is badly described by hours, and a record of it will be either false or unflattering. The measure will not become more accurate by being cryptographic.

**Where the record will be read as a character reference.** Time allocation is a description of circumstances at least as much as of choices. Illness, caring, disability, poverty and precarity all show up as a differently shaped week, and a reader treating shape as merit will misread all of them. A client SHOULD NOT provide affordances that invite that reading, and section 7.8 removes the most obvious ones.

**Where a summary would do.** If the counterparty would accept "about forty hours", the strongest version of this mechanism buys nothing and discloses a schedule.

The mechanism is worth its cost where a specific bounded claim is contested by a specific party who can check it: an invoice, a grant report, an hours-based contract. It is a poor fit for everything shaped like general accountability.

### 10. Not Specified Here

**Zero-knowledge aggregates.** Proving "at least forty hours of `work` in this week" without revealing which hours is the extension that would most improve section 6.2, since it separates the useful figure from the sensitive one. The commitment structure does not preclude it. It is unspecified because it needs a proving system, a circuit and a verification cost this document is not in a position to fix.

**Sub-hour granularity.** Refused rather than deferred, per section 2.3.

**Estate and inheritance.** What becomes of commitments and outstanding grants when a holder dies is unaddressed. The commitments persist because the chain does; the salts do not, so an undisclosed record becomes permanently unreadable, which is a reasonable default and not a considered one.

**Tokenomic incentives.** Deliberately absent. Paying people to keep accurate records pays them to keep records that look accurate, and section 7.8's argument applies with money attached.

**Portability to other chains.** Out of scope.

### 11. Relationship to Other Documents

Handles, resolution, messagebox delivery, attestation and delegation are [BRC-169](/peer-to-peer/0169); this document defines no addressing and no new certificate type beyond the countersignature of section 5.3, which is an ordinary BRC-52 certificate. The standing-authority list of section 6.4 is [BRC-218](/apps/0218) section 5.19, and a conversational surface for allocating or granting is a matter for that document, as a custom verb under its section 8; no verb is defined or reserved here. A room may gate on contributed time under [BRC-146](/apps/0146) by treating a disclosure as the fact a vouch gate reads, and this document neither requires nor endorses that.

## Security Considerations

**Unsalted commitments publish everything.** The category vocabulary is six members; a label vocabulary is small and personal. Salting per field per hour, as section 3.2 requires, is what keeps a published root from being a puzzle solvable in seconds, and it is the single implementation error most likely to be made.

**Position is the disclosure.** Sharing "category only" for a month of hours shares a schedule, and a schedule is among the most identifying and most sensitive things a person has. Section 6.2 requires clients to say so at the point of granting; an implementation that describes a category-level grant as minimal has misled its user about the main risk.

**Existence leaks participation.** A commitment on chain says that this handle keeps time records, every week it does so, and at roughly what time it commits them. For most users this is uninteresting; for somebody whose employment or immigration status turns on it, it may not be.

**Disclosure is irreversible and revocation is not a remedy.** Section 6.5 is a statement about reality, not a feature, and a client that implies otherwise has caused the harm it appears to prevent.

**Multiple handles multiply the budget.** The 168-hour bound holds per handle and the cost of a second handle is whatever an ecosystem charges for one, which is usually nothing. An aggregate assembled across handles is unbounded, so a relying party summing disclosures from several handles believed to be one person is relying on that belief and not on this document. Section 8 states the limit; there is no mitigation in the protocol.

**Coercion defeats every control here.** The threat model of a person who can be fired, evicted or deported for withholding a disclosure is not addressed by consent-based access control, because their consent is available on demand. This document limits the blast radius — partial records are valid, untracked is the default, grants are per-slice — and does not solve it.

**Countersignature is a public claim by the signer.** Somebody who countersigns an hour has attested to another person's account of their own week, attributably. Signing them casually is how a signature stops meaning anything.

**A delegate writes the principal's record.** Under section 5.4 a compromised or careless delegate can allocate anything within scope, and the record will attribute it to the holder as well as to the delegate. Scope and expiry are the only limits and should be narrow.

**Aggregation across grantees.** Two recipients holding different slices can combine them, and neither needs the holder's consent to do so. A holder who discloses `work` positions to an employer and `care` positions to a benefits assessor should assume a party seeing both learns more than either was granted.

**Superseding chains can be broken deliberately.** A holder who wishes to obscure an amendment can publish a correction whose `replaces` points at a commitment they never publish. Section 3.3 requires verifiers to reject a chain they cannot resolve, which converts the attack into a visible refusal rather than a silent substitution.

## Implementations

None. This document is a specification ahead of its implementation, and says so rather than describing a reference client that does not exist. Section 7 is therefore its least evidenced part — nine subsections of interface guidance with no client behind them, which is why almost all of it is a SHOULD rather than a requirement.

What *is* verified is the construction. `example.py` computes every value in the Test Vectors from the rules in sections 3.1 and 3.2, and `verify_vectors.py` parses them back out of this document and checks them: thirty checks, including that a single-field disclosure walks to the published root, that substituting the disclosed value does not, and that the commitment signature verifies against the canonical form and fails against a modified one.

A minimal useful implementation is smaller than the document suggests: sections 2, 3 and 4 produce a committed, conserved, timed record, and section 6 with the `cat` field alone produces a disclosure a counterparty can verify. Sections 5.3, 6.4 and 7.2 are what make it pleasant to live with. Section 7.5's recipient preview is the one interface requirement that should not be deferred, because the harm it prevents is the harm that cannot be undone.

## Test Vectors

Every value below is computed from the rules in sections 3.1 and 3.2 by `example.py`, and `verify_vectors.py` parses them back out of this document and checks them. An implementation producing a different root from the same week and `weekSecret` has a construction bug, and its disclosures will not verify against anybody else's commitment.

### The example week

`@crumbs@nexus.app` records `2026-W31`. Four hours are allocated and the remaining 164 are untracked:

| Hour | Category | Subject            | Label           |
| ---- | -------- | ------------------ | --------------- |
| 34   | `work`   | `@client@lkup.net` | `Project Alpha` |
| 35   | `work`   | `@client@lkup.net` | `Project Alpha` |
| 36   | `work`   | `@client@lkup.net` | `Project Alpha` |
| 80   | `care`   | —                  | `Mum`           |

The identity private key is `SHA-256("BRC-168 EXAMPLE / crumbs identity")` reduced mod `n`, so an implementer can regenerate it:

| Value                |                                                                      |
| -------------------- | -------------------------------------------------------------------- |
| identity private key | `abfda371f34c70099ef1547318cee579b6f0ab3ff100e62a66e42b52d63c3f7d`   |
| identity public key  | `029cc83f8b3481391411598d845d605bede3db2d5900a66d33839430e7efa99642` |
| `weekSecret`         | `e9be52d2a862b8c6dec0d1504675540d50303e5a885a41038453243c76297a4d`   |

Section 3.2 derives `weekSecret` under BRC-42 from the identity key. This vector **fixes** it to the value above, so that what is being checked is the commitment construction rather than the key derivation — a reader wanting the derivation checked has BRC-169's Appendix A for that.

### Field commitments and a leaf

For hour 34, with `salt(i, f) = HMAC-SHA-256(weekSecret, uint8(i) ‖ f)` and `c(i, f) = SHA-256(salt(i, f) ‖ value)`:

| Field  | Value              | `salt(34, f)`                                                      | `c(34, f)`                                                         |
| ------ | ------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| `cat`  | `work`             | `fc9c49f3ddc03b620a83c925ad87a31f83bfb7aaf675a6b55f1af55b89b1c952` | `710bf9af2955edd4253d70c1175f2f2ea3c263e2cf8f5eeb7149191663b9966a` |
| `sub`  | `@client@lkup.net` | `aaa26eca343fc25f464a889785c8968fa191af90f1b0c9c4e5ea6f5fd7d2f054` | `24e06626f60ab74db6670d62c8343c91892059acc1968bce2af535f59b025029` |
| `lab`  | `Project Alpha`    | `9ae35c2b573555b082527dc6f04a46561ec9eb273bd0616ced34151436013cd2` | `e0b2b709e0ac8131e3bc3d433a846c42a040b11f11882e9db33d7b992bd45c1b` |
| `note` | *(empty)*          | `2e17fcee9fef419415befb606bde37e5c5fcf099ca7417f45bfa10d4785d30d7` | `986a51909d6c007b1ac819e314fef6cb4ae8e8b60a0f472e18b7ec04e709af32` |

The leaf is `SHA-256` over the four commitments in the fixed order `cat`, `sub`, `lab`, `note`:

```
leaf(34) = a3373138d80ef7373447a0baa9c85d3efda7b0c44542debc75074b9c213b6ff6
```

An **untracked** hour commits to four empty values and is the same shape, which is what keeps the week's shape hidden — nothing about `leaf(0)` says it is empty:

```
leaf(0)   = f6b6638b2364470863039dc26bf8a09f548274d186b23ed88deff0b98b29d62a
leaf(168) = bbfa6d97bf45341cf4c534734c94e1ec296e0b1a8e6e5b1f3f38bdd993a1fec5      (filler, per 3.2)
```

### The root

256 leaves, combined pairwise as `SHA-256(left ‖ right)` and halved until one node remains, giving a tree of depth 8:

```
root = 61c9aa87b889634c06c691db9565a6231f98426fc2b9eef5bf62ca2d6444f6e3
```

### A disclosure, verified

A grant covering hour 34 with `fields: ["cat"]` conveys the revealed value and its salt, the three withheld commitments, and eight sibling hashes:

```
value             work
salt(34,"cat")    fc9c49f3ddc03b620a83c925ad87a31f83bfb7aaf675a6b55f1af55b89b1c952
c(34,"sub")       24e06626f60ab74db6670d62c8343c91892059acc1968bce2af535f59b025029
c(34,"lab")       e0b2b709e0ac8131e3bc3d433a846c42a040b11f11882e9db33d7b992bd45c1b
c(34,"note")      986a51909d6c007b1ac819e314fef6cb4ae8e8b60a0f472e18b7ec04e709af32
sibling 0         345709bbfea57c9f7257c350b03d190f2c443c9780c50a7785ede177476e367f
sibling 1         fc5f89882419f20d9f0f3d7687a44830da69256c8c829dd1146b6d3e172ae9ba
sibling 2         c651578f7bf01137d344ec8c69260c065ebabf55eb0aef771e1743b4d926351f
sibling 3         37ca04fd62686e120c1f188c1fafab4e3977b2d6e6870adec4ec9c639a472cc8
sibling 4         ed35f08d7f13ea5b21d894532a4c1c0d7141dcc0801fcca23821e4083bb3dda2
sibling 5         651c043ac1466eb5e4a0366614c4749d9f2a2b200507f99cb241ab21948e43b0
sibling 6         b1e28243a20336cd326651036b331d2dfd60ab1189aa10954ca1579b735f9722
sibling 7         709f4d320a6d42fb1b75801462117ee4c0f3993b221a58f39542b1a801cec6ed
```

The grantee recomputes `c(34,"cat")` from the value and salt, rebuilds `leaf(34)` from the four commitments, then walks the siblings — combining as `SHA-256(node ‖ sibling)` where the running index is even and `SHA-256(sibling ‖ node)` where it is odd, halving the index at each level — and arrives at the root above. Nothing in that path trusts the grantor.

Note what the grantee learns without being told: **index 34 is Tuesday 10:00 UTC**. That is section 6.2's warning in its concrete form, and no choice of `fields` avoids it.

### The commitment object

```json
{
  "protocol": "metanet-time",
  "version": 1,
  "handle": "@crumbs@nexus.app",
  "week": "2026-W31",
  "counter": 1,
  "root": "61c9aa87b889634c06c691db9565a6231f98426fc2b9eef5bf62ca2d6444f6e3",
  "replaces": null
}
```

RFC 8785 canonical form, 188 bytes:

```
{"counter":1,"handle":"@crumbs@nexus.app","protocol":"metanet-time","replaces":null,"root":"61c9aa87b889634c06c691db9565a6231f98426fc2b9eef5bf62ca2d6444f6e3","version":1,"week":"2026-W31"}
```

Its digest, which is what a superseding commitment carries in `replaces` per section 3.3:

```
711b44f9167df07893cf4527c915e702e4ede695c5fcf83c0359f35124cc026f
```

And the signature over the canonical form by the identity key above:

```
3044022052b42b2196bafef93f46c0e760dbc2a705767bab362ff855963c2c6c3442ddfe022030335ee9937e71c4e64a923c7e94f537482aefbb9ef8ccc86f37451280a2c823
```

### Conservation is structural, so there is nothing to reject

Section 2.2's bounds cannot be violated by a well-formed commitment, and the vector for them is the absence of one. There are exactly 168 hour-leaves; a 200-hour week has nowhere to put the extra 32. Hour 34 has one leaf, so it cannot carry two allocations. A calendar day is 24 leaves. An implementation looking for a rejection path here has misread the construction — the bound is a property of the tree's shape, not a check performed on it.

What a verifier does check is that the tree has 256 leaves and the claimed index is below 168. A commitment whose root was computed over a larger tree would admit a 169th hour, and that is the only way the bound could be evaded.

## Conclusion

The useful thing here is not the record. It is the bound.

A week has 168 hours whether or not anybody is counting, and a claim about how those hours were spent is the rare kind of claim whose upper limit two strangers already agree on. Everything in this document is an attempt to make that agreement usable: to fix the claim at a moment a chain can attest to, to keep it unreadable until its author chooses otherwise, to let the choosing be per-recipient and per-field, and to leave a second party a way to say they agree.

What it deliberately does not do is more important than usual. It does not make time transferable, because a purchasable hour measures a wallet rather than a life. It does not enforce a shape on anybody's week, because a specification has no standing to have an opinion about sleep. It does not score, rank or congratulate, because a record its subject writes will bend toward whatever it is scored on. And it does not claim to prove that work was done, because it cannot, and a mechanism that overstates its evidence is worse than the honour system it replaces — the honour system, at least, is understood by everyone using it to be exactly as good as the person on the other side.

What remains is modest and, for a small number of real disputes, sufficient: a bounded, timed, tamper-evident, selectively disclosed account of where somebody says their hours went.

## References

* [BRC-2: Encryption and Decryption](/wallet/0002)
* [BRC-3: Digital Signature Creation and Verification](/wallet/0003)
* [BRC-42: BSV Key Derivation Scheme](/key-derivation/0042)
* [BRC-48: Pay to Push Drop](/scripts/0048)
* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](/overlays/0087)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [BRC-146: Access Gates for Metanet Rooms](/apps/0146)
* [BRC-218: Chat-Native Command Grammar for the Metanet](/apps/0218)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785)
* [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model-2.0/)
* E. Cahn, *Time Dollars* (1992); Ithaca HOURS (1991) — time as a transferable unit
* E. Weyl, P. Ohlhaver and V. Buterin, "Decentralized Society: Finding Web3's Soul" (2022)
* E. P. Thompson, "Time, Work-Discipline, and Industrial Capitalism", *Past & Present* 38 (1967)
* S. Zuboff, *The Age of Surveillance Capitalism* (2019)
* C. Goodhart (1975), as commonly stated by M. Strathern (1997): "When a measure becomes a target, it ceases to be a good measure"


# Derived Collectibles

Crumbs, root, Kuro

## Abstract

This document specifies **scores**: inscribed, immutable rules from which a collectible's appearance is **derived** rather than stored, so that the same object is a different object at a different block height and in different hands.

Derivation is the chain's own primitive and this document adds nothing to it. A public key is derived from a private key, an address from a public key, a transaction identifier from a transaction, a root from a set of leaves, a block from a search. Every one of those is cheap forward and infeasible backward, and every one is verified the same way: not by inspecting a record but by recomputing the derivation and comparing. A work under this document is verified in exactly that manner. Its state is not asserted by anybody; it is recomputed from the score, the height, and the work's own lineage, and two parties who disagree recompute until they find where they diverged.

Two families of input are defined. The **clock** is the chain read as a metronome: height and the intervals it divides into. The **record** is the chain read as an accretion: the mint, every transfer since, who held it, for how long, how often it moved, and what its holders chose to add. A score maps both onto layers, palettes and positions.

The document's governing claim is that **derivation yields approximation and never proof**. The chain settles the past probabilistically, an indexer witnesses rather than establishes, a date inferred from a height is an estimate, and a price is mostly invisible. Section 3 makes that a discipline rather than a disclaimer: every derived quantity carries a stated confidence class, a client renders the class alongside the value, and a work may render its own uncertainty, so that a freshly transferred piece is visibly not yet settled and resolves as its transfer buries.

No new token, transfer, or custody mechanics are introduced. Objects are held and moved by the 1Sat Ordinals stack as profiled in [BRC-147](/tokens/0147), their lineage is followed per [BRC-150](/tokens/0150) and [BRC-156](https://github.com/bsv-blockchain/BRCs/tree/master/tokens/0156.md), and where a holder is named rather than keyed it is a [BRC-169](/peer-to-peer/0169) handle. Score and mark type identifiers are derived from strings printed in section 2.7, in the registry-free manner of BRC-169 section 4.5, because there is no allocator and there is not going to be one.

## Introduction

Bitcoin is a metronome and a katamari.

A metronome is the small device a musician sets going before they play, and all it does is emit a bare, countable pulse that everybody in the room hears at the same instant.

The metronome is the block. Roughly every ten minutes a beat lands, every listener hears the same one, and the beats are numbered. That numbering is the only clock on which two strangers who share nothing else already agree, which is what makes it worth building a temporal artwork on: an object that changes on height changes at the same moment for everybody, without a server telling them so. The count is exact. The tempo is not, and section 4.1 is careful about the difference, because almost every mistake available in this design is made by treating a beat as ten minutes.

A katamari is the ball you push around in *Katamari Damacy* (Namco, 2004), a game built on one mechanic: everything the ball touches sticks to it and nothing ever comes off, so it begins the size of a marble, then picks up coins, then cats, then cars, then houses, and every one of them is still visibly stuck to it at the end.

The katamari is what the chain does with what it touches. It rolls in one direction only, whatever it passes adheres, and nothing is ever set back down, so an object living on the chain does not simply have a history filed away somewhere else: it carries that history stuck to its own surface, and anybody can measure how much of it has accumulated. A file copied between two computers arrives identical and unaccompanied. A collectible transferred between two keys arrives with the transfer recorded, the previous holder still named, the interval it sat unmoved still countable, and the whole of that available to a stranger's client without asking either party. The record is not metadata attached to the object. It is the object's shape.

Both are derivations, and derivation is the only primitive underneath either of them.

Bitcoin has two one-way functions and everything else is those two applied over and over. Scalar multiplication on secp256k1 turns a private key into a public one; SHA-256 turns bytes into a digest. Both are cheap in one direction and infeasible in the other, which is what makes them useful for agreement between parties who do not trust each other: a value that anybody can recompute and nobody can invert needs no authority to vouch for it. Custody is a derivation you cannot reverse. An address is a derivation of a derivation. Proof of work is derivation by search, which is why a block cannot be computed and can only be looked for. And verification, throughout, is re-derivation: Simplified Payment Verification does not consult a record of inclusion, it recomputes a merkle root from a path and compares, and [BRC-168](/apps/0168)'s selective disclosure recomputes a commitment from a value and a salt and walks to a root that was published before the conversation began. The idiom is always recompute and compare.

This document applies that idiom to a collectible, which has so far been exempt from it. A stored image on a chain is a foreign object: an assertion parked inside a system in which every other value is recomputable. A derived collectible is native. Its state is a computation any stranger can repeat, in any decade, from the score and the chain.

The second thing derivation gives is more interesting and less often said. **Derivation is lossy, and it does not stop at the picture.** There are four derivations here, not three: chain facts derive state, state derives a render, and a render derives a reading, which happens in a person and is nobody's to specify. Each stage loses something and none can be inverted. You cannot recover a score from a picture any more than you can recover a private key from an address, and you cannot recover from a reading what the work was. That is what Sol LeWitt meant by the instructions being the work and the drawing being a version of it; what Eno meant by a system rather than a recording; and what *Longplayer* demonstrates the hard way, having been rebuilt more than once in a quarter of a century because a performance is always an approximation of a score and the machinery of performance rots faster than the score does.

So the honest description of what a work under this document offers is not certainty about an artwork. It is a derivation anybody can repeat, an error term stated rather than hidden, and a reading that remains the viewer's own. We approximate the work. We do not arrive at it. Section 3 is where that stops being a sentiment and becomes a requirement.

## Motivation

A collectible today is a picture and a receipt. The picture is bytes that do not change, and the receipt is a chain of transfers nobody renders. The interesting half is thrown away.

Four properties are available on a chain and are not being used.

**A shared clock with no clockkeeper.** Anything scheduled off wall-clock time needs somebody's clock, and a work that phones a server to ask what time it is has made the server a party to the artwork. Height is a fact both the artist and the collector can look up, and it is monotonic, public, and free. It is also the only such fact: this document uses it in preference to timestamps everywhere the choice arises, and section 4.1 says why the alternative is worse than it looks.

**A provenance that can be read by the work itself.** Every collectible already carries a complete, public, tamper-evident record of everywhere it has been, and in every deployed system that record is a table on a marketplace page. Nothing renders it. A work that reads its own record can make holding legible: a piece that visibly wears, accumulates, ages, or quiets according to what was actually done with it, where "actually done with it" is recomputable by whoever is looking rather than asserted by whoever is selling. This is the half of the design that most changes what a collectible is, and it is the half with the sharpest failure mode, because some provenance facts cost real money to produce and others cost a transaction fee. Section 6.2 separates them and is blunt about which is worth building on.

**Coordination by derivation rather than by registry.** This corpus already runs on the pattern. [BRC-42](/key-derivation/0042) and [BRC-43](/key-derivation/0043) derive keys from protocol and key identifiers rather than from a directory; BRC-169 section 4.5 derives its certificate type identifiers from printed strings, because BRC-52 defines the type as opaque bytes and no BRC defines an authority over them; [BRC-145](/apps/0145) derives a typed content anchor with the type inside the digest, explicitly so that anybody can introduce a type without asking; [BRC-87](/overlays/0087) establishes no registry at all and leaves naming to agreement. A derived collectible belongs to that family and section 2.7 joins it rather than inventing an allocator.

**Rules are smaller than renders, and they survive better.** A hundred-year artwork cannot be a video file, because nothing will decode it. It can be a page of arithmetic and a handful of inscribed assets, if the arithmetic is specified tightly enough that a reimplementation is possible from the specification alone. That is not a claim that this document achieves longevity, and Limitation 9 refuses the claim explicitly. It is a claim about which of the two has a chance.

### What this is not for

**It is not a way to change what somebody bought.** A score is fixed at mint and this document gives nobody a way to edit it afterwards, including its author. An evolving work is one whose *rules* were published before the collector agreed to them and whose *state* moves within those rules; a work whose rules can be rewritten by the artist is a subscription with a picture on it. Section 2.5 makes immutability structural rather than a promise, and section 9 requires a client to show a collector the whole schedule of what the work will do before they buy it.

**It is not a mechanism for scarcity theatre.** Nothing here mints, prices, ranks, or grades. A score may read the record and it may not score it, and no client may narrate it: sections 6.5 and 6.6 forbid a rating of a holder and forbid a caption interpreting one, because a leaderboard built out of provenance is a machine for manufacturing provenance and an adjective attached to it is a stranger's reading passed off as a chain fact.

**It is not a substitute for storing the work.** A score referencing assets that live on somebody's web server is a work that ends when the bill goes unpaid, and calling it on-chain because its rules are on-chain is the most common dishonesty available in this design. Section 5.4 requires the distinction to be disclosed per asset and refuses to pretend the two are equivalent.

**It is not proof of anything.** A rendering is not evidence, and section 3.4 says so as a requirement. What the chain supports is a recomputation; a picture is the far end of a one-way function and establishes nothing about what went into it.

## Prior Art

Six bodies of work bear on this. The first supplies the epistemics, the next two the mechanism, the fourth the artistic argument, the fifth is the closest relative and the one this document is most often mistaken for, and the sixth is a warning nobody in this field has yet answered.

**Probabilistic settlement.** The original Bitcoin paper does not claim that a transaction becomes irreversible. It gives the probability that an attacker with a given share of hashpower catches up from a given depth, and that probability falls exponentially without ever reaching zero. Six confirmations is a bound on plausibility, not a proof of anything, and Simplified Payment Verification ([BRC-9](/transactions/0009)) is that bound made portable: a merkle path plus a chain of headers, re-derived by the verifier. Everything in section 3 of this document is an application of that stance to properties of an artwork, and the stance is worth naming as prior art rather than presented as caution, because it is the reason a chain can be relied on at all. A system that states its error term can be checked. One that claims certainty cannot.

**The block schedule as a source of properties.** Ordinals assign every satoshi a rarity from its position in the issuance schedule: the first satoshi of a block, of a difficulty adjustment period, of a halving epoch, and of the cycle where the two coincide. That is the construction of section 4.2, arrived at first and by a different route, and it establishes the useful half of the idea: a property derived from the schedule needs no oracle, no signature and no registry, because every client recomputes it and none can disagree. What it does not do is evolve. A satoshi's rarity is fixed at issuance, which is correct for a rarity and leaves the temporal case open.

**On-chain composition and provenance on BSV.** [BRC-45](/tokens/0045) established the UTXO as the token, and the 1Sat Ordinals stack built the deployed collectible on top of it: [BRC-147](/tokens/0147) profiles custody into a wallet basket, and [BRC-150](/tokens/0150) and [BRC-156](https://github.com/bsv-blockchain/BRCs/tree/master/tokens/0156.md) specify how a client follows one object's lineage back to its origin, which is the read section 6 depends on entirely. [BRC-224](/apps/0224) is the nearest structural neighbour: a composition expressed as a small manifest referencing independently owned components rather than as one opaque file, which is the same architectural bet this document makes about layers. [BRC-226](/tokens/0226) demonstrates that terms can be made to travel with an object across transfers by consensus rather than by convention, and is the mechanism the companion reserved in section 10 would reach for. [BRC-113](/tokens/0113) is cited for the shape of its verification, which is a single merkle proof against a genesis rather than a walk along a lineage, and which is the cheapest known answer to "is this the object it says it is". [BRC-60](/state-machines/0060) is the corpus's document on state advancing along a chain of events, which is what a work under this document is, and the difference is worth stating: there a transition happens because a party creates a transaction, while here the state moves with the chain whether anybody acts or not, so there is no event to index and nothing to miss.

**Generative and instruction-based art.** The claim that the rules are the artwork and the render is a performance of it is not this document's. Sol LeWitt's wall drawings were sets of instructions executed by other people, and the instructions were the work; the drawings were versions of it. Vera Molnar, Georg Nees and Manfred Mohr were parameterising composition in the 1960s, and Brian Eno's generative music, from *Discreet Music* through *77 Million Paintings*, is a fifty-year argument that a piece can be a system rather than a recording. What all of them supply is the answer to the objection that a computed artwork is not a work, and they are the reason section 2.5 puts the score rather than any particular frame at the centre of the object. They also supply the fourth derivation: LeWitt's drafters produced different walls from one instruction, and nobody thought the instruction had been violated.

**Chain-native dynamic collectibles.** Four systems are close enough to be worth distinguishing carefully.

Art Blocks fixes a work's parameters from the minting transaction hash and never changes them, which is the strongest available guarantee about what a collector owns and deliberately gives up evolution. Async Art split a work into a master and separately owned layers, each holder able to change their own layer's state, and is the closest prior art to sections 5 and 7: it establishes that distributed control over a composition is workable and that the interesting question is what a holder may change rather than whether they may. Its difference from this document is where the change comes from. There, a layer moves because its owner chose; here, it moves because the chain did, and the owner's choice is confined to the bounded mark of section 7. EulerBeats derived audio from a seed and made the derivation the whole of the artwork, which is the precedent for the sound companion section 10 reserves rather than for anything specified here. Terraforms holds an evolving state entirely on chain and advances it on block progression, which is the closest thing to section 4 that has actually shipped.

The product category around all of these is where the honesty problem lives. "Dynamic NFT" in current usage most often means an image whose metadata a server rewrites, sometimes on an oracle's word, and there is no way for a holder to tell that from a derived collectible by looking. The difference is not aesthetic and it is not enforced by any of the systems above: it is whether the state is recomputable by a stranger from public facts, or asserted by a party who could assert something else tomorrow. Every requirement in sections 2 and 3 exists to make that difference legible, which is why this document specifies the derivation in more detail than the artwork.

**Very long duration, and the thing nobody has solved.** John Cage's *Organ2/ASLSP* has been playing in Halberstadt since 2001 on a 639-year schedule; Jem Finer's *Longplayer* has been running since 1999 on a thousand-year one; Katie Paterson's *Future Library* seals a manuscript a year until 2114. All three are the ambition of this document's century-scale properties, and all three have met the same wall, which the chain does not remove. *Longplayer* has been through several complete migrations of the machinery that performs it, because the hardware and software it was written for stopped existing on a timescale of a decade or two rather than a century. Read that as evidence for the fourth derivation rather than as a failure: a performance approximates a score, migration is the normal condition of a long work, and what survives is the thing that can be re-derived. Limitation 9 takes it as the governing constraint.

The accretion metaphor in the Introduction is borrowed from *Katamari Damacy*, cited there and in the References.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119. They are used where interoperability or a collector's ability to recompute a claim actually breaks; the rest of this document describes a mechanism.

Throughout, "handle" and "resolve" have the meanings given in [BRC-169](/peer-to-peer/0169) sections 2.1 and 5.7, and "lineage" means the chain of transfers established by [BRC-150](/tokens/0150).

**One word is reserved out of this document.** Nothing here is a proof and nothing here proves anything, with the single exception of a merkle path, which proves inclusion. A client MUST NOT use "proof", "proven", "verified true", or a synonym about a state, a render, a provenance quantity, or a price. The available words are recomputed, re-derived, witnessed, estimated, and asserted, and section 3 assigns them.

### 1. Terminology

* **Work**: one collectible object, held as a 1Sat output per BRC-147 and identified by its **origin**, the outpoint at which it was minted.
* **Score**: the immutable rules from which a work's state is derived. Inscribed once, at mint, per section 2.5, and serialised per section 2.6 and typed per section 2.7.
* **Derivation**: a computation from stated inputs that any party can repeat and no party can invert. The document's only mechanism.
* **Shared state**: the values a score derives from chain facts alone, at a stated height. Identical for every client. Section 2.2.
* **Local state**: values derived from shared state plus the declared viewer inputs of section 2.3. Identical for every client given the same inputs.
* **Reference rendering**: a work's state with every viewer input at its neutral value, per section 2.3(4). The view works are compared and priced in.
* **Presentation**: how a state is drawn. Never an input to anything. Section 2.4.
* **Reading**: what a viewer takes from a render. The fourth derivation, per section 2.1, and outside this document's authority.
* **Confidence class**: how well a derived value is known, from the five of section 3.1.
* **Clock inputs**: the chain facts of section 4, derived from height.
* **Record inputs**: the chain facts of section 6, derived from the work's own lineage.
* **Layer**: one element of the composition, with a z-order and a rule selecting what occupies it. Section 5.
* **Mark**: a bounded, permanent addition made by a holder. Section 7.
* **Evaluation height**: the height at which a state is derived. Every state is stated with one, per section 2.8.
* **Retrospective state**: a state derived at a past height, per section 2.9. Labelled as such, and keyed on an owner key rather than on a handle.

### 2. Derivation

#### 2.1 The four derivations

A work reaches a viewer through four derivations. Each is lossy, none is invertible, and this document has authority over the first two, an opinion about the third, and nothing to say about the fourth.

| Stage | From                                 | To               | Whose                      | Reproducible by              |
| ----- | ------------------------------------ | ---------------- | -------------------------- | ---------------------------- |
| 1     | chain facts, score                   | **shared state** | this document, normatively | anybody, from the height     |
| 2     | shared state, declared viewer inputs | **local state**  | this document, normatively | anybody told the inputs      |
| 3     | state                                | **render**       | the client                 | anybody with the same client |
| 4     | render                               | **reading**      | the viewer                 | nobody, including the viewer |

The line that matters most is not between the chain and the viewer. It is between **reproducible** and **unrepeatable**. A value derived from the chain can be recomputed by a stranger in a hundred years. A value derived from the chain and a stated timezone can be recomputed by a stranger in a hundred years who is told the timezone. A value derived from whatever a browser happened to report cannot be recomputed by anybody, including the viewer, tomorrow. Stages 1 and 2 are on one side of that line and stage 3 is on the other, which is the whole of the reason they are separated.

Stage 4 is in the table because leaving it out is how a specification comes to believe it has produced meaning. It has not. A render is where this document stops, and what a work is *about* is derived by a person from a picture, unrepeatably, and belongs to them. Two requirements follow from admitting it: section 3.4, which forbids treating a render as evidence, and section 6.6, which forbids a client from performing stage 4 on the viewer's behalf and presenting the result as a fact about the object.

#### 2.2 Shared state is a pure function of chain facts

A score's shared state at height `h` MUST be derivable from, and only from:

1. the score itself, and any assets it commits to by digest;
2. the work's origin outpoint;
3. the evaluation height `h`, and the chain facts of section 4 derived from it;
4. the work's lineage up to `h`, and the record facts of section 6 derived from it.

Nothing else. Not a network fetch other than the ones needed to obtain 1 to 4, not an oracle, and not a random number that was not derived per section 8.2.

The property this buys is the one the whole document is for. Two clients at the same height agree; a client at a later height recomputes any earlier one; and a collector, a marketplace, a court and a stranger are all looking at the same object. A score whose every property is shared is the strongest form available under this document, and an author who does not need section 2.3 should not use it.

#### 2.3 Local state, and the closed list of viewer inputs

A score MAY declare a property **local**, derived from shared state together with facts about the viewer. A beach at the collector's own sunset is a better thing to own than a beach at a height-derived sunset, and a work that can only ever be the same for everyone cannot do it. What makes it specifiable rather than merely permitted is that the inputs are enumerated and the result is still reproducible from stated values.

1. A score MUST enumerate every viewer input it reads, and each MUST be drawn from this closed list. A score MUST NOT read an input outside it, and a client MUST refuse to render a score that names one.

| Input        | Type                                          | Supplied by                                                                           |
| ------------ | --------------------------------------------- | ------------------------------------------------------------------------------------- |
| `utcOffset`  | integer minutes, `-720` to `+840`             | The viewer's own setting or platform offset at the moment of rendering.               |
| `hemisphere` | `north` or `south`                            | The viewer's own setting. Never a network lookup, and never inferred from an address. |
| `calendar`   | an identifier from a set the score enumerates | The viewer's own setting, where a score renders dates at all.                         |

2. Locality is declared **per property**, not per work. A score MUST state, for each property it derives, whether it is shared or local, and a client MUST show a viewer which of the properties in front of them are which.
3. **A local state is reproducible or it is not a state.** Wherever a local state is displayed, exported, cached, or transmitted, it MUST carry the height and the value of every viewer input it consumed, per section 2.8. Two parties who disagree about a picture can then settle it by comparing inputs rather than by comparing hours.
4. **Every work MUST also have a reference rendering**: its state with `utcOffset` at `0`, `hemisphere` at the value the score names as its default, and `calendar` at the first value the score enumerates. A client MUST be able to produce it, and MUST use it wherever works are compared, listed, priced, or disputed. The reference rendering is what a marketplace shows and what an appraisal argues about; the local one is what the owner looks at.
5. Local state MUST NOT feed back into shared state, MUST NOT be written on chain, and MUST NOT be used to derive any value another party is expected to recompute.
6. A score SHOULD keep its local properties to the ones that genuinely want a personal hour, and SHOULD NOT make a work's rarest or most valuable state local. A state only reachable in one timezone is a state most owners cannot reach and none can demonstrate they reached.

What this costs is worth stating where an author decides. Two collectors comparing local works see different pictures and cannot tell from looking whether the difference is the work or the hour, which is what rule 4 exists to answer. And the viewer inputs are less stable than they look: `utcOffset` is a function of the timezone database, which governments change and sometimes change retroactively, so the same work at the same height in the same room may render differently in two different years. That is Limitation 3, it is real, and it is the price of the effect rather than a defect in the mechanism.

#### 2.4 Presentation reads the viewer and records nothing

Everything not covered by 2.2 or 2.3 is **presentation**: screen size, pixel density, available fonts, colour management, whether audio is permitted, whether the viewer has asked for reduced motion, the easing of an animation between two states. A client MAY vary all of it freely.

Presentation MUST NOT alter shared or local state, MUST NOT be recorded or transmitted as though it had, and MUST NOT be enumerated by a score. The line between 2.3 and 2.4 is that a score names its local inputs and reads them; it never names a font.

#### 2.5 A score is fixed at mint

A score MUST be inscribed at or before the work's origin, and MUST be committed to by digest in the work's minting transaction. A client MUST verify that digest before rendering, and MUST refuse to render a work whose score does not match rather than rendering what it was served.

There is no amendment mechanism, no upgrade path, and no author key with standing to change anything. This is a refusal rather than an omission. A score is the entire description of what the buyer is acquiring, and every argument for letting an author revise it is an argument for the buyer having acquired something else. Where an author wants a new version, the instrument is a new work.

Two consequences follow and should be stated where an author will meet them. A bug in a score is permanent, and there is no patch, so a score should be published and rehearsed against real lineages before anything is minted under it. And a score that references an asset it does not commit to by digest has left a hole exactly the size of the amendment mechanism this section refuses, which is what section 5.4 is about.

#### 2.6 The score object

A score is a JSON object. Everything a client needs in order to derive a state is in it, it is canonicalised before it is hashed, and the digest of that canonical form is what the minting transaction commits to.

| Field               | Type                 | Required    | Meaning                                                                                                                                                                                                            |
| ------------------- | -------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocol`          | string               | yes         | The ASCII string `derived-collectibles`. Carries no BRC number, per section 2.7(2).                                                                                                                                |
| `version`           | integer              | yes         | `1` for this document.                                                                                                                                                                                             |
| `type`              | string               | yes         | The score type identifier of section 2.7.                                                                                                                                                                          |
| `grid`              | `[integer, integer]` | yes         | Width and height in integer units, for the `place` operation of section 5.3.                                                                                                                                       |
| `layers`            | array                | yes         | Ordered back to front, per section 5.1.                                                                                                                                                                            |
| `hemisphereDefault` | `north` or `south`   | conditional | Required where any layer reads `season`. Also the value the reference rendering of section 2.3(4) uses.                                                                                                            |
| `seasonBoundaries`  | array of `MM-DD`     | conditional | Required where any layer reads `season`. Ascending, per section 4.4.                                                                                                                                               |
| `settlement`        | `resolve` or `hold`  | no          | Whether a `probable` property renders unresolved and resolves, per section 3.3. Absent means `hold`.                                                                                                               |
| `marks`             | object               | no          | Mark configuration: `kinds` as an array of the kinds the score offers, plus the `mechanism` of section 7.4, the `eligibility` rule of section 7.5, `slots`, and whatever each kind needs to enumerate its choices. |

A layer object:

| Field              | Type                            | Required    | Meaning                                                                                  |
| ------------------ | ------------------------------- | ----------- | ---------------------------------------------------------------------------------------- |
| `slot`             | string                          | yes         | Names the layer. Unique within the score.                                                |
| `locality`         | `shared` or `local`             | yes         | Per sections 2.2 and 2.3.                                                                |
| `op`               | one of section 5.2's operations | yes         |                                                                                          |
| `reads`            | string                          | yes         | The name of one clock quantity from section 4.2 or one record quantity from section 6.1. |
| `bands`            | array of integers               | conditional | Ascending thresholds. Required for `select`, `palette`, `place` and `absent`.            |
| `variants`         | array of asset objects          | conditional | Required except for `absent` with no asset.                                              |
| `palettes`         | array of arrays of strings      | conditional | Required for `palette`. Six-digit lowercase hexadecimal, without a leading `#`.          |
| `path`             | array of `[integer, integer]`   | conditional | Required for `place`. Waypoints on the grid.                                             |
| `divisor`, `bound` | integers                        | conditional | Required for `repeat`.                                                                   |

An asset object is `{ "storage": "inscribed" | "external", "digest": <64 lowercase hex>, "location": <string> }`, where `location` is required for `external` and optional for `inscribed`, and `storage` and `digest` are the labels section 5.4 requires.

**Resolution is by band count, and this is the whole of the rule.** For a value `v` and ascending `bands`, the index is the number of entries of `bands` that `v` meets or exceeds. So `bands` of length `k` selects among `k + 1` entries, and a score whose `variants`, `palettes` or `path` is not exactly `k + 1` long is malformed and MUST be refused. For `repeat`, the count is `min(v / divisor, bound)` in integer division. For `absent`, index `0` renders nothing.

**Canonicalisation and the digest.**

1. A score MUST contain only objects, arrays, strings and integers. Floating-point numbers, `null` and booleans MUST NOT appear, which keeps section 8.1's arithmetic rule true of the score itself and keeps canonicalisation to the part of [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) that two implementations cannot disagree about.
2. `scoreDigest` is `SHA-256` over the RFC 8785 canonical form, encoded UTF-8.
3. The minting transaction MUST carry a [BRC-48](/scripts/0048) PushDrop output whose pushed fields, in order, are the ASCII string `derived-collectibles`, the ASCII string `1`, the 32-byte `scoreDigest`, and a location: either the outpoint of the inscription carrying the score bytes, or the ASCII string `inline` where the score is the work's own inscribed content.
4. A client MUST recompute `scoreDigest` from the bytes it fetched and MUST refuse to render on a mismatch, per section 2.5. This is the document's own thesis applied to its first step: the score is not trusted, it is re-derived and compared.

**Version.** A client meeting a `version` it does not implement MUST treat the work as unrenderable and say so, and MUST NOT render the subset of layers it recognises. Silently ignoring an unrecognised field produces a picture the author did not write, which is worse than no picture, and the same reasoning appears in [BRC-146](/apps/0146) section 2.1. The cost is that a version bump is a hard break, so a later revision wanting a gradual path should add optional fields within version 1.

**Ceilings.** These are limits on the score, not on the client, and they exist because a bound every client picks for itself is a bound two clients disagree about. A client that refused a work another client rendered would break the determinism of section 2.2 as surely as a floating-point derivation would.

| Limit                                                    | Value |
| -------------------------------------------------------- | ----- |
| Layers                                                   | 16    |
| Entries in any `bands`, `variants`, `palettes` or `path` | 16    |
| `repeat` bound                                           | 64    |
| Mark slots per work                                      | 32    |
| Inscribed asset size                                     | 1 MiB |
| Total inscribed asset size per score                     | 8 MiB |

A score exceeding any ceiling is malformed and MUST be refused rather than truncated. The values are claimed rather than derived from anything, in the manner of BRC-146 section 2.1's sixteen-entry cap, and they are chosen so that a marketplace page holding a hundred works does a bounded amount of work per work.

#### 2.7 A score is typed by derivation, not by allocation

A score and a mark each carry a 32-byte type identifier, and both are **derived from a printed string** rather than assigned by anybody:

```
type = base64( SHA-256( ASCII derivation string ) )
```

| Object              | Derivation string               | `type`                                         |
| ------------------- | ------------------------------- | ---------------------------------------------- |
| Score (section 2.5) | `derived collectibles score v1` | `iDB79v3araIhb1GLVn21KzfM6n8KJHT2qvXn9hUswpo=` |
| Mark (section 7)    | `derived collectibles mark v1`  | `4zYAcdXK54vxfdCPLF4mdYbDA7Ew97HDvIJOqVs68sw=` |

1. Both values are normative. An implementation MUST use them and MUST NOT substitute a locally chosen constant.
2. The derivation strings carry no BRC number, and nothing else in this document carries one in a wire name either, so that a later revision does not leave the deployed vocabulary naming a superseded document.
3. The `v1` suffix versions the type rather than the document. A revision changing the meaning of either object MUST mint a new type by changing the derivation string, so that objects issued under two revisions stay distinguishable and no client is ever obliged to guess which set of meanings applies.

This is BRC-169 section 4.5's construction and it is here for the same reason, which is worth stating plainly rather than treating as convention. There is no allocator of type identifiers, there is no committee that could become one, and waiting for an assignment is waiting for an event that cannot happen. A derived identifier needs no authority, is reproducible from this section alone by anybody with a hash function, and cannot be captured. It is also the smallest possible demonstration of the document's own thesis: where a value can be derived, deriving it is strictly better than being told it.

#### 2.8 Every state is stated with a height, and with its inputs

A state MUST be accompanied by the height it was derived at, and by the value of every viewer input it consumed under section 2.3, wherever it is displayed, exported, cached, or transmitted. A state without them is not wrong, it is unfalsifiable: nobody can recompute it and nobody can say it has changed.

A cached state MUST be discarded when the tip advances past any height at which the score's own rules change a value, when any consumed viewer input changes, and on any lineage event for the work.

#### 2.9 The work at an earlier height

Section 2.2 makes every past state recomputable, and that is worth more than a property: it is the one thing a derived collectible can do that a stored image cannot, which is to be looked at as it was. A client MAY offer such a view, and where it does:

1. A retrospective state MUST be derived at a stated height from the lineage **as it stood at that height**, and MUST carry that height per section 2.8. Marks made after it MUST NOT appear. This is the mistake the naive implementation makes, because marks are permanent and the obvious code renders all of them.
2. A retrospective view MUST be labelled as retrospective and MUST NOT be presented as the work's current state.
3. **It MUST use the reference rendering of section 2.3(4) unless the viewer supplies viewer inputs explicitly.** A past holder's `utcOffset` is not a chain fact and was never recorded, so a client that renders a past state in the present viewer's timezone has invented a picture nobody ever saw.
4. Confidence is not retrospective. A value that was `probable` when a past viewer saw it is `settled` now, and a client MUST render the class as it stands at the evaluation height rather than reconstructing the uncertainty of the time.

**A tenure is the interval worth offering.** The record gives the transfer heights, so the period during which any one key held the work is bounded and derivable, and "as it was while this key held it" is the natural request. A client offering it SHOULD offer the state at the start and at the end of the tenure, since a work that moved during a long holding did most of its moving there.

**Ask it of a key, not of a handle.** A [BRC-169](/peer-to-peer/0169) handle is not a durable name for a party: section 2.1(4) of that document permits a handle to be released and reassigned, and its section 4.4 exists because the same handle can later resolve to a stranger. So the key in the lineage is `settled` and the binding from that key to a handle at that past height is not: a present resolution answers for the present, and nothing in a resolution response answers for a height years ago. A client MUST therefore key a retrospective view on the owner key, MAY label it with a handle, and MUST class that label `witnessed` per section 3.1. Where the handle's current binding differs from the one the client recorded, it MUST show that the name may have changed hands, exactly as BRC-169 section 4.4 requires before a value-moving action.

The honest summary is that a work can be shown as it was, and the person who held it can only be named as well as anybody can name a key. That is the fourth derivation arriving in the record: the object's history is recomputable, and who the owners were is a reading of it.

### 3. Confidence

Every value in this document is derived, and derived values are not equally well known. A quantity recomputed from a block buried a year deep, one recomputed from a transfer six blocks old, one an indexer reported without the client checking, a date inferred from a height, and a price nobody can see are five different epistemic objects, and a client that renders them alike has told its viewer that they are the same.

This section is the document's answer to its governing claim. Derivation yields approximation. The remedy is not to pretend otherwise but to carry the error term along with the value, in the manner of [BRC-146](/apps/0146) section 3.1's three-state verdict and BRC-169 section 6.2's insistence that an unsigned exchange rate is the sender's assertion rather than evidence.

#### 3.1 The five classes

Every derived value MUST carry exactly one class.

`settlementDepth` is **100 blocks** throughout this document. One constant with one value, named here so that sections 4.5 and 9.4 cannot drift apart from it, and claimed rather than derived: it is deep enough that a reorganisation of that size would be the network's problem rather than the artwork's.

| Class       | Means                                                                                                  | Typical source                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `settled`   | Re-derived by this client from data buried at least `settlementDepth` blocks                           | `age`, `hops` over old transfers, any clock quantity below the tip    |
| `probable`  | Re-derived by this client, from data shallow enough that a reorganisation could change it              | a transfer six blocks deep, entropy not yet matured under section 4.5 |
| `witnessed` | Reported by an indexer and not independently re-derived                                                | a lineage the client took on trust, per section 8.4                   |
| `estimated` | Re-derived through a lossy conversion, so exact in its own units and approximate in the ones displayed | a date or duration from a height, a season from median time past      |
| `asserted`  | Cannot be re-derived by anybody from chain facts                                                       | `valueMoved`, `lastPrice`, a host-supplied display name               |

Two rules make the classes real rather than decorative.

1. **A derived value takes the weakest class of its inputs.** A property computed from `age` (`settled`) and `lastPrice` (`asserted`) is `asserted`. There is no averaging and no majority: a chain of derivations is exactly as sound as the worst link in it, which is the same arithmetic BRC-169 section 8.4 applies to a delegation chain.
2. **A client that cannot establish a class MUST use `asserted`,** and MUST NOT omit the value silently. Not knowing how well you know something is a state worth showing.

#### 3.2 Rendering the class

1. A client MUST make the class of every displayed value discoverable, and MUST distinguish `asserted` from every other class **without requiring an interaction**. The failure this prevents is a price-derived property sitting in the same visual language as an age-derived one.
2. A client MUST NOT render an `estimated` value in the units of an exact one. A height converted to a date is the case this arises in, and section 4.1 already forbids it.
3. A client MUST carry the class through export, transmission, and any interface built on top of a state, per section 2.8. A class stripped at the boundary is a class that existed only for the developer.
4. A client MUST NOT aggregate values of different classes into one figure without reporting the weakest, per 3.1(1).
5. A `witnessed` value SHOULD name what witnessed it, and SHOULD say when. This is BRC-169 section 4.2(3)'s rule about recording the time of a revocation check, arriving for a different fact.

#### 3.3 A work may render its own confidence

A score MAY make confidence visible in the work itself, and this document recommends it.

The construction is one mechanism doing two jobs. A property derived from shallow data is `probable`, and a score MAY render a `probable` property **unresolved**: out of focus, undecided between two variants, thin in the mix, drawn without its final detail. As the data buries and the class advances to `settled`, the property resolves. A freshly transferred work is therefore visibly not yet settled, and settles over the following day.

1. Where a score does this, the unresolved and resolved forms MUST both be derived from the same state, so that resolution is a change of class and not a change of value. A property that would render differently once settled has a bug rather than a style.
2. A score MUST NOT make an unresolved state more desirable than a settled one. The reason is section 6.2's: anything a holder wants can be manufactured, and the way to manufacture an unsettled state is to keep transferring the work.
3. A client MUST NOT present an unresolved rendering as a defect, an error, or a load failure. It is the work saying what it currently knows.

What this buys is worth more than its cost. Probabilistic settlement is the single most misunderstood property of the chain the work lives on, and a collectible that goes soft on transfer and hardens over the next hundred blocks teaches it to everybody who owns one, without a word of explanation and without a warning nobody reads.

#### 3.4 One way, and therefore not evidence

Each of the four derivations is one-way, and the consequence is a requirement rather than an observation.

1. A rendering MUST NOT be presented as evidence of a work's state, its provenance, its age, or its authenticity, and a client MUST NOT imply that it is. A picture is the far end of a one-way function: you cannot invert a render to recover a score, a record, or a height, exactly as you cannot invert an address to recover a key.
2. What can be relied on is the recomputation. A party asserting something about a work is asserting that a derivation from stated inputs yields a stated output, and the counterparty's remedy is to run it. A client SHOULD therefore be able to export the inputs of any state it displays, so that a recipient can re-derive rather than believe.
3. Where two parties derive different states, the disagreement MUST be resolvable by comparing inputs, in the order height, viewer inputs, lineage digest (section 8.3), score digest. This is why section 8.3 exists: two clients that agree on every input and differ in the output have an implementation bug, and two that differ on an input never had a disagreement about the work.

The aesthetic corollary is already in the document and is worth naming here, because it is the same property. A monotone construction, of the kind section 6.3 recommends, **is** a one-way function the viewer can see. Patina that deepens and never polishes out is not a decision about surfaces; it is irreversibility made visible, which is the honest rendering of handling, because handling is not reversible either.

### 4. The Clock

#### 4.1 Height counts, and does not keep time

Height is exact, monotonic, and shared. The interval between heights is none of those things. The ten-minute target is a long-run average maintained by retargeting, individual intervals vary from seconds to hours, and the average itself has historically run slightly fast, so a count of blocks and a count of ten-minute periods diverge without bound.

The consequence is a rule and not a caution. **Where a score expresses a duration, it MUST express it in blocks**, and where a client presents a block quantity as a duration in days or years it MUST class it `estimated` per section 3.1 and MUST NOT present it as a date. A score that wants an event on a calendar date cannot have one; what it can have is an event at a height, and a client that renders "on 1 January 2050" from a height has invented a precision the chain does not carry.

This is the same choice [BRC-146](/apps/0146) section 2.4 makes for its notice period and for the same reason: two clients share a chain and do not share a clock.

#### 4.2 The intervals height divides into

A score MAY read any of the following, each an integer function of `h`:

| Quantity        | Derivation                         | Notes                                     |
| --------------- | ---------------------------------- | ----------------------------------------- |
| `height`        | `h`                                | The beat.                                 |
| `chainDay`      | `h / 144`                          | 144 blocks is a day at target. Not a day. |
| `retargetEpoch` | `h / 2016`                         | The difficulty period.                    |
| `halvingEpoch`  | `h / 210000`                       | The longest hand on the clock.            |
| `dayPhase`      | `h % 144`                          | Position within the chain day, 0 to 143.  |
| `retargetPhase` | `h % 2016`                         |                                           |
| `halvingPhase`  | `h % 210000`                       |                                           |
| `subsidy`       | Per consensus, from `halvingEpoch` | Halves every epoch.                       |

All division is integer division. The names are normative so that two scores mean the same thing by `chainDay`, and the units are deliberately named for the chain rather than the calendar: a `chainDay` is 144 blocks and a client MUST NOT label it as a day without qualification, for the reason section 4.1 gives.

A score MAY define its own intervals as multiples of a block. A score MUST NOT define one in seconds.

#### 4.3 The wall clock, where it is unavoidable

Some properties are about the world rather than the chain, and a season is the obvious one. Two chain-derived timestamps exist and only one of them is usable.

A block's own `nTime` MUST NOT be used. It is chosen by the miner, is valid up to two hours ahead of network-adjusted time, and is not monotonic, so a score reading it can go backwards and can be pushed forwards by whoever found the block.

**Median time past**, the median of the preceding eleven blocks' timestamps, MUST be used instead where a score needs an approximate wall-clock. It is non-decreasing by consensus, it cannot be moved far by any single miner, and it lags real time by roughly an hour. A score reading it MUST NOT depend on a resolution finer than a day, a value derived from it is `estimated` per section 3.1, and a client MUST NOT render it as a clock time.

#### 4.4 Seasons, and the refusal of an ephemeris

A score MAY derive a season from median time past by fixed UTC date boundaries stated in the score, and MUST NOT derive one astronomically.

The refusal is deliberate and worth the paragraph. An equinox is a function of the Earth's orbit, requires an ephemeris and a model of the difference between terrestrial and universal time, and two implementations of it will disagree in the minutes around a boundary, which is exactly where a work that changes on the boundary is being looked at. A specification is not entitled to an ephemeris. Fixed dates are wrong by a day or so against the astronomical event and identical in every implementation, which is the trade this document takes everywhere it appears: a stated approximation everybody shares beats an unstated precision nobody can reproduce.

A score MUST state which hemisphere its seasons are for, or declare the season local and read `hemisphere` per section 2.3. A work whose winter is January is a northern work, and a southern collector is entitled to know that rather than conclude the work is broken. Where the season is local, the reference rendering of section 2.3(4) uses the score's declared default hemisphere, so a listing still shows one agreed season.

#### 4.5 Derivation by search, and what a miner can do about it

Not every derivation is a computation. A block cannot be computed, only looked for, and that difference decides which chain facts are safe to build a property on.

| Mode           | Cost                                | Predictability                | Who chooses the result           |
| -------------- | ----------------------------------- | ----------------------------- | -------------------------------- |
| By computation | free                                | fully predictable from inputs | nobody                           |
| By search      | a block's worth of work per attempt | unpredictable before the fact | **whoever performed the search** |
| By witness     | a network request                   | not derived at all            | whoever answered                 |

A score MAY derive unpredictable values from block hashes, which is derivation by search, subject to two rules.

1. A value derived from the hash of the block at height `k` MUST be classed `probable` until the tip is at least `k + settlementDepth`, and MUST NOT be treated as `settled` before that. Below that depth a reorganisation can change the hash and therefore the value. Section 3.3 is the recommended way to render the interval rather than hide it.
2. A score MUST NOT derive a value whose payoff to a miner exceeds the cost of discarding a solution, from a block that miner could be mining.

Rule 2 is the middle column of the table cashed out. Whoever performs a search sees the result before anybody else and may discard it and search again, so a trait revealed by the hash of the block confirming a mint is a trait that block's miner can grind for, and where the rare outcome is worth more than a block reward the grinding is rational rather than malicious. This cannot be checked mechanically and is stated so that an author does not learn it from a market. The safe constructions are the ordinary ones: derive from a hash fixed long before anybody knew what it would be worth, derive from the record rather than from entropy, or accept that the reveal is grindable and disclose it under section 9.

Where a score wants values unpredictable before mint and fixed forever after, the origin outpoint is the right source, and section 8.2 defines the derivation. Note where that moves the search: the minter chooses the transaction's inputs, so the minter can grind, which is cheaper than a miner's grind and usually less valuable. It MUST be disclosed under section 9.

### 5. Composition

#### 5.1 Layers are ordered and the order is stated

A composition is an ordered list of layers, given **back to front**, so that the first entry is the furthest away and the last is nearest the viewer. Each layer names a slot in the picture and a rule selecting what occupies it.

The direction is stated because it is got wrong at once. A sky sits behind everything; a sun or a moon sits in front of the sky and behind every figure; a figure in the middle distance sits behind one in the foreground. An author listing celestial bodies as foreground has described a scene in which the moon occludes the people, which is a choice available to them and is almost never the one meant.

A worked example, back to front:

```
0  ground      the base scene, always present
1  horizon     substituted by season
2  celestial   sun or moon, positioned by dayPhase
3  weather     present or absent, derived per 8.2
4  distant     figures, count derived from the record per 6.1
5  near        figures, count derived from the record per 6.1
6  surface     patina, accumulated per 6.3
7  marks       holder marks, per section 7
```

#### 5.2 What a layer rule may do

A layer's rule MUST resolve to exactly one of the following, and MUST resolve deterministically from state:

| Operation | Effect                                                                   |
| --------- | ------------------------------------------------------------------------ |
| `select`  | Choose one variant from a fixed, enumerated set.                         |
| `absent`  | Render nothing.                                                          |
| `palette` | Render the layer's asset with a palette chosen from a fixed set.         |
| `place`   | Render at a position derived from state, along a path the score defines. |
| `repeat`  | Render `n` instances, `n` derived from state and bounded by the score.   |

`repeat` MUST carry a bound in the score, and a client MUST refuse to render a work whose bound is absent. An unbounded count derived from a quantity that grows with the record is a work that eventually cannot be drawn, and the Security Considerations treat it as the denial-of-service surface it is.

Every enumerated set MUST be fixed in the score. A rule that can resolve to something not enumerated at mint is the amendment mechanism section 2.5 refuses.

#### 5.3 Positions are integers on a grid

A `place` operation derives a position in integer units on a coordinate grid whose dimensions the score states. Interpolation, easing, and sub-unit motion are presentation and belong to the client.

The reason is section 8.1's: two implementations agreeing on integers will disagree on floating-point curves, and a work whose sun is at a slightly different height in two clients has failed the only test this document sets.

#### 5.4 Where assets live, stated per asset

Every asset a score references MUST be committed to by digest, and MUST be labelled with one of:

| Label       | Meaning                                                                                                   |
| ----------- | --------------------------------------------------------------------------------------------------------- |
| `inscribed` | The bytes are on chain, at a stated location, and a client can fetch them and recompute the digest.       |
| `external`  | The bytes are somewhere else. The digest says what they should be; nothing guarantees they will be there. |

A client MUST verify every asset it renders against its digest, MUST refuse to substitute an asset whose digest does not match, and MUST show a viewer which of its assets are `external` and how many.

The labelling is the whole of this section's contribution and it is not a technicality. A work described as living on the chain, whose sky is a URL, ends when that URL does, and the digest does not save it: a digest detects what is missing rather than supplying it. An author entitled to use external assets is not entitled to have them described as though they were inscribed, and a collector deciding what to pay is deciding partly on this.

### 6. The Record

This is the katamari half. Every quantity in this section is derived by following the work's lineage per BRC-150 and BRC-156, and every one of them is available to any client that can read the chain.

#### 6.1 The provenance vector

At evaluation height `h`, a work's record yields:

| Name               | Meaning                                              | Derivation                                                          | Class                    |
| ------------------ | ---------------------------------------------------- | ------------------------------------------------------------------- | ------------------------ |
| `age`              | Blocks since mint                                    | `h - originHeight`                                                  | `settled`                |
| `hops`             | Transfers so far                                     | Count of lineage transfers                                          | `settled` past depth     |
| `hands`            | Tenures the work has passed through                  | `hops + 1`, so a reacquisition is a further hand                    | `settled` past depth     |
| `holders`          | Distinct owners                                      | Count of distinct owner keys, the minter included                   | `settled` past depth     |
| `tenure`           | Blocks in the current hands                          | `h - lastTransferHeight`, or `h - originHeight` where `hops` is `0` | `probable` while shallow |
| `longestTenure`    | The longest anybody has held it                      | Max over every tenure, the one still running included               | `settled`                |
| `shortestTenure`   | The briefest                                         | Min over every tenure, the one still running included               | `settled`                |
| `returns`          | Reacquisitions                                       | Transfers to a key that held the work before                        | `settled`                |
| `firstHolderHolds` | Whether the work has never been transferred          | True while `hops` is `0`, false permanently after                   | `settled`                |
| `ecosystems`       | Distinct handle domains among holders                | Where owners resolve to handles per BRC-169                         | `witnessed`              |
| `marks`            | Marks added                                          | Section 7                                                           | `settled`                |
| `lineageDigest`    | A commitment to the whole record                     | Section 9.3                                                         | as its inputs            |
| `valueMoved`       | Cumulative satoshis paid across transfers            | Section 6.2                                                         | `asserted`               |
| `lastPrice`        | Satoshis paid at the most recent transfer            | Section 6.2                                                         | `asserted`               |
| `merged`           | Whether the work has ancestors on more than one line | Section 6.7                                                         | `settled`                |

The class column is normative and follows section 3.1(1): a property derived from any of these takes the weakest class among the ones it read. `ecosystems` is `witnessed` because resolving an owner key to a handle is somebody's answer rather than a recomputation,..

**Every quantity is defined at mint, and none is a minimum over an empty set.** A work with no transfers has one tenure, the one still running, so `tenure`, `longestTenure` and `shortestTenure` all equal `age`; `hops` is `0`, `hands` and `holders` are `1`, `returns` and `marks` are `0`, and `firstHolderHolds` is true. Every work passes through that state, and a specification that leaves it open collects three answers.

**`hands` and `holders` are different quantities and it is worth saying how.** `holders` is the size of a set, so a work that goes from Alice to Bob and back to Alice has two holders. `hands` counts tenures, so the same work has passed through three hands. Where `returns` is `0` the two differ by exactly one and a score should read whichever it means; where `returns` is nonzero they diverge, and the mistake to avoid is reading `holders` for "how many times has this changed hands", which is `hops`.

A score MAY read any of these. A score MUST NOT read a fact about a holder that is not in this list, and in particular MUST NOT read a holder's other holdings, balance, or activity: those are facts about a person rather than about the work, and the Security Considerations are blunt about what a work that renders them becomes.

#### 6.2 Tenure cannot be manufactured. Hops can be bought.

The quantities above are not equally trustworthy, and the confidence classes of section 3 do not capture the difference, because this one is about *cost to fake* rather than about *how well known*. A value can be `settled` and worthless.

`hops`, `holders`, `returns` and `ecosystems` are cheap. One person with two keys can transfer a work between them for a fee, as many times as they like, and produce any value of `hops` they want in an afternoon. Nothing detects it: the keys are unrelated as far as the chain is concerned, and a marketplace sale between two wallets one person controls is indistinguishable from a sale between strangers.

`age`, `tenure`, `longestTenure` and `shortestTenure` cannot be manufactured at all, because the only way to produce them is to wait. They are the provenance equivalent of [BRC-146](/apps/0146) section 4.5's lock: what makes them worth reading is not that they are hard to forge but that forging them costs exactly what having them costs, which is time nobody gets back.

`valueMoved` and `lastPrice` are worse than cheap, they are usually wrong, which is why section 6.1 classes them `asserted`. A transfer's satoshi value is visible only where the sale settled on chain in one transaction; a gift moves for a dust output, a bundle prices five works as one, and anything settled off chain shows nothing.

The recommendation this yields is short. **Prefer the properties that cost time.** A work that deepens with tenure rewards the collector who kept it; a work that brightens with hops rewards the collector who wash-traded it, and will be wash-traded.

#### 6.3 Constructions

The ways a record can be made visible are the point of the section, and the following are offered as constructions rather than requirements. Each is a pure function of the vector above, or of the marks of section 7.

| Construction          | Reads                    | What it does                                                                                                                                                                                    |
| --------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Patina**            | `hops`                   | A surface accumulates handling. Deepens monotonically and never reverses, which is what makes it read as wear rather than decoration, and which is section 3.4's one-way function made visible. |
| **Growth rings**      | `age`                    | One ring per `halvingEpoch`, drawn from the centre out, so the work's age is countable by looking.                                                                                              |
| **Settling**          | `tenure`                 | Elements drift toward rest while nobody moves the work, and are disturbed on transfer. Pairs naturally with section 3.3's resolution, since both say the same thing about a recent transfer.    |
| **Dust**              | `longestTenure`          | Accumulates over the longest unmoved stretch and is never cleared, so a work that slept for a decade carries the decade.                                                                        |
| **Depth of field**    | `hands`                  | Each tenure occupies a plane, receding, so the composition acquires literal depth from the number of times it changed hands.                                                                    |
| **The minter's mark** | `firstHolderHolds`       | Present only while the work has never left its creator, and gone permanently once it does. Unrepeatable by construction.                                                                        |
| **Homecoming**        | `returns`                | A distinct, non-accumulating state while a work is back with a previous holder. The only construction here that is not monotone, and the score must say so.                                     |
| **Scars**             | `shortestTenure`         | A record of the briefest holding, which is the trace a flip leaves.                                                                                                                             |
| **The cairn**         | `order` marks, `slotted` | Each holder adds one stone. The pile is the provenance, countable at a glance.                                                                                                                  |
| **Footprints**        | `position` marks         | Every holder leaves one, where they chose, and the tide never takes them.                                                                                                                       |
| **Windows**           | `palette` marks          | One lit window per holder, in their colour. A work held by forty people is a lit street.                                                                                                        |
| **Rings and knots**   | `keyprint` marks         | An involuntary trace per holder, so the work records who had it without anybody deciding to be recorded.                                                                                        |

Two design notes carry across all of them. A construction that reads a cheap quantity should not be one whose output a holder wants more of, per 6.2. And a construction reading a monotone quantity produces a work that can only move one way, which is a strong artistic constraint and the honest one: handling is not reversible, and a work whose wear can be polished out is telling the viewer something untrue about what happened to it.

#### 6.4 What a holder can and cannot do

There is one role in this document and no ladder. A work has a holder, the holder is whoever the lineage shows, and this section states what that is worth, because the rest of the document leaves it implicit and a reader will assume more of it than is there.

**What the holder may do.**

1. **Mark the work**, per section 7, within the score's vocabulary, mechanism, eligibility rule and remaining slots. This is the only way anybody adds anything to a work, and it is available to the holder alone because section 7.1(2) checks the signature against the key the lineage shows.
2. **Merge or split**, where the score defines it, per section 6.7, and only over works the holder controls at that height.
3. **Transfer it**, which is the token layer's business and not this document's.

**What the holder may not do, and this is the longer list.** A holder MUST NOT be able to change the score (section 2.5), remove or alter any mark including their own (section 7.1(4)), change the record, prevent the work being rendered by anybody, make the work or its record private, or stop a future holder marking it. A client offering any of these has invented an authority the mechanism does not contain.

**Viewing is not a capability.** Everyone derives the same work from the same public facts, holder and stranger alike, and a client that gates rendering on ownership has added a restriction this document does not have and cannot enforce. Ownership buys the right to **add**, never the right to **see**, and the asymmetry is deliberate: a record whose readership its subject controlled would be a claim rather than a record.

#### 6.5 No scores about holders

A score MUST NOT derive, and a client MUST NOT display, a rating, ranking, grade, or comparison of a work's holders or of works against each other on the basis of their records.

This is a requirement for the reason [BRC-168](/apps/0168) section 7.8 gives about its own records, and it applies harder here because the quantities are public. A leaderboard of works by `hops` is an instruction to transfer; one by `tenure` is an instruction to sit on an object that would otherwise have moved, which is the same distortion wearing better clothes. The record is worth rendering as what happened. It stops being worth anything as a target.

#### 6.6 The client does not narrate the record

A client MUST NOT caption, characterise, or interpret a work's record. No adjectives, no epithets, no summaries in the register of judgement: not "well loved", not "neglected", not "cherished", not "flipped", not "a survivor".

This is the fourth derivation of section 2.1, and the rule is that it is not the client's to perform. A record is a set of quantities with classes attached, and every reading of it is a person's inference from those quantities: forty transfers is enthusiasm or churn, a decade unmoved is devotion or a lost key, and a client cannot tell and neither can a specification. A caption resolves that ambiguity on the viewer's behalf and then presents the resolution in the same interface as the chain facts, where it acquires their authority and cannot be distinguished from them.

What a client MAY do is render the quantities, render their classes, render the constructions the score defines, and say what the derivations were. A viewer looking at a work with sixty rings and no dust can conclude whatever they like about the person who held it, and that conclusion is theirs, unrepeatable, and correctly located.

This will be the most frequently violated requirement in the document, because narrating provenance is very good marketing copy and a bare integer is not. It is a requirement anyway. A marketplace that describes a work as treasured has invented a fact about a stranger and attached it to somebody's property.

#### 6.7 Merging, splitting, and what happens to the record

A score MAY define a merge, in which two works are consumed and one is produced. Where it does:

1. The child's record MUST carry both parents' records, and the child MUST be marked `merged` permanently.
2. `hops` and `holders` become ill-defined across a merge, since the child has two lines. The score MUST state how it combines them, from `sum`, `max`, or `both`, and a client MUST show which was used.
3. `age` MUST be taken from the **older** parent. A merge does not make an object younger, and taking the newer origin is how a work launders its age.
4. A merge is irreversible. A score MUST NOT offer an unmerge, and a client MUST state before a merge that both parents cease to exist and that the operation cannot be undone.

A score MAY define a split only where the work was itself produced by a merge, and the split MUST produce the parents' records rather than halves of the child's. Splitting an unmerged work is not defined here: an object with one history has nothing to divide, and dividing the picture is a different operation from dividing the record.

**Fractional ownership is not specified here.** A claim on part of a work is a custody arrangement, needing a custodian, a redemption path, and a rule for what happens when the arrangement ends, none of which this document provides and all of which [BRC-146](/apps/0146) section 11 sets out for the analogous case. What this document can say is narrower and is worth saying: where a work's ownership becomes a set rather than a single key, every quantity in section 6.1 that reads "the owner" is undefined, and a score intended for fractional ownership MUST state what it reads instead.

### 7. Marks

Section 6 is everything the chain observed about a work's holders. A **mark** is the one thing a holder gets to choose. It is bounded, permanent, attributable, and made while they hold the work, and it is the mechanism by which the katamari picks up something somebody put there deliberately.

The whole of the design tension is in the bounds. A holder who may add anything anywhere is a holder who may overwrite the work, and the first work under a permissive score to be defaced is the last work anybody buys under it. So the vocabulary is closed, the count is fixed, and nothing is ever free-form.

#### 7.1 The rules that hold for every mark

1. A mark MUST be confined to a slot the score defines, with a fixed maximum size and a fixed maximum count, and the count MUST NOT exceed the ceiling of section 2.6. A client MUST refuse to render a score whose marks are unbounded in either.

   The count bounds marks **made on** the work. Marks a work inherited through a merge are not made on it and do not consume its slots, so a child of two full parents carries both sets and has its own slots still open. A score MUST state whether inherited marks are carried or dropped, since the two produce visibly different objects and section 6.7(1) otherwise leaves the answer to whoever implements the merge.
2. A mark MUST carry the type identifier derived in section 2.7 and MUST be signed by the key the lineage shows as holding the work at the height the mark was made. A client MUST verify both, and MUST ignore a mark that fails either. Without this rule a mark is not a holder's mark, it is a stranger's graffiti attached to somebody else's object.
3. **A mark's height is derived, not asserted.** It is the confirmation height of the transaction that carried the mark, and it MUST NOT appear in the mark object, for the reason [BRC-168](/apps/0168) section 3.1 gives about its own commitments: a self-asserted time is the thing a chain is being used to replace. Marks are ordered by that height, ties broken by the lexicographically lowest transaction identifier, so that every client renders them in the same order.
4. A mark MUST NOT be removable, by anybody, including the artist, the marker, and every subsequent holder. A guest book editable by whoever holds it last is not a record of anything.
5. A mark's vocabulary MUST be enumerated in the score, from section 7.3. **Free bytes MUST NOT be permitted**, in any quantity, under any encoding.
6. A score MUST state its marking mechanism, from section 7.4, and its eligibility rule, from section 7.5.

#### 7.2 The mark object

A mark is a JSON object, canonicalised and signed the way section 2.6 canonicalises a score. Two things are carried by the transaction rather than by the object, and both are derived rather than claimed: the height, per section 7.1(3), and the identity of the marker, which is the key that signed.

| Field       | Type                     | Required    | Meaning                                                                                                         |
| ----------- | ------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------- |
| `protocol`  | string                   | yes         | The ASCII string `derived-collectibles-mark`.                                                                   |
| `version`   | integer                  | yes         | `1` for this document.                                                                                          |
| `type`      | string                   | yes         | The mark type identifier of section 2.7.                                                                        |
| `origin`    | string                   | yes         | The work being marked, as `<txid>_<vout>`. A mark that does not name its work can be replayed onto another one. |
| `kind`      | string                   | yes         | One of section 7.3's kinds, and one the score enumerates.                                                       |
| `value`     | integer, string or array | conditional | The holder's choice, per the table below. Absent exactly where the kind derives its own value.                  |
| `signature` | string                   | yes         | DER-encoded ECDSA, hexadecimal, per the rule below.                                                             |

No other field is permitted. A client MUST ignore a mark carrying an unrecognised field rather than rendering the part it understood, because a mark is the one place in this document where a stranger's bytes reach a renderer.

**What `value` holds, by kind.**

| Kinds                                 | `value`                                                                               |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| `palette`, `variant`, `glyph`, `dial` | An integer index into the set the score enumerates, or the integer itself for `dial`. |
| `position`                            | `[integer, integer]`, inside the score's region.                                      |
| `order`                               | An array of integers, a permutation of the score's set.                               |
| `string`, `dedication`                | A string, within the score's length and character set.                                |
| `kin`                                 | An origin, as `<txid>_<vout>`.                                                        |
| `curated`                             | A digest from the score's pool, 64 lowercase hexadecimal characters.                  |
| `keyprint`, `weight`                  | **Absent.**                                                                           |

`keyprint` and `weight` take no `value` because neither is chosen: a keyprint is derived from the marker's key per section 8.2, and a weight is derived from the satoshis destroyed. A mark object carrying a `value` for either is malformed.

**A `keyprint` needs no mark object at all.** Its value, its marker and its height are already in the lineage, so there is nothing left for an object to assert and nothing for a signature to add. A client derives keyprints from the lineage directly, subject to the eligibility rule of section 7.5 exactly as any other mark is. This is the only kind with that property, and it has it because it is the only kind the holder does not choose.

**Canonicalisation and the signature.**

1. A mark MUST contain only objects, arrays, strings and integers, as section 2.6 requires of a score, so that canonicalisation is the uncontentious part of RFC 8785.
2. The preimage is `SHA-256` over the RFC 8785 canonical form of the object **with `signature` removed**. This is [BRC-168](/apps/0168) section 3.1's construction and [BRC-169](/peer-to-peer/0169) section 7.2's: canonicalising rather than signing the transmitted bytes lets a relay or an indexer re-serialise the object without invalidating it, while any change to a value is detected.
3. The signature MUST be by the key the lineage shows as holding the work at the mark's height, verified per section 7.1(2). There is no separate marker field: the signer **is** the claim, and a field naming the marker would be a second, weaker copy of a fact the signature already carries.
4. A client MUST verify the signature before rendering, and MUST ignore rather than render a mark that fails. A mark whose signature does not verify is not a damaged mark, it is somebody else's.

**How each mechanism carries the object.** The mechanisms of section 7.4 differ only in which transaction the object rides in, and all of them carry it identically: as a [BRC-48](/scripts/0048) PushDrop output whose pushed fields, in order, are the ASCII string `derived-collectibles-mark`, the ASCII string `1`, and the canonical form of the object. For `on-acquisition` and `on-disposal` that output is in the transfer transaction; for `standalone` it is in a transaction of its own; for `co-signed` the object carries a second signature appended to the same field, by the counterparty's key; and for `commit-reveal` the commit output carries `SHA-256` of the canonical form in place of the object, and the reveal carries the object itself.

#### 7.3 What a mark may be

Every kind below resolves to a value from a set fixed at mint, which is what makes it renderable a century later and what stops it being a channel for arbitrary content. A score MAY offer more than one kind and MUST enumerate each.

| Kind         | The holder supplies                                                            | Example in a work                                                                      |
| ------------ | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `palette`    | One entry from a fixed palette                                                 | The colour of a light left burning in a window                                         |
| `position`   | Integer coordinates within a fixed region                                      | Where on the shore they left a footprint                                               |
| `variant`    | One option for a layer slot the score reserved for marking                     | Which shell is on the sand                                                             |
| `glyph`      | One symbol from an enumerated set                                              | A sigil cut into the rock face                                                         |
| `dial`       | An integer in a bounded range                                                  | How far the tide came in that year                                                     |
| `order`      | A permutation of a fixed set of elements                                       | The arrangement of stones in a cairn                                                   |
| `pairing`    | Two enumerated elements chosen together                                        | A bird and the branch it sits on                                                       |
| `string`     | A fixed-length string from a restricted character set                          | Initials scratched into a post                                                         |
| `dedication` | A [BRC-169](/peer-to-peer/0169) handle, resolved and displayed fully qualified | Who they held it for. A mark that names somebody else                                  |
| `kin`        | The origin of another work under the same score                                | A rope tied between two works, per section 6.4                                         |
| `curated`    | One digest from a pool the score enumerated at mint                            | An asset the artist prepared and the holder chose between                              |
| `keyprint`   | **Nothing.** Derived from the holder's own key per section 8.2                 | An involuntary mark: the shape of their key, which they did not choose and cannot pick |
| `weight`     | An integer derived from satoshis burned, per section 7.4                       | How deep the mark is cut, priced in something destroyed                                |

Two of these deserve a note because they are the interesting ones.

`keyprint` is a mark nobody chooses. It is a deterministic derivation from the holding key, so a holder cannot shop for a nicer one without changing keys, and it produces a work in which every holder has left a trace whether or not they wanted to. It is the closest thing here to a fingerprint on a handled object, and it is the only mark kind with no aesthetic decision in it at all.

#### 7.4 How a mark is made

The mechanism is what a client checks against the chain, so a score MUST name exactly one and a client MUST reject a mark made another way. The options are ordered from cheapest to re-derive to most demanding.

| Mechanism        | The holder does this                                                                                                                           | What it costs, and what it buys                                                                                                                                                                                                                |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `on-acquisition` | Carries the mark in the very transaction that transfers the work to them                                                                       | Cheapest to verify, since the mark and the transfer are one object. Forces the choice at the moment of purchase, before they know what the work will become.                                                                                   |
| `on-disposal`    | Carries the mark in the transaction that transfers the work away                                                                               | The parting mark. They decide what the work was to them at the moment they know, which is when they let it go.                                                                                                                                 |
| `standalone`     | Publishes a [BRC-48](/scripts/0048) PushDrop output referencing the work's origin, signed by the holding key, at any height while they hold it | One extra transaction. Lets a holder mark when they have decided rather than when the chain forced them to.                                                                                                                                    |
| `co-signed`      | Marks in the transfer, signed by both the outgoing and incoming holders                                                                        | Records a handover rather than a holder. Two parties agreed that this is what passed between them.                                                                                                                                             |
| `countersigned`  | Marks, and a later holder attests it with a [BRC-52](/peer-to-peer/0052) certificate                                                           | A mark somebody else vouched for, distinguishable from one nobody did.                                                                                                                                                                         |
| `burn-weighted`  | Accompanies the mark with satoshis paid to a provably unspendable output                                                                       | A costly signal in the strict sense: the amount is destroyed, so nobody profits from the mark existing. The construction is [BRC-146](/apps/0146) section 2.4's, and its warning applies: an output whose key may exist is a gift, not a cost. |
| `delegated`      | An agent marks under a [BRC-169](/peer-to-peer/0169) section 9 delegation whose scope names the marking action                                 | Lets a custodian or an agent mark for a principal, attributably to both. Subject in full to that document's caps and expiry.                                                                                                                   |
| `conversational` | Issues the mark through a chat command, as an ecosystem-custom verb under [BRC-218](/apps/0218) section 8                                      | The friendliest surface and the least specified. No verb is claimed or reserved by this document.                                                                                                                                              |

A score MAY additionally require a mark to be **timelocked**, in which case the marking output encumbers satoshis to the holder's own key until a stated height, and the mark is admitted only while that lock stands. This is the strongest available form and prices a mark in patience rather than in destruction, per [BRC-146](/apps/0146) section 4.5. Its costs transfer too: a locked marker cannot leave, and a term cannot be shortened.

#### 7.5 When a holder may mark

The eligibility rule is what stops a work being filled by its first owner. A score MUST name one.

| Rule              | Effect                                                                                                                                                                                             |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `once-per-holder` | One mark per distinct holder key. A returning holder gets no second mark, and a holder who splits across two keys gets two.                                                                        |
| `once-per-tenure` | One mark per period of holding, so a returning holder marks again. Rewards reacquisition, which section 6.2 notes is cheap.                                                                        |
| `tenure-gated`    | A mark is admitted only where the lineage shows tenure above a threshold at the marking height. The right to mark is earned by keeping the work rather than by buying it. The recommended default. |
| `slotted`         | The score reserves a fixed number of slots, filled in height order. When the last is filled the work is complete and can never be marked again.                                                    |
| `epoch-gated`     | At most one mark per `halvingEpoch`, or per any interval of section 4.2. A work that can only be marked a few times per century.                                                                   |
| `minter-opened`   | Marking is closed until the work first leaves its creator, per `firstHolderHolds` in section 6.1.                                                                                                  |

`tenure-gated` and `slotted` are the two that behave well over a long life, and for the same reason: both make marking scarce in a way a buyer cannot shortcut. An unrationed score is filled inside a week by whoever is most enthusiastic, and everything after that is a work with no room left in it.

#### 7.6 A mark is untrusted content

A mark is **content authored by a third party and rendered in every future viewer's client**, and MUST be treated as data.

1. A client MUST NOT interpret a mark as markup, script, a style, a font, a URL to fetch, a filesystem path, or an instruction of any kind.
2. A client MUST render a mark within the bounds the score set, and MUST clip rather than reflow. A mark that can change the layout of anything outside its slot can obscure the work.
3. A client MUST validate a mark against its declared kind before rendering, and MUST ignore a mark that does not conform rather than rendering a best effort at it.
4. A `string` mark MUST be checked against the score's character set, and a client MUST apply the confusability skeleton of [BRC-169](/peer-to-peer/0169) section 2.3 wherever a mark is displayed beside a name, since a `string` mark is the obvious place to impersonate somebody.
5. A `dedication` mark MUST be resolved and displayed fully qualified, and MUST NOT be presented as though the named handle consented to it. Being dedicated to somebody is a claim by the marker, not an act by the named party.

This is [BRC-218](/apps/0218) section 2.4's rule arriving in a different medium, and it is stricter here for one reason: a chat client that executes a hostile message can be fixed and the message deleted, while a work that executes what a previous holder wrote has that content on the chain permanently, in every wallet that renders it, for as long as the work exists.

### 8. Determinism

#### 8.1 Integers only

Every value in shared or local state MUST be derived in integer arithmetic. Floating-point arithmetic MUST NOT appear anywhere in a derivation.

The failure this prevents is silent and total. Two clients deriving the same position from the same state will agree on integers and will eventually disagree on doubles, and the disagreement appears as a work that is subtly different in two wallets with no way to say which is right. [BRC-146](/apps/0146)'s test vector for the same hazard is instructive: the bug is invisible in the cases where both answers agree, which is most of them.

Where a score needs a ratio, it MUST express it as a pair of integers and compare by cross-multiplication. Where it needs a curve, the curve belongs to presentation.

#### 8.2 The derivation

Pseudorandom values are derived from a seed by a construction every language can implement with a hash function and nothing else.

```
seed        = SHA-256( scoreDigest || origin || uint32BE(domain) )
stream(i)   = SHA-256( seed || uint32BE(i) )
```

where `origin` is the work's origin outpoint as 32 bytes of transaction identifier followed by `uint32BE(vout)`, and `domain` is a small integer the score assigns to each independent use, so that two uses of randomness in one score do not correlate.

An integer in the range `[0, n)` is drawn by rejection sampling: take successive 32-bit big-endian words from the stream, discard any word at or above `floor(2^32 / n) * n`, and return the first survivor modulo `n`. Rejection rather than a plain modulo is required because a plain modulo biases the low outcomes, and a biased rarity is a rarity nobody can reason about.

A `keyprint` mark (section 7.3) takes the holder's 33-byte compressed key in place of `origin`, which is what makes it involuntary: the holder supplies nothing and cannot search for a better outcome without changing keys.

Where a score draws from chain entropy rather than from the origin, `seed` takes the block hash at the stated height in place of `origin`, subject to section 4.5's depth, class and grinding rules.

#### 8.3 The lineage digest

A score MAY commit to the whole record with

```
lineageDigest = SHA-256( origin || t(1) || t(2) || ... || t(k) )
t(j)          = uint32BE(height) || ownerKey
```

over the work's transfers in lineage order up to the evaluation height, where `ownerKey` is the 33-byte compressed key receiving the work.

It is not a proof of anything and section 3.4 forbids calling it one. What it is for is the disagreement procedure: two clients rendering the same work differently compare lineage digests, and either they differ, in which case they followed different lineages and the argument is about which indexer is right, or they match, in which case the difference is in the derivation and one of the two has a bug. Without it, every disagreement looks the same.

#### 8.4 An indexer is a witness

Every quantity in section 6 is derived by following a chain of transfers, which is what an indexer does. A client MUST NOT treat any indexer as authoritative, SHOULD prefer one it operates or trusts, and MUST NOT accept a score's nomination of one as binding. A value the client took from an indexer without re-deriving it is `witnessed` per section 3.1, and MUST be rendered as such.

This is [BRC-146](/apps/0146) section 4.1's rule and the reasoning transfers exactly: two honest indexers agree because they are reading the same chain, so naming one buys convenience rather than agreement, and a score able to bind every viewer to one indexer would let whoever controls it decide what the work looks like.

An unconfirmed transfer is not a transfer. A client MUST NOT advance a record on an unconfirmed spend, and MUST class as `probable` any value derived from data shallower than `settlementDepth`.

### 9. What a Collector Is Shown

A work under this document is a schedule as much as a picture, and a buyer who is shown only tonight's picture has been shown the least informative view of it. A client offering a work for sale SHOULD show:

1. the reference rendering at the current height, with the height, per sections 2.3(4) and 2.8;
2. the confidence class of every property, per section 3.2, and in particular every `asserted` one;
3. which properties are shared and which are local, per section 2.3(2), and what the local ones would show in the buyer's own timezone;
4. which properties are derived from the clock and which from the record;
5. every `external` asset the score references, counted and named, per section 5.4;
6. the next height at which any property changes, and what it changes to;
7. whether any property is monotone, and therefore whether the work can return to a state it has left;
8. which quantities the score reads that are cheap to manufacture, per section 6.2;
9. what marks the work carries, who made them, and how many slots remain, per section 7;
10. whether any value was derived by search, and whether it was grindable when it was fixed, per section 4.5.
11. the work as it stood under each previous holder, per section 2.9, which is the part of a provenance a buyer can actually look at rather than read.

Points 6 and 7 are the ones most likely to be omitted and most likely to matter. A work whose patina deepens for ever is a work whose best-looking day was its first, and a buyer entitled to know that before rather than after is every buyer.

### 10. Not Specified Here

**Enforcement, which is reserved rather than merely absent.** Nothing here is enforced by consensus. A score is a rule that conforming clients honour, exactly as [BRC-146](/apps/0146) section 9 says of a gate, and a non-conforming client can render whatever it likes. That is sufficient for a work whose rules nobody gains by breaking, and insufficient wherever a state carries a payment, a royalty, or a mark whose absence somebody profits from.

The consensus-enforced form is therefore **reserved for a companion document**, in the manner of [BRC-218](/apps/0218) section 6: a name held open for a specification rather than a name held closed. [BRC-226](/tokens/0226) already demonstrates the mechanism, which is an `OP_PUSH_TX` covenant compelling the transaction that spends a work to re-create it under stated rules, and a companion would have to settle four things this document deliberately does not touch. Which parts of a score the covenant binds, since binding all of it makes every render a consensus matter and binding none of it changes nothing. How a mark is admitted by script rather than by client convention, per section 7.3. What happens to a work whose covenant cannot be satisfied, which is an object nobody can move. And what the arrangement costs per transfer, since a covenant is paid for by whoever spends it and a work too expensive to move is a work nobody moves.

A client MUST NOT describe a score as enforced, guaranteed, or unstrippable. Until that companion exists, every property in this document is derived by convention, and a collector told otherwise has been sold a promise the mechanism does not make.

**Sound, which is reserved rather than dropped.** An earlier revision specified it and the specification could not carry its weight: pitch, rhythm and duration are expressible as integer functions of state, and timbre is not, so a score whose identity depended on a particular synthesiser had an undeclared external dependency in the sense of section 5.4 and no vector could be written for it. Nothing in sections 2, 3, 4, 6 or 8 is specific to pixels, so a companion specifying generated audio needs no new derivation machinery: it needs a time base, a synthesis model exact enough that two implementations agree, and an answer to what a work sounds like on a device that refuses to play it. The `interval` and `tempo` mark kinds and the constructions that read them are reserved with it.

**Fractional ownership.** Per section 6.7.

**Oracles.** A score reading weather, price, or any other off-chain fact is outside this document. It would break section 2.2 outright: the state would no longer be recomputable from chain facts, every value downstream of it would be `asserted` per section 3.1(1), and a work whose 2126 state depends on a 2026 weather service is a work with an expiry date nobody wrote down.

**Renderer identity.** This document specifies what to derive, not what to draw with. Two conforming clients agree on state and may legitimately differ in typography, antialiasing, and synthesis. That is the third derivation, and Limitation 9 is about how far it goes.

**Estate and inheritance.** What becomes of a work whose holder's keys are lost is unaddressed. The work persists because the chain does; the record simply stops.

## What Is Derived, and What Is Only Asserted

The case for this document is made at the front and the concessions are distributed across sections 3, 6.2, 11, the Limitations and the Security Considerations. A collector deciding what to believe is entitled to see both totalled in one place, since that decision is the document's whole subject.

**What can be recomputed, by anybody, from public facts.** That a work's score is the one committed at its origin, because the digest is checked (2.5). That the score is of a stated type, because the type is derived from a printed string rather than assigned (2.7). That the score is the bytes the author wrote, because the canonical form is re-canonicalised and the digest recomputed rather than taken on the word of whoever served it (2.6). Every clock quantity, exactly, at any height, forever (4.2). Every time-based record quantity: age, tenure, the longest and briefest holdings, the count of transfers, of hands and of distinct keys (6.1). Every layer selection, palette, position and repetition the score defines, identically in every conforming client, because the arithmetic is integer and the randomness is a specified hash construction (5, 9.1, 9.2). That a mark was made by the holder of record at the height it claims, because the signature is checked against the lineage (7.1). And that two clients looking at the same work followed the same lineage, because the digests are comparable (9.3).

**What is witnessed rather than recomputed.** The lineage itself, unless the client walked it (8.4). Any resolution of an owner key to a handle, and therefore `ecosystems` (6.1) and the label on any retrospective view (2.9). Each is somebody's answer, each is labelled, and each fails soft: a client that cannot reach a witness reports that rather than substituting a guess.

**What is estimated.** Every conversion from blocks into human time. A duration in days, a date, a season. Exact in blocks, approximate in the units a viewer reads (4.1, 4.3).

**What is only asserted, and cannot be otherwise.** Price, and everything derived from it (6.2). Any host-supplied attribute. The claim that a work is beautiful, significant, treasured, or neglected, which is the fourth derivation and belongs to whoever is doing the looking (2.1, 6.6).

**What nothing here provides at all.** Enforcement (11). Confidentiality of who holds what, since the record is public by construction. Recovery of standing lost with a key. Any guarantee that the machinery to perform the work will exist (Limitation 9). And certainty, which the chain does not offer about its own history either (3.1).

The honest summary is narrow. A work whose state is derived is not a work you can be certain about. It is a work whose uncertainty is stated, apportioned, and checkable, which is the most any object on this chain has ever been able to offer, and considerably more than a stored picture with a table of transfers beside it.

## Limitations

Nine, and none of them is small.

**1. The metronome drifts.** Section 4.1 is a rule about expression and does not repair the underlying problem: a score written in blocks and read as calendar time will be wrong by a growing amount. A work whose "century" is 5,256,000 blocks will reach it early, and no version of this document can fix that without importing a clock. The `estimated` class states the error rather than removing it.

**2. Wall-clock resolution is about a day.** Median time past lags, and section 4.3 forbids finer use. Anything genuinely diurnal is therefore either chain-derived and offset from the sun, or local under section 2.3 and reproducible only from stated inputs.

**3. The timezone database is political and mutable.** Local state reads `utcOffset`, which is a function of rules governments change and sometimes change retroactively, so the same work at the same height in the same room may render differently in two different years. Stating the offset alongside the state (2.8) makes a past local state reproducible; nothing makes a future one predictable. A score whose most valuable states are local has accepted this, which is why 2.3(6) advises against it.

**4. Cheap provenance is cheap, and the confidence classes do not catch it.** Section 6.2 is a recommendation, not a defence, and section 3's classes are orthogonal to it: a wash-traded `hops` count is `settled` and worthless at the same time. One person with two keys and a fee budget can drive `hops`, `holders` and `returns` to any value, and there is no detection available, because the chain cannot see that two keys are one person. Works reading only time-based quantities are immune.

**5. Ownership is a key, not a person.** Everything in section 6 is about keys. A collector who rotates keys for good reasons appears as two holders and loses a tenure; one who consolidates two works into one wallet creates `kin` that never happened socially. [BRC-146](/apps/0146) section 8 works through the same problem for access and recovers part of it with attestations; nothing equivalent is specified here, and a score reading `holders` should expect the count to be wrong in both directions.

**6. Reorganisations change facts.** The `probable` class and the depth rules of 4.5 and 9.4 bound the exposure and do not remove it. A deep reorganisation would change a work's state retroactively, and the state a viewer saw beforehand would have been correct when they saw it and wrong afterwards. That is the chain's own condition, not this document's failure, and section 3.3 is the recommendation to show it rather than hide it.

**7. Price is mostly invisible.** Per section 6.2. Any construction reading value is reading a lower bound at best, which is why it is classed `asserted` and why a client that renders it like an age has misrepresented it.

**8. A work can be photographed.** The derived properties of a work are not enforceable against a screenshot, and for most viewers most of the time a screenshot is what they will see: in a listing, in a feed, in a search result. A still is the third derivation frozen and detached from its inputs, section 3.4 forbids treating it as evidence, and none of that stops it being what circulates.

**9. A performance is an approximation, and the machinery of performance rots.** The chain preserves the score and the inscribed assets and preserves nothing about what turns them into a picture or a sound. Codecs are abandoned, font formats superseded, colour spaces redefined, audio permission models rewritten, and the browser a work was written against will not exist. The right way to read this is not as a threat to the work but as the ordinary condition of the third derivation: every performance approximates its score, and a long work is one that gets re-derived repeatedly rather than one that never needs to be. *Longplayer* has been rebuilt more than once in a quarter of a century and is not thereby diminished. What follows is an obligation on the author rather than a caveat for the reader: section 9's integer arithmetic and the inscribing of rules rather than renders exist so that re-derivation is *possible*, and an author who wants the work performed in a hundred years should write a score somebody could implement from the score alone.

## Security Considerations

**Marks are attacker-supplied content rendered in every future viewer's client.** Section 7.6 is the whole of the mitigation and it is not optional. A client that interprets a mark as markup, script, or a URL has given every previous holder of a work a permanent foothold in every wallet that renders it, and unlike an ordinary injection there is no way to withdraw the content: the chain keeps it.

**A score is attacker-influenced input.** Anyone can inscribe a score. A client rendering one is executing a stranger's arithmetic against a stranger's assets, and MUST bound everything the score can ask for: the `repeat` counts of section 5.2, asset sizes, layer counts and the enumerated sets of section 5.2. A score that renders slowly enough is a denial of service against everybody who opens a marketplace page.

**External assets are a rug pull with a delay.** Section 5.4's digest detects substitution and cannot prevent disappearance. A work whose principal layers are `external` is a work its author can end by cancelling a hosting bill, and the collector's only protection is having been told which layers those were.

**Whoever performs a search chooses the result.** Section 4.5. A miner grinding a confirming block hash, or a minter grinding an origin outpoint by choosing inputs, sees the outcome before anybody else and may discard it. Any reveal worth more than the cost of one discarded attempt is a reveal that will be ground.

**Wash trading manufactures provenance.** Restating limitation 4 as a security property, because it is one: any score whose desirable state is reached by transferring has funded an attack on itself, and the attacker is often the holder. The same applies to section 3.3 if an unresolved state is made desirable, which is why 3.3(2) forbids it.

**A work that renders its holders publishes a wallet.** A composition showing `ecosystems`, resolving holders to handles, or drawing a figure per holder is a public statement about who owns what, assembled from facts that were individually public and are considerably more sensitive in aggregate. This is [BRC-146](/apps/0146)'s balance oracle in a different guise. A score naming holders MUST be understood as making that disclosure permanent, and a client SHOULD offer to render a work without holder identities.

**Indexer capture decides what the work looks like.** Section 9.4. An indexer that misreports a lineage changes the state every client relying on it derives, and the discrepancy is invisible to a viewer with one indexer. The `witnessed` class is what makes the dependency visible; comparing lineage digests per 8.3 is what makes it diagnosable.

**A stripped confidence class is worse than no class.** An interface that renders `asserted` and `settled` values alike has not merely lost information, it has laundered the weakest value into the authority of the strongest. Section 3.2(3) requires the class to survive export for this reason, and a client that shows classes in its own view and drops them at its API has built the failure it appears to prevent.

**Determinism failures are silent.** A floating-point derivation, a plain modulo, or an unbounded `repeat` produces plausible output almost always, so the bug ships. Section 9 is written as requirements for that reason.

## Implementations

None. This document is a specification ahead of its implementation and says so rather than describing a client that does not exist.

What can be said about implementability is narrower and more useful. Every read this document requires is already performed by deployed software: a 1Sat indexer following an origin per BRC-150 and BRC-156 is what any wallet displaying an ordinal already does, height and block hashes are available from any node or public API, and median time past is derived from eleven headers a client already holds under [BRC-9](/transactions/0009) style verification. Nothing here needs a new script, a new transaction format, or a new overlay topic, which was the criterion for including a mechanism rather than deferring it. The type identifiers of section 2.7 are computed rather than requested, so nothing needs registering either.

A minimal useful implementation is smaller than the document suggests. Sections 2, 3.1, 4.1, 4.2, 5 and 8 produce a work that evolves on height, deterministically, with stated confidence and no record reads at all, and that is the whole of the clock half. Section 6.1's `age` and `tenure` add the record half at the cost of one lineage walk. Section 3.3's resolution, marks, sound, merging and `kin` are each independently optional.

**Test vectors.** Appendix A carries them, and `verify_vectors.py` parses every printed value back out of this document and checks it against a fresh run of `example.py`: eighty-one checks, no third-party libraries, deterministic across runs. Among them: the canonical score re-canonicalises byte for byte; the rejection-sampling vector genuinely discards a word rather than merely claiming to; a property derived from a quantity that did not change between two heights did not change either; the class of `tenure` crosses `settlementDepth` between them; two `utcOffset` values render different waypoints; the signed mark of A.8 verifies against the public key as printed; and the same signature against a mark whose `value` was altered by one fails.

Four of the last kind are worth naming as a category, because they are the ones a vector suite usually omits. A vector that only demonstrates the happy path demonstrates nothing about an implementation that skips a check: the discard, the tamper, the held-still palette and the class transition each fail loudly on a plausible wrong implementation, which is the only reason to print them.

**What the vectors do not cover.** There is no renderer, so nothing here checks that two implementations draw the same picture from the same state, only that they derive the same state. The `co-signed` mechanism of section 7.4 is specified and not vectored, as is the burn behind a `weight` mark, and neither is the retrospective view of section 2.9, which needs a state derived at a height inside a past tenure. Sound is reserved to a companion under section 10 rather than specified here, so there is nothing owing for it.

## Appendix A: Worked Example

Every value below is computed from the rules in this document by `example.py`, and `verify_vectors.py` parses them back out of this document and checks them against a fresh run. An implementation producing a different `scoreDigest` from the same score, or a different state from the same height and lineage, has a bug, and its renders will not agree with anybody else's.

**These keys are published in a public specification and are therefore compromised by construction.** They are for conformance testing only.

### A.1 The type identifiers

Re-derived from the strings printed in section 2.7, which is the whole of their authority:

| Object | Derivation string               | `type`                                         |
| ------ | ------------------------------- | ---------------------------------------------- |
| Score  | `derived collectibles score v1` | `iDB79v3araIhb1GLVn21KzfM6n8KJHT2qvXn9hUswpo=` |
| Mark   | `derived collectibles mark v1`  | `4zYAcdXK54vxfdCPLF4mdYbDA7Ew97HDvIJOqVs68sw=` |

### A.2 The score

A reduced beach: four layers rather than section 5.1's eight, enough to exercise one operation of each kind, one local property, and both a chosen and a derived mark. Asset digests are `SHA-256("BRC-210 EXAMPLE ASSET / " + label)`, so the example is reproducible without shipping any artwork.

| Slot        | Locality  | Operation | Reads      | Effect                                        |
| ----------- | --------- | --------- | ---------- | --------------------------------------------- |
| `ground`    | shared    | `select`  | `season`   | One of four grounds                           |
| `celestial` | **local** | `place`   | `dayPhase` | The sun at one of three waypoints             |
| `surface`   | shared    | `palette` | `hops`     | Patina deepening across four palettes         |
| `chorus`    | shared    | `repeat`  | `holders`  | One figure per distinct holder, bounded at 16 |

Its `marks` object offers `kinds` of `palette` and `keyprint`, under the `standalone` mechanism and the `tenure-gated` eligibility rule at 4,032 blocks, with 12 slots and a seven-entry palette.

The RFC 8785 canonical form, 1712 bytes:

```
{"grid":[1024,640],"hemisphereDefault":"north","layers":[{"bands":[1,2,3],"locality":"shared","op":"select","reads":"season","slot":"ground","variants":[{"digest":"ecd1c4136cd224fb9ebd679a9911364fda892d9c3e74d6400e9468eb3c5d75bf","storage":"inscribed"},{"digest":"43ed804b66ad4eaa7f4159768df3040ed2ad272be9edfdbf5020ea1125579f79","storage":"inscribed"},{"digest":"e442c36bcc74d46514364c9ab24e55a53f3b3b8ae5d9875ca3943f4312d24543","storage":"inscribed"},{"digest":"1fd3d6a6a1acf9369fc6929e951e20e3b189149b41ac54e3cc57d04ebce73301","storage":"inscribed"}]},{"bands":[36,108],"locality":"local","op":"place","path":[[128,96],[512,48],[896,96]],"reads":"dayPhase","slot":"celestial","variants":[{"digest":"d8afc51a18adaf4c3b74fa12eea98aad5e627541f95c15b88e0db5565a6beeee","storage":"inscribed"}]},{"bands":[1,3,8],"locality":"shared","op":"palette","palettes":[["e8e2d0"],["d8cfb6"],["c2b697"],["a89a78"]],"reads":"hops","slot":"surface","variants":[{"digest":"ab0495f4ea270dd49cbfd22506762c3283289b6591ce671fb11215ff39dd7c48","storage":"inscribed"}]},{"bound":16,"divisor":1,"locality":"shared","op":"repeat","reads":"holders","slot":"chorus","variants":[{"digest":"227903df8672eaba5f5ed23c15defc0148739591a2dcaca0c62145417c4a6607","storage":"inscribed"}]}],"marks":{"eligibility":"tenure-gated","kinds":["palette","keyprint"],"mechanism":"standalone","palette":["ffd27f","9fd0ff","ffa8a8","b8f0c0","e0c4ff","fff4a8","c8c8c8"],"region":[64,480,960,600],"slots":12,"tenureBlocks":4032,"type":"4zYAcdXK54vxfdCPLF4mdYbDA7Ew97HDvIJOqVs68sw="},"protocol":"derived-collectibles","seasonBoundaries":["03-01","06-01","09-01","12-01"],"settlement":"resolve","type":"iDB79v3araIhb1GLVn21KzfM6n8KJHT2qvXn9hUswpo=","version":1}
```

```
scoreDigest = 842f23000912be13e568bf222c19461a0db588d038ac791aec13e652e4bb4bdf
```

That digest is what the minting transaction's PushDrop output carries, per section 2.6(3).

### A.3 The derivation of section 8.2

The work's origin is vout `0` of `0795cda147e624791aa48a1ba71d6e47a49c6d55a91b46a5cf4412e8037570c7`.

A weather layer drawing one of four states under domain `1`:

```
seed  = SHA-256( scoreDigest || origin || uint32BE(1) )
      = aafa95c911b5e08f518ce5b5d67de02d855ea7ff7fbb329b25efa0dde4d19a98
draw(4) = 1
```

**The discard path.** A realistic `n` rejects a few parts in four billion and no reachable vector would ever exercise it, so this one uses a deliberately hostile `n = 2500000000`, for which `floor(2^32 / n) * n` is `2500000000` and roughly two words in five are rejected. Under domain `6`:

```
seed              = e7d0befc9fe984dbd7ee22abb3cc98bcd85f3e46dfa0f1c765c07bc970249c9c
word 0            = 3829205201   rejected, at or above the limit
word 1            = accepted
draw(2500000000) = 331813804
```

An implementation that returns `word 0 % n` here, rather than discarding and drawing again, is biased and will disagree with every conforming client on this vector.

### A.4 The lineage

| Party | Label                     | Compressed public key                                                |
| ----- | ------------------------- | -------------------------------------------------------------------- |
| Alice | `BRC-210 EXAMPLE / alice` | `039b35f91f5cba7cc750808bb6757c929c4023e378aac343fc7a4e5734947f5a66` |
| Bob   | `BRC-210 EXAMPLE / bob`   | `035b455e935daba1dbe5051f4048319c617cf3b5b9b820f471c111cd8a4be50bfd` |
| Carol | `BRC-210 EXAMPLE / carol` | `036ff4e90b683b6bf9b94c0fddd7e41a88f0d3a586cb3346113dc5a49c3c4ebb41` |

Private keys are `SHA-256(label)` reduced mod `n`, as BRC-168 and BRC-169 do, so an implementer can regenerate them. The work is minted to Alice at height `900000` and transferred three times:

| Height | To                     |
| ------ | ---------------------- |
| 903600 | Bob                    |
| 918000 | Carol                  |
| 921590 | Alice, a reacquisition |

The lineage digest of section 8.3, over `uint32BE(height) || ownerKey` for each transfer in order:

```
lineageDigest = 6dd555fd41a03b270d8aa5725a7cd8d8268c30f4d9d3644c58a9bed4c0d8edf1
```

### A.5 The record and the state, at two heights

The median time past at `921600` is given as `1786752000`, which is `2026-08-15` UTC. Against the score's boundaries that is season index `1`, summer in the northern hemisphere, so the `ground` layer selects variant `1`.

| Quantity           | at 921600      | at 925632     |
| ------------------ | -------------- | ------------- |
| `age`              | 21600          | 25632         |
| `hops`             | 3              | 3             |
| `hands`            | 4              | 4             |
| `holders`          | 3              | 3             |
| `tenure`           | 10             | 4042          |
| `longestTenure`    | 14400          | 14400         |
| `shortestTenure`   | 10             | 3590          |
| `returns`          | 1              | 1             |
| `firstHolderHolds` | false          | false         |
| class of `tenure`  | **`probable`** | **`settled`** |

And the resolved layers:

| Layer     | at 921600        | at 925632        |
| --------- | ---------------- | ---------------- |
| `ground`  | variant 1        | variant 1        |
| `surface` | palette `c2b697` | palette `c2b697` |
| `chorus`  | 3 figures        | 3 figures        |

Three things in that pair are the reason it is the vector worth having. `hops` is `3` at both heights, so the patina palette does not move: a property derived from a quantity that did not change must not change either. `tenure` moves from `10` to `4042`. And the class of `tenure` moves from `probable` to `settled`, because the transfer at `921590` is `10` blocks deep at `921600` and `4042` deep at `925632`, which crosses `settlementDepth`. A score with `settlement` of `resolve` renders the first unresolved and the second settled, per section 3.3, from the same derived value.

### A.6 One local property, at two offsets

The `celestial` layer is declared `local` and reads `utcOffset`, converted to blocks at the ten-minute target by integer division, per section 8.1:

```
localDayPhase = ( h + utcOffset / 10 ) mod 144
```

| `utcOffset` | `localDayPhase` at 921600 | Waypoint | Position    |
| ----------- | ------------------------- | -------- | ----------- |
| `0`         | 0                         | 0        | `[128, 96]` |
| `600`       | 60                        | 1        | `[512, 48]` |

Both are the same work at the same height. The first is the reference rendering of section 2.3(4), because `utcOffset` is at its neutral value; the second is what a collector ten hours east sees. Each is reproducible by anybody told the offset, which is the whole of what section 2.3 asks, and neither is reproducible by anybody who was not, which is why section 2.8 requires the offset to travel with the state.

### A.7 A keyprint mark, which nobody signs

Alice's `keyprint` under section 7.3, which she does not choose and which needs no mark object at all, per section 7.2: her key, her height and her identity are already in the lineage. Her key stands in for the origin in the derivation of section 8.2:

```
seed  = SHA-256( scoreDigest || alicePubKey || uint32BE(3) )
      = 07389818f683d6441ec96bfd7ea4a24f4e8808c31f2f01be7db3c4253e6214a0
draw(7) = 6   ->   palette entry `c8c8c8`
```

She cannot shop for a different one without changing keys, which is the property that makes a keyprint a trace rather than a decision.

### A.8 A signed mark

Alice's `palette` mark, which she does choose, and which therefore is an object and is signed. The mark object of section 7.2, before signing:

```
{"kind":"palette","origin":"0795cda147e624791aa48a1ba71d6e47a49c6d55a91b46a5cf4412e8037570c7_0","protocol":"derived-collectibles-mark","type":"4zYAcdXK54vxfdCPLF4mdYbDA7Ew97HDvIJOqVs68sw=","value":3,"version":1}
```

211 bytes, canonical per RFC 8785, with `signature` absent because the preimage is the object without it. Note what is **not** in there: no height, because section 7.1(3) derives it from the carrying transaction, and no marker, because the signature is the claim.

```
preimage  = SHA-256( canonical form )
          = e74e09e758096f916de36d5ce0f86e7e0420d10552590c67073407c65c2ada61
signature = 3044022075d5fadc6e985335f55e70b5d2a1be803cb7f4bbc164a0f08b1e60208210b80302200b327916c6ec2ca3d0150ec97423332d3530d6448e3b14fb82560a497becd62a
```

Deterministic ECDSA per RFC 6979 over secp256k1, DER-encoded, low-S normalised, by Alice's key from A.4. It verifies against her public key as printed.

**The vector that matters is the negative one.** Change `value` from `3` to `2`, leave the signature alone, and verification fails. An implementation that renders the altered mark has skipped section 7.2(4), and the failure is invisible in every case where nobody has tampered with anything, which is almost every case.

## References

* [BRC-9: Simplified Payment Verification](/transactions/0009)
* [BRC-42: BSV Key Derivation Scheme (BKDS)](/key-derivation/0042)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-45: Definition of UTXOs as Bitcoin Tokens](/tokens/0045)
* [BRC-48: Pay to Push Drop](/scripts/0048)
* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-60: Simplifying State Machine Event Chains in Bitcoin](/state-machines/0060)
* [BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](/overlays/0087)
* [BRC-113: Merkle Proof Token](/tokens/0113)
* [BRC-145: Registry-Free Typed Content Anchor with On-Chain Code Provenance](/apps/0145)
* [BRC-146: Access Gates for Metanet Rooms](/apps/0146)
* [BRC-147: 1Sat Ordinals Basket Profile for BRC-46 / BRC-100](/tokens/0147)
* [BRC-150: 1Sat Provenance Remittance for Basket `1sat`](/tokens/0150)
* [BRC-156: Latched 1Sat Provenance for Basket `1sat`](https://github.com/bsv-blockchain/BRCs/tree/master/tokens/0156.md)
* [BRC-168: Verifiable Time Allocation](/apps/0168)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [BRC-218: Chat-Native Command Grammar for the Metanet](/apps/0218)
* [BRC-224: Block Media Format (BMF), Composable On-Chain Audio/Video](/apps/0224)
* [BRC-226: Miner-Enforced Resale-Royalty Covenant Tokens (OP\_PUSH\_TX)](/tokens/0226)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [RFC 6979: Deterministic Usage of DSA and ECDSA](https://www.rfc-editor.org/rfc/rfc6979)
* [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785)
* [BIP-113: Median time-past as endpoint for lock-time calculations](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki)
* S. Nakamoto, "Bitcoin: A Peer-to-Peer Electronic Cash System" (2008), section 11, on the probability of an attacker catching up from a given depth
* Ordinal Theory Handbook, "Rarity" (satoshi rarity from the issuance schedule)
* S. LeWitt, "Paragraphs on Conceptual Art", *Artforum* (1967), and the wall drawings as executed instructions
* V. Molnar, G. Nees and M. Mohr, algorithmic composition from the 1960s
* B. Eno, *Discreet Music* (1975) and *77 Million Paintings* (2006)
* Art Blocks, hash-seeded immutable generative works; Async Art, independently owned mutable layers; EulerBeats, seed-derived audio; Terraforms by Mathcastles, on-chain state advancing on block progression
* J. Cage, *Organ2/ASLSP* (1987), in performance at Halberstadt since 2001 on a 639-year schedule
* J. Finer, *Longplayer* (1999), a thousand-year composition, and its documented migrations between performing systems
* K. Paterson, *Future Library* (2014 to 2114)
* Namco, *Katamari Damacy* (2004)


# Chat-Native Command Grammar for the Metanet

Crumbs, Deggen, BrandonC, Luke Rohenaz

## Abstract

This document specifies a slash-command grammar for conversational Metanet clients, and a global set of command verbs that MUST behave identically in every conforming client regardless of ecosystem.

Commands are the chat-native surface over the addressing, payment, messaging, reachability, and delegation mechanics of [BRC-169](/peer-to-peer/0169). `/pay @brandon@handcash $2.18` performs a BRC-169 section 6 payment; `/trolltoll 300 sats` sets a BRC-169 section 8 policy. This document defines the syntax, the parsing and confirmation rules, and the exact behavior of each verb. It defines no new cryptographic or wire mechanics.

It also reserves a set of verbs for commands that are anticipated but not yet specified, so that ecosystems cannot claim them, and defines how an ecosystem advertises its own custom commands without colliding with the global set.

## Motivation

The interface through which people use the Metanet is converging on chat, and two properties make a standardized command grammar worth writing down.

**Agents read and write the same grammar.** An agent that can parse `/pay @brandon@handcash $2.18` can also emit it. A slash command is simultaneously a human-readable interface and a machine-parsable API, which is what an agent-mediated decade requires. That dual role is also a hazard: text that arrives from a counterparty must never be executable, and an agent acting for a user must be bound by verifiable authority rather than by convention. Both are addressed here.

**Identical parsing is what makes commands portable.** If `/pay` means one thing in one client and something else in another, the grammar is worthless as an interoperability layer, and worse than worthless as a target for agents. Reserving the global verbs and fixing their meanings in a versioned document is the whole contribution.

A note on scope. Commands requiring a multi-party protocol or an on-chain construct that BRC-169 does not define are reserved in section 6 rather than described loosely. "Every client MUST parse this identically" is not a meaningful requirement for behavior that has not been specified, and a verb sketched in a sentence invites divergent implementations with funds at stake. Reserving the name costs nothing and keeps it available for a specification that does the work.

## Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119.

Throughout, "resolve" means the client resolution algorithm of [BRC-169](/peer-to-peer/0169) section 5.7, and "value-moving action" has the meaning given in BRC-169 section 1.

### 1. Terminology

* **Command**: a line of user input beginning with `/`, parsed per section 2.
* **Global verb**: a verb defined in section 5, or reserved in section 6. Global verbs are reserved across all ecosystems.
* **Custom verb**: a verb defined by a single ecosystem and advertised per section 8.
* **Thread**: the conversation in which a command is issued. A command issued in reply to a specific message is **bound** to that message.

### 2. Grammar and Parsing

```abnf
command   = "/" verb *( 1*SP arg )
verb      = alnum [ *30( alnum / "-" ) alnum ]      ; 1 to 32 characters
arg       = recipient / amount / duration / period / quoted / word
recipient = <the recipient rule of BRC-169 section 2.1>
amount    = fiat / sats
fiat      = [ iso4217 1*SP ] "$" 1*DIGIT [ "." 1*2DIGIT ]
          / iso4217 1*SP 1*DIGIT [ "." 1*2DIGIT ]
sats      = 1*DIGIT 1*SP ( "sat" / "sats" )
duration  = 1*DIGIT ( "m" / "h" / "d" )              ; minutes, hours, days
period    = "/" ( "day" / "week" / "month" )
quoted    = DQUOTE *( %x20-21 / %x23-5B / %x5D-7E / "\" DQUOTE ) DQUOTE
word      = 1*64( %x21-7E )                          ; no whitespace
iso4217   = 3UPPER
alnum     = lower / DIGIT
lower     = %x61-7A
UPPER     = %x41-5A
```

1. A line beginning with `/` is a command. A line beginning with `//` is chat whose first character is a literal `/`, with the leading `//` reduced to `/` before display. Any other line is chat.
2. Verbs are case-insensitive and MUST be normalized to lowercase before dispatch. Arguments are case-sensitive except where a referenced specification requires normalization.
3. Where a command takes free text as its last argument, that argument extends to the end of the line and is not subject to the `word` rule.
4. **A client MUST parse commands only from input composed locally by its own user.** Text received from a counterparty, whether in message content, a display name, a memo field, an attachment, or a search result, MUST NOT be parsed or executed as a command under any circumstances, and MUST be rendered as literal text. See Security Considerations.
5. A client encountering a verb it does not implement MUST report it as unsupported. It MUST NOT execute a different command, and it MUST NOT reassign the verb's meaning.
6. Parsing MUST NOT have side effects. A command takes effect only after the confirmation rules of section 4 are satisfied.
7. **Token boundaries are whitespace, not `@`.** A fully-qualified recipient contains two `@` characters (BRC-169 section 2.1). A client MUST find the start of a recipient token by scanning back to the preceding whitespace or the start of input, not to the nearest `@`. Scanning to the nearest `@` truncates the token the moment the user types the ecosystem separator, which is precisely when autocomplete is most useful.
8. Where an input could be read as either a handle or a paymail address, a leading `@` decides: with one, the token is a handle per BRC-169 section 2.1; without one, it MAY be treated as paymail per BRC-169 section 2.1(7).

### 3. Amounts

1. `21545 sats` denotes exactly 21,545 satoshis.
2. `$2.18` denotes 2.18 USD. Other currencies use an ISO-4217 prefix, as in `CHF 5`.
3. Fiat amounts MUST be converted at send time through the oracle interface of BRC-169 section 6.2, subject to its staleness bound, disclosure rules, and audit-trail requirements.
4. A client MUST display the satoshi amount alongside the fiat amount in the confirmation of section 4 for any fiat-denominated command.
5. A client MUST reject a fiat amount it cannot convert. It MUST NOT substitute a stale rate, a cached rate beyond its validity, or a rate from an undisclosed source.
6. The `fiat` rule permits at most two decimal places. A client MUST reject a finer fiat amount rather than rounding it, and SHOULD direct the user to satoshis, which have no such limit. Examples elsewhere in this document are held to the same rule.

#### 3.1 Token-denominated amounts

Ecosystems issue tokens, and a payment in one is a different operation from a BSV payment rather than a formatting variant of it. A client MAY accept a token amount:

```abnf
amount    =/ token
token     = 1*DIGIT 1*SP symbol
symbol    = 1*12( ALPHA / DIGIT )
```

1. The symbol MUST resolve against a token list the client already knows. An unrecognised symbol MUST NOT be treated as an amount; it falls through to free text, so that `3 nutri` and a memo beginning "nutri" cannot be confused.
2. The confirmation MUST state that the transfer is of a token and not of BSV.
3. Any fiat equivalent shown for a token amount MUST be marked indicative. A token is worth what its issuer and its market say it is, and a client quoting a rate for one is making a claim it cannot support.

### 4. Confirmation and Execution

1. Before executing any command that moves value, issues or revokes a certificate, or changes a reachability policy, a client MUST present a structured confirmation showing at minimum: the verb, the fully-qualified recipient where one applies, the amount in both satoshis and the typed fiat where applicable, and a plain statement of the effect.
2. **A command that does none of those MUST NOT require a confirmation.** A lookup moves nothing, sends nothing, and is not disclosed to the handle being looked up; there is nothing for the user to consent to. Asking anyway is not a free precaution: a client that puts the same sheet in front of a read and a payment teaches the user to dismiss it, and the dismissal habit is carried to the sheet that mattered. `/whois` and `/help` are the clear cases.
3. The confirmation MUST show the recipient as `@handle@domain.tld` in fully-qualified form, never as an alias alone, and MUST reflect the display and confusability rules of BRC-169 sections 2.3 and 2.4.
4. Clients MUST offer autocomplete for verbs and recipients. Autocomplete MUST NOT substitute a recipient the user did not select, and a completed recipient MUST remain visible and editable before confirmation. Accepting a suggestion MUST insert the fully-qualified handle, which is what makes the no-substitution rule observable. A suggestion list MAY display the bare handle: in a single-ecosystem thread every row otherwise ends in the same suffix, and the list becomes harder to read rather than safer.
5. **Argument navigation is not substitution.** A client MAY offer keyboard navigation between a command's argument positions — the grammar is positional, and a user filling in `<recipient> <amount> [memo]` will reach for a key that moves between them. Doing so MUST NOT alter the command text. Only an explicit accept per rule 4 may insert anything. This is easy to violate without noticing: where the navigation key is also bound to an open suggestion list, pressing it completes the token under the caret instead of moving past it, and the command that executes is not the one on screen.
6. Resolution failures, revoked handles, and key changes MUST be surfaced at confirmation time and MUST block execution of a value-moving command until acknowledged, per BRC-169 sections 4.4 and 5.3.
7. **Non-interactive execution.** An agent or automation executing commands on a user's behalf without per-command human confirmation MUST hold a delegation certificate per BRC-169 section 9 covering each verb it executes, and MUST remain within that certificate's scope, caps, and expiry. A client MUST NOT offer a mode that suppresses confirmation without a corresponding delegation, and MUST NOT treat a suppressed confirmation as raising the limits in the certificate.
8. **Disclosure of automated participants.** Where an agent acts in a conversation under a delegation, the other participants cannot tell from the messages alone whether they are addressing the person or the automation. A client that supports non-interactive execution MUST provide a way for the delegating user to declare in the conversation that an agent is acting for them, and what its scope and expiry are. The delegation itself is between the user and the agent; the participants' need to know who they are dealing with is not served by it. The declaration mechanism is left to the ecosystem, which MAY define a custom command for it under section 8.
9. A command bound to a message applies to that message. Where a command requires a binding and none exists, the client MUST report the error rather than apply the command to the thread's most recent message.
10. A client that offers bound verbs MUST provide a way to establish the binding that does not depend on hover. A reply affordance revealed only on pointer hover makes every bound verb unreachable on a touch device.
11. **Actions offered on a command's result.** A client MAY offer actions on the record of a completed command — lifting a toll it set, cancelling a subscription it started, revoking a certificate it issued, paying a request it made. Where such an action moves value, it MUST route through the confirmation of 4.1; a control inside a popover or a notification is not a structured confirmation. Where it only reverses standing state and moves no value, it MAY execute directly. Either way the action SHOULD be recorded in the conversation as its own command, so that the change is visible to the participants rather than happening silently.

### 5. The Global Command Set

The verbs in this section are reserved across all ecosystems and are versioned by this document: additions and changes happen by revision. Every conforming client MUST parse them identically. A client MAY decline to execute a verb it does not support, but MUST NOT reassign its meaning.

#### 5.1 `/pay`

```
/pay <recipient> <amount> [memo]
```

Resolve the recipient and execute a BRC-169 section 6.1 payment. `memo` is free text and MUST be carried in the encrypted envelope content, not in envelope metadata.

Where the recipient has a toll in force, the client MUST follow BRC-169 section 8.3, quoting before sending and attaching the toll in addition to the payment amount. The toll and the payment are distinct amounts and MUST be shown separately at confirmation.

#### 5.2 `/message`

```
/message <recipient> <text>
```

Send `text` to the recipient as a BRC-169 section 7.2 envelope. Equivalent to plain chat once a thread exists; the explicit form exists for cross-ecosystem first contact and for agents.

#### 5.3 `/request`

```
/request <recipient> <amount> [memo]
```

Send a payment request. The recipient's client MUST render it as an actionable item that, on acceptance, executes `/pay` back to the requester for the stated amount, and MUST re-derive the amount at acceptance time where it was fiat-denominated, showing both the original and current satoshi values.

A request confers no authority. It MUST NOT cause any transfer without the recipient's explicit confirmation, and a client MUST NOT allow a request to be auto-accepted by any policy short of a delegation certificate scoped to `pay`.

#### 5.4 `/tip`

```
/tip [amount]
```

A `/pay` bound to the message being replied to, addressed to that message's verified sender. The binding MUST be recorded in the envelope content so the recipient can attribute the tip. Where `amount` is omitted the client SHOULD offer a configured default, and MUST still confirm per section 4.

#### 5.5 `/split`

```
/split <recipient> 1*( 1*SP <recipient> ) <amount>
```

Divide `amount` among the named recipients and send each leg as an independent BRC-169 section 6.1 payment. Recipients MAY be in different ecosystems.

1. Division is equal by default. Clients MAY offer custom weights through their own interface, but the grammar above is what MUST be parsed identically.
2. Rounding MUST be deterministic: divide into integer satoshis, and allocate any remainder one satoshi at a time to recipients in the order given.
3. **Legs are independent and failure is partial.** A resolution or delivery failure on one leg MUST NOT roll back the others. The client MUST report per-leg outcomes and MUST NOT report the command as succeeded unless every leg succeeded.
4. All recipients MUST be resolved and displayed at confirmation before any leg is sent. Clients SHOULD warn when a split exceeds a configured number of legs, since fan-out multiplies both cost and disclosure.

#### 5.6 `/subscribe`

```
/subscribe <recipient> <amount> <period>
/subscribe <recipient> "off"
```

Establish a standing payment executed by the **sender's** wallet on the stated period, or end one already running.

1. This grants the recipient **no pull authority**. No certificate is issued, nothing is delegated, and the recipient cannot initiate. Each execution is an ordinary `/pay` performed by the sender's own wallet.
2. The subscription is cancellable at any time, and cancellation MUST take effect before the next scheduled execution without requiring the recipient's cooperation. The `off` form is the interoperable way to express this: without it, every client invents its own gesture and the counterparty learns nothing. Cancellation ends future executions only and reverses nothing already sent.
3. Before each execution after the first, the client MUST re-resolve the recipient and apply the key-change rule of BRC-169 section 4.4, suspending the subscription and requiring confirmation if the identity key has changed.
4. Where the amount is fiat-denominated it MUST be re-converted at each execution. The client MUST disclose at setup that the satoshi amount will vary, and SHOULD allow a satoshi ceiling above which execution pauses for confirmation.
5. A client MUST maintain a visible list of active subscriptions with their next execution time.

#### 5.7 `/whois`

```
/whois <recipient>
```

Resolve and display the attested identity without transacting: handle, domain, identity key, certificate status including the revocation check and the time it was performed, messagebox, whether the identity is in the user's address book, whether its key has changed since it was added, and any peer attestations per BRC-169 section 10.

1. `/whois` on a subhandle MUST return the base handle's identity, noting the queried tag, per BRC-169 section 3.1.
2. Unverified, host-supplied attributes such as a display name or avatar MUST be labeled as unverified.
3. `/whois` MUST perform a fresh resolution rather than answering from cache, and MUST report the age of the revocation check it relied on.
4. **The result belongs in the conversation.** A resolution presented only in a panel outside the thread turns the command into a navigation step and leaves the conversation with no record of what was resolved — which is the case people most often run it for, showing someone else who a handle belongs to. A client SHOULD render the outcome inline where the command was issued, with the identity key, the resolving domain, and the certificate state legible without a further click.
5. **Resolution is not instantaneous, and SHOULD NOT be drawn as though it were.** A handle is resolved over the network. A client that paints a complete answer in the same frame the command was issued teaches its user that resolution is free and always succeeds, which is the belief that makes a stale binding or a substituted key easy to miss later. Rule 3 already requires reporting the age of the revocation check the answer relied on.

#### 5.8 `/attest`

```
/attest <recipient>
```

Publish a peer attestation of the recipient's handle to key binding, per BRC-169 section 10.3. The client MUST require a fresh, successful `/whois`-equivalent verification immediately before issuing, and MUST make clear at confirmation that the attestation is a public, signed statement that others may rely on.

#### 5.9 `/scope`

```
/scope ( "everyone" / "contacts" / "ecosystem" / "toll" ) [ <recipient> ]
```

Set the reachability scope defined in BRC-169 section 8.1. The optional recipient argument, which MUST be a subhandle of the user's own handle, scopes the setting to that tag.

The command changes policy at the user's own messagebox. A client MUST confirm that the change was accepted by the messagebox before reporting success, and MUST warn that the messagebox, not the client, is the enforcement point.

#### 5.10 `/trolltoll`

```
/trolltoll [ <recipient> ] ( <amount> / "off" )
```

Set, change, or lift the per-message toll defined in BRC-169 section 8.2. With a recipient, the toll applies to that sender; without one, to everyone in scope. A recipient that is a subhandle of the user's own handle sets a per-tag toll.

1. The toll is paid to the user, is due for every message each time, and is not refunded on reply.
2. Setting a toll MUST NOT retroactively apply to quotes already issued and still valid, per BRC-169 section 8.3.
3. `off` lifts the toll for the named sender, or for everyone where no sender is named. Lifting a general toll does not lift per-sender tolls, and the client MUST say so.

#### 5.11 `/delegate`

```
/delegate <recipient> [scope] [amount] [duration]
```

Issue a delegation certificate to the recipient per BRC-169 section 9.1, where `scope` is a BRC-169 section 9.2 scope string, `amount` sets `fields.perActionCap`, and `duration` sets `fields.expiry`.

The confirmation MUST state, in plain language: which verbs are being granted, the per-action cap, the expiry, whether onward delegation is permitted, and, where a cumulative cap is set, **whether it is actually enforced** per BRC-169 section 9.3.3. A client MUST NOT describe a cumulative cap as guaranteed unless a principal-funded arrangement is in place.

Issuing `*` scope MUST require an additional, distinct confirmation.

#### 5.12 `/revoke`

```
/revoke <recipient> [serial]
```

Revoke a delegation certificate the user issued, by spending its revocation outpoint. Where the user has issued more than one certificate to the recipient and no serial is given, the client MUST list them and require a selection rather than guessing.

The client MUST report that revocation is detectable rather than instantaneous, and is subject to the indexing lag described in BRC-169 section 4.2. It MUST NOT report the delegate as unable to act until the spend is confirmed.

#### 5.13 `/handoff`

```
/handoff <recipient> [amount] [duration]
```

Issue a delegation certificate bound to the current thread, per BRC-169 section 9.7, with scope qualified by `thread:<id>` for the thread in which the command is issued.

The confirmation MUST state the thread being delegated, the per-action cap, and the expiry. Where `amount` is presented to the user as a spend limit, the client MUST apply BRC-169 section 9.3.3 and state explicitly whether the limit bounds total spend or only each individual action. A client MUST NOT use the words "hard cap", "maximum", or equivalent for a cumulative limit that is not principal-funded.

#### 5.14 `/sign`

```
/sign [text]
```

Sign content with the user's identity key, producing a [BRC-3](/wallet/0003) signature over its canonical hash, delivered into the same thread.

The binding is **optional**, and decides what is signed:

1. **Bound** — issued in reply to a message, the signature covers that message's content as the signer received and displayed it. This is a countersignature.
2. **Unbound** — issued on a message the user is composing, the signature covers that message: its text and **every attachment carried with it**. With no attachment it covers the text alone.

Further requirements:

3. A client MUST show the exact content being signed at confirmation, including an enumeration of the attachments covered.
4. The result MUST state what the signature covered. "Signed" over a message and "signed" over a message and four files are different claims and a reader cannot distinguish them from the word alone.
5. Signing an unbound message requires that its attachments exist on the draft before it is sent. A client that posts an attachment the moment it is selected cannot offer this form at all, because there is no composed message for a signature to cover.
6. Verifiers MUST check the signature against the signer's resolved identity key, per BRC-169 section 5.7.
7. Multi-party document signing, counterparty ordering, and on-chain anchoring of a completed document are **not** specified here. See section 6.

#### 5.15 `/receipt`

```
/receipt
```

Request a signed acknowledgment for the message being replied to, or for the user's most recent message in the thread where the command is unbound. The counterparty's client, if it honours the request, returns a BRC-3 signature over the message hash together with a timestamp.

A receipt is voluntary. No client is obliged to honour a request, absence of a receipt means nothing, and clients MUST NOT present a missing receipt as evidence that a message was not delivered or not read.

#### 5.16 `/help`

```
/help [command]
```

List the commands the client supports, or describe a single command where one is named.

1. A conforming client MUST implement `/help`. It is the only command a user can issue without already knowing the grammar, which makes it the entry point to everything else; a grammar discoverable only by reading this document is not discoverable.
2. The listing MUST distinguish verbs the client will execute from those it reports as unsupported, covering both section 2.5 declinations and the reserved verbs of section 6. A user who cannot tell "this client does not do that" from "nobody does that" has been told very little.
3. Custom verbs (section 8) MUST be listed as belonging to their ecosystem rather than to this document.
4. **The argument is a command name**, and clients MUST accept it with or without the leading slash. A reader who has just been told to type `/pay` will type `/help /pay`. Calling the argument a *verb* in the prompt, or reusing the free-text placeholder for it, tells a user who does not know the grammar to type the wrong thing.
5. **Order.** The listing MUST lead with the commands runnable in this ecosystem, custom verbs (section 8) before the global set, and MUST place declined and reserved verbs after both. The order answers "what can I do here" before "what exists but not here". Section 5's own order is not a listing order: it puts the verbs a user can actually run last.
6. **One line per command is not sufficient.** A summary that fits beside a grammar string cannot also carry the behaviour a user needs before running the command — that a per-action cap is enforced by the counterparty and a cumulative one generally is not, or that lifting a general toll leaves a per-sender toll in force. The listing MUST make a fuller description reachable for each command, and SHOULD keep the list itself scannable rather than expanding every description at once.
7. `/help` is answered locally. It MUST NOT be transmitted, and its reply MUST be presented per section 9.

#### 5.17 `/refund`

```
/refund [amount]
```

Return a payment, bound to the payment it returns.

1. `/refund` MUST bind to a message carrying a completed payment, per section 4.9.
2. Nothing reverses on chain, so a refund is a **new payment in the opposite direction**. The client MUST carry a machine-readable reference to the payment being returned. A memo is not sufficient: section 2.4 forbids executing received text, so a reference a counterparty's client can act on cannot be prose.
3. Omitting the amount returns the full amount of the bound payment. A smaller amount is a partial refund and MUST be shown as such at confirmation, stating both figures; "refund" implies the whole of it, and often is not.
4. **Only the party that received a payment can refund it.** A client MUST refuse `/refund` bound to a payment the user sent, and SHOULD point at `/request`, which is what asking for money back actually is.
5. A recipient's client SHOULD mark the original payment as refunded on receiving one that references it, in whole or in part.

#### 5.18 `/cancel`

```
/cancel
```

Withdraw a payment request you sent.

1. `/cancel` MUST bind to a message carrying a `/request` (section 5.3) that the user sent. A client MUST refuse to cancel a request it did not send.
2. A request creates an obligation in the recipient's client that section 5.3 gave no way to discharge except paying it. Without a defined withdrawal the sender's only remaining move is to ask repeatedly, and every client invents its own gesture, which the counterparty cannot read.
3. On cancellation the recipient's client MUST stop presenting the request as owed. Nothing moved, so nothing is returned, and a client MUST NOT present a cancellation as a payment or a refund.

#### 5.19 `/standing`

```
/standing
```

List everything still acting on the user's behalf.

1. A conforming client MUST implement `/standing`, and MUST list at minimum: delegation certificates issued and not revoked, active subscriptions, tolls in force, and the current reachability scope.
2. Every entry MUST state its bounds — scope, cap, and expiry — rather than only naming the thing. An entry without them is a claim that no bound exists, which for a certificate is the most dangerous thing it could fail to say.
3. Authority that has **lapsed or been revoked** SHOULD be listed and labelled as such. "It expired" and "it was never issued" are different answers to the same question, and only one of them means the user remembered correctly.
4. `/standing` moves nothing and is answered locally. It MUST NOT be transmitted, and its reply MUST be presented per section 9.
5. This section exists because everything it lists keeps acting **without asking again**. A grammar that hands out standing authority and never requires a way to enumerate it leaves users holding authority they would revoke if they could see it. The prior art is the authorized-applications list every OAuth provider was eventually obliged to ship.

#### 5.20 `/send`

```
/send <recipient> #asset
```

Transfer a non-fungible asset the sender holds.

1. `/send` moves the **thing**, not an amount. Clients MUST NOT treat it as a variant of `/pay`: the two answer different questions and a client that renders them alike will eventually let someone confirm the wrong one.
2. An asset is named by a `#reference` that a person can type from looking at it. A client MUST resolve the reference against assets the sender **actually holds** and MUST refuse an unresolved one rather than guessing at a near match.
3. The confirmation of section 4.1 MUST show the asset's **artwork and its serial**, not only its name and id. A collectible is identified by looking at it, and confirming against an identifier asks the user to verify from the label on the box — which is the check people skip.
4. The record left in the conversation MUST identify **both** parties, not only the recipient. "Sent to Randy" is ambiguous the moment it is quoted, forwarded, or read by somebody who joined the room afterwards.
5. Where the transfer settles on chain, the record SHOULD link to the transaction. An asset transfer is the case where a reader most wants to check for themselves.

#### 5.21 `/escrow`

```
/escrow <agent> [#asset] [amount] <duration>
```

Commit one side of a trade to a named agent for a bounded window.

This section claims a verb reserved by section 6, which anticipated exactly that: each reserved verb is a candidate for its own BRC that extends this document by claiming its name. It claims the **named-agent** case only. Arbitration, dispute resolution and script-enforced release remain unspecified and out of scope; see the note at the end.

1. Each party commits **one side**: the asset, or the payment. A commitment naming neither is not a side and MUST be refused.
2. An escrow forms when two sides name the **same agent**, carry complementary halves, and agree on the amount, before either window closes.
3. **Pairing MUST be deterministic.** Where more than one open side could match, a client MUST pair the **earliest** unmatched one, and MUST show the committer which side it paired with. Two offers of the same amount to one agent are otherwise indistinguishable, and an agent left to guess which payment answers which asset will eventually guess wrong.
4. **Two commands mean two clocks.** The escrow lives by the **earlier** of the two windows. A side whose window closes unmatched lapses alone, and nothing moves.
5. The agent's acceptance moves value and therefore MUST route through the confirmation of section 4.1. A control on a card is not a structured confirmation, and this is the point at which two other people's property becomes the agent's responsibility.
6. Every state change — pairing, acceptance, refusal, lapse, release — MUST be reported in the conversation. The parties cannot see the agent's client, and an escrow that changes state silently is one where the only person who knows is the one holding everything.
7. **The client MUST state what it does not guarantee.** Nothing here is arbitrated: the agent holds both halves and can keep them. A client MUST say so, in those terms, before either side commits, and for as long as the statement is true. It MUST stop saying it once the escrow has settled — a released escrow claiming nothing has moved is worse than saying nothing.
8. A client SHOULD show the agent's standing under BRC-169 section 10 at the point of commitment. The agent's reputation is the only bond in this arrangement, which makes it the one fact a committer most needs and the one they are least likely to go and look up.
9. Controls addressed to the agent MAY be shown only to them, but a client MUST NOT present this as enforcement. Visibility inside a shared room is a courtesy of the rendering client; what constrains the agent is that they are the party the other two named.

**Deliberately unspecified.** A dispute path, an arbiter with a defined role beyond custody, partial release, and any script that removes the need to trust the agent. Each needs an on-chain construct BRC-169 does not provide, and specifying a trusted-agent escrow does not make an arbitrated one out of scope for a later document — it narrows what that document has left to settle.

### 6. Reserved Verbs

The following verbs are **reserved and MUST NOT be assigned by any ecosystem**, but are not specified by this document. `/escrow` was among them until section 5.21 claimed it, which is the route this section intends: a reservation is a name held open for a specification, not a name held closed. A conforming client MUST report them as unsupported rather than implementing local behavior under these names.

| Verb        | Intended purpose                               | What a specification must settle                                                                                                                                                                                        |
| ----------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/bounty`   | An open, claimable payment addressed to a room | Claim submission and adjudication, protection against front-running a claim, and release or expiry of unclaimed funds                                                                                                   |
| `/poll`     | Payment-gated voting                           | Vote aggregation, the auditability claim, protection against the poll operator discarding votes, and refund or forfeit of vote payments                                                                                 |
| `/gate`     | An entry fee for a room or channel             | A room and membership model, which BRC-169 does not define, plus fee custody and split policy. `/gate` is the *write* half — charging for entry. Section 11 specifies the read half, which needs no verb and no custody |
| `/contract` | Multi-party document signing and anchoring     | Party enumeration, signing order, partial-signature state, the canonical document hash, and the anchoring transaction format                                                                                            |

Each of these requires either a multi-party protocol or an on-chain construct that BRC-169 does not provide. Each is a candidate for its own BRC, which would extend this document by claiming its reserved verb.

### 7. Precedence

1. Global verbs, whether specified in section 5 or reserved in section 6, are reserved across all ecosystems. A custom verb MUST NOT shadow one.
2. Where a collision occurs, including where a future revision of this document promotes a verb that an ecosystem had already claimed, clients MUST resolve it in favour of the global meaning and the ecosystem MUST rename its custom command.
3. A client MUST NOT allow a user, a configuration file, or a plugin to rebind a global verb to different behavior. Aliases that expand to a global verb are permitted; redefinitions are not.

### 8. Ecosystem-Custom Commands

An ecosystem MAY define additional verbs and advertise them in the `metanet.handles.commands` array of its manifest (BRC-169 section 5.1), so that foreign clients can surface them contextually or explain why they cannot.

```json
{
  "verb": "gift",
  "description": "Send a payment wrapped in an animated gift card.",
  "args": [
    { "name": "recipient", "type": "recipient", "required": true },
    { "name": "amount",    "type": "amount",    "required": true },
    { "name": "design",    "type": "word",      "required": false }
  ],
  "fallback": "payment",
  "docs": "https://example.com/docs/gift"
}
```

| Field           | Requirement     | Meaning                                                                                    |
| --------------- | --------------- | ------------------------------------------------------------------------------------------ |
| `verb`          | MUST            | The verb, per the `verb` rule of section 2. MUST NOT be a global verb.                     |
| `description`   | MUST            | One sentence, for display in autocomplete and help.                                        |
| `args`          | MUST            | Ordered argument descriptors. May be empty.                                                |
| `args[].type`   | MUST            | One of `recipient`, `amount`, `duration`, `period`, `quoted`, `word`, `text`, `enum`.      |
| `args[].values` | MUST for `enum` | Permitted values.                                                                          |
| `fallback`      | MUST            | What a client that does not implement the verb should do: `payment`, `message`, or `none`. |
| `docs`          | MAY             | A URL documenting the command.                                                             |

1. Custom commands MUST follow the section 2 grammar, the section 4 confirmation rules, and the section 7 precedence rule.
2. A client MUST NOT execute a custom verb it does not implement. It MAY offer the declared `fallback`: `payment` means the equivalent plain `/pay`, `message` means a plain `/message`, and `none` means the command is unavailable outside its ecosystem. A fallback MUST be presented as a substitution and separately confirmed.
3. A client that shows where a verb comes from MUST label a custom verb by its **ecosystem** and a global verb by its section in this document, and MUST NOT use one format for both. Citing a custom verb as though it were specified here claims an authority it does not have.
4. Descriptors are host-supplied and unattested. A client MUST render `description` and `docs` as untrusted text, MUST NOT follow `docs` automatically, and MUST NOT allow a descriptor to alter the confirmation requirements of section 4.
5. Commands advertised by one ecosystem apply to recipients of that ecosystem. A client MUST NOT offer a custom verb for a recipient whose domain does not advertise it.
6. **A custom verb carrying a flag that changes who is exposed MUST confirm the flag explicitly.** Where an argument decides whether the *user* is identified — as `/renounce [p|public]` does — the confirmation of section 4.1 MUST state which way it is going, in words, before it runs. A single character deciding whether a statement is anonymous is the kind of argument that is mistyped once and cannot be untyped.

Three illustrations. A wallet ecosystem might define `/gift @r $x [design]`, wrapping a payment in an animated presentation for its own users while foreign clients fall back to a plain payment carrying the design as an attachment. A machine-to-machine ecosystem might define `/charge 15kWh` for device handles such as `@charger-0042@voltnet`, where a vehicle's agent pays a charging post through streamed payments under a thread-scoped delegation, with no account and no roaming contract. And an ecosystem that has defined a vouch verb will eventually want its inverse: Nexus defines `/renounce [p|public] @handle [reason]`, a signed statement that the author does not stand behind someone, unattributed unless they choose otherwise, under the rules of BRC-169 section 10(7). It is deliberately not offered as a button anywhere — speaking against someone should cost the effort of typing it.

### 9. Local Responses

Several things a client says in a conversation are not messages: the `/help` listing of section 5.16, the unsupported-verb report of section 2.5, a parse error, a confusability warning under BRC-169 section 2.3, and a resolution failure under section 4.4. Each is the client answering its own user, inside a thread, with no counterparty.

1. A local response MUST NOT be transmitted to any counterparty and MUST NOT appear in any other participant's view of the thread.
2. It MUST be visually distinguishable from a message, and MUST be labelled. A local response is drawn inside a shared transcript and looks like a message in it; the label is the only thing separating "the client answered me" from "I posted a manual at everyone", which is not a SHOULD.
3. **The label MUST say that nothing was sent, and that no other participant receives it, human or automated.** "Only visible to you" is no longer enough on its own: a conversation may hold agents acting under section 4.7 delegation, and a user who knows an agent is reading the thread cannot tell from that phrase whether the agent is included. The `/help` listing of section 5.16 needs this most, because it is the local response that most resembles a document deliberately posted to the room.
4. It MUST be dismissible, and MUST NOT be treated as part of the transcript for the purposes of signing (section 5.14), receipts (section 5.15), or export.
5. It MUST NOT be parseable as a command on a later pass, per section 2.4. A client's own output is still not user-composed input.

### 10. Display

This document specifies what a command means, not how it looks, with three exceptions where display is load-bearing.

1. **A command is a message.** A command the user issued SHOULD be rendered inline, as the line they typed, with its resolved arguments legible. Rendering every result as a full-width record turns a conversation into a stack of receipts: a `/whois` then occupies the same space as a paragraph, and the conversation it was issued in becomes hard to follow. The structured record of section 4.1 remains available; it does not have to be the resting state.
2. **Amounts and recipients keep their marks.** Where a client renders a resolved argument, it SHOULD carry the same identifying mark used elsewhere for that thing — the person's avatar for a recipient, the token's mark for an amount — so that a misdirected command is visible at a glance rather than only on reading.
3. **Rendering a handle MUST NOT rewrite it.** Where a handle is displayed as a chip or otherwise decorated, the client MUST render the form the user wrote. `@23@treechat` and `@thoth@treechat` name one identity, and silently redrawing one as the other edits the message. Resolution is canonical; display is not.

### 11. Access Gates

A room may condition **reading** it on facts about the reader — a token they hold, a vouch somebody signed, a statement written against them. That mechanism is specified in [BRC-190](https://github.com/bsv-blockchain/BRCs/tree/master/apps/0190.md), which subsumes and replaces the sketch that previously stood in this section.

Two boundaries are worth restating here, because both are about this document.

1. Access gates define no verbs and reserve none. Configuring a gate from a conversational interface is a matter for this document; what a gate *is* and how it evaluates is a matter for BRC-190.
2. The `/gate` verb reserved in section 6 is a different thing and remains reserved. It is the **write** half — charging for entry, with custody, refunds, and a rule for what happens when the room ends. BRC-190 specifies the **read** half only.

## Security Considerations

**Received text must never be executable.** A chat client that parses commands out of incoming message content, memos, display names, or search results hands every counterparty the ability to spend the recipient's money. Section 2.4 forbids it absolutely. This extends to agents: an agent that reads a thread and acts on instructions found in it is executing counterparty-supplied commands, and MUST treat message content as data. Where an agent is intended to act on a counterparty's request, the action must still pass its delegation's scope and caps, and the request must be treated as a proposal rather than an instruction.

**Agents are bounded by delegation, not by the client.** Section 4.5 requires a delegation certificate for non-interactive execution because a client-side "auto-approve" toggle is unverifiable by anyone else and unenforceable once the agent holds keys. The exposure is what BRC-169 section 9.3 says it is: bounded per action, and bounded in total only where the principal funds each action. An agent granted `/pay` with a per-action cap and no principal funding can spend without limit until its certificate expires.

**Confirmation is the last line of defence, and it is a human factors problem.** Homoglyph handles, an alias that resolves somewhere unexpected, an autocomplete that fills a similar recipient, or a fiat amount whose satoshi value has moved are all things a user confirms without noticing. Sections 4.2 and 4.3 exist for this reason, along with the confusability rules of BRC-169 section 2.3. Clients should resist the temptation to make confirmation frictionless for value-moving commands.

**Fan-out amplifies mistakes and disclosure.** A `/split` across many recipients in several ecosystems performs many resolutions and many payments, discloses the sender's intent to every domain involved, and fails partially by design (section 5.5.3). A mistyped recipient in a long split is not recoverable.

**Standing authority accumulates quietly.** `/subscribe` grants no pull authority, which makes it safer than a card mandate, but it does create a recurring outflow that a user may forget. Section 5.6.5 requires a visible list. Fiat-denominated subscriptions vary in satoshi terms and can grow without any further user action, which is why 5.6.4 requires disclosure and recommends a ceiling.

**Revocation is not immediate.** `/revoke` spends an outpoint. Until that spend is confirmed and indexed by whatever source a counterparty consults, the delegate may still transact successfully. Section 5.12 requires the client to say so rather than implying the delegate has been cut off.

**Custom command descriptors are attacker-influenced input.** A hostile or compromised host can publish a descriptor whose description misrepresents what the command does. Section 8.3 requires descriptors to be treated as untrusted and forbids them from weakening confirmation. A client that renders a description as instructions to an agent has reintroduced the injection problem from a different direction.

**Reserved verbs are reserved for a reason.** A client that implements `/escrow` with locally invented semantics creates the exact interoperability failure this document exists to prevent, with funds at stake. Section 6 requires such verbs to be reported as unsupported.

## Implementations

* Every command in section 5 is implementable today against the BRC-169 stack and the reference `@bsv/sdk` and `@bsv/wallet-toolbox` components. No command in section 5 requires a mechanism that BRC-169 does not define, which was the criterion for including it here rather than reserving it in section 6.
* A minimal conforming client implements the section 2 grammar, the section 4 confirmation rules, and `/pay`, `/message`, and `/whois`. Everything else is independently optional, provided unimplemented verbs are reported as unsupported rather than repurposed.
* Ecosystems adopting custom commands are encouraged to publish descriptors from launch, so that foreign clients can explain an unavailable command rather than failing silently.

## References

* [BRC-3: Digital Signature Creation and Verification](/wallet/0003)
* [BRC-190: Access Gates for Metanet Rooms](https://github.com/bsv-blockchain/BRCs/tree/master/apps/0190.md)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)


# NotaryHash — Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates

Gregory Ward, CTO, SmartLedger (<codenlighten1@gmail.com>) · <https://github.com/codenlighten>

## Abstract

This proposal defines a transaction format and a self-contained certificate for anchoring a *signed hash* to the Bitcoin blockchain. A signer proves they signed a specific hash; the on-chain anchor fixes that proof in time at the block in which it is mined. The notarizing service never receives the original document and never handles any client private key. Certificates are independently verifiable — offline for the signature and proof integrity, and against block headers alone (SPV) for the anchor — with no dependency on the issuing service or any chain-indexing API.

## Motivation

Many "blockchain notarization" services require trusting the issuer's database, or a block explorer, to attest that a record exists on-chain. This proposal removes that trust and standardises an interoperable, privacy-preserving format so that any party can produce and verify these proofs:

1. **Privacy.** Only `SHA-256(content)` and a signature over it leave the client; the document itself is never disclosed.
2. **Determinism.** The integrity root is a fixed length-prefixed binary encoding (never `JSON.stringify`, which is not stable across implementations), so any implementation in any language reproduces identical bytes.
3. **Post-quantum readiness.** Classical (ECDSA) and post-quantum (ML-DSA, SLH-DSA) signatures are first-class, so proofs intended to last remain verifiable as classical schemes weaken.
4. **Trustless verification.** A certificate carries an SPV envelope, so a verifier confirms the anchor using only Bitcoin block headers (BRC-9, BRC-10/BRC-11).

## Specification

### Roles

* **Signer** hashes content locally, signs the hash locally, and submits `{algorithm, payloadHash, publicKey, signature}`.
* **Service** verifies the signature, builds canonical proof bytes, anchors them in an `OP_RETURN`, and returns a certificate.
* **Verifier** re-checks the signature and proof bytes offline and confirms the anchor.

### Algorithms

| family             | ids                                                           | hash    |
| ------------------ | ------------------------------------------------------------- | ------- |
| ECDSA              | `ECDSA-secp256k1`                                             | SHA-256 |
| ML-DSA (FIPS 204)  | `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87`                         | SHA-256 |
| SLH-DSA (FIPS 205) | `SLH-DSA-{SHA2,SHAKE}-{128,192,256}{s,f}` (12 parameter sets) | SHA-256 |

The signer signs the 32-byte `payloadHash` directly; post-quantum schemes apply their own internal hashing.

### Canonical proof bytes (integrity root)

`proofHash = SHA-256(canonicalBytes)`, where:

```
lp("NotaryHash/1.0") || u8(version=1) ||
lp(algorithm) || lp(hashAlgorithm) ||
lp(payloadHash) || lp(publicKey) || lp(signature) ||
u64be(createdAtUnix)
```

`lp(x) = u32be(len(x)) || x`; `u8`/`u64be` are unsigned big-endian integers. All multi-byte fields are length-prefixed, so field boundaries are unambiguous regardless of content. This binary encoding — never JSON — is what makes the proof reproducible across implementations.

### On-chain record (`OP_FALSE OP_RETURN`)

A safe data output whose pushes are discriminated by the `mode`/`kind` byte at push index 2:

* **full** (`mode = 0`): `"NOTARYHASH" | u8(1) | u8(0) | algorithm | hashAlgorithm | payloadHash | proofHash | publicKey | signature`.
* **hybrid** (`mode = 1`): as full, but the final two pushes are `SHA-256(publicKey)` and `SHA-256(signature)`; the full blobs live in the certificate (keeps large post-quantum records small on-chain).
* **batch** (`kind = 2`): `"NOTARYHASH" | u8(1) | u8(2) | merkleRoot(32) | u32be(leafCount)`. One transaction anchors many proofs under an [RFC 6962](https://www.rfc-editor.org/rfc/rfc6962) Merkle root, domain-separated (`leaf = SHA256(0x00 ‖ d)`, `node = SHA256(0x01 ‖ l ‖ r)`, split at the largest power of two `< n`, last leaf never duplicated).

### Certificate

A self-contained JSON object, canonicalised via [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) for hashing and transport. Required fields: `protocol`, `version`, `mode`, `algorithm`, `hashAlgorithm`, `payloadHash`, `publicKey`, `signature`, `encoding`, `proofHash`, `createdAt`, `anchor`. A batched certificate additionally carries a `merkle` inclusion proof `{root, leafIndex, leafCount, path[]}` whose folding (leaf → root) must equal the on-chain batch root.

#### SPV envelope (additive)

Attached once the anchoring transaction is mined:

```json
"spv": {
  "rawTx": "<hex>",
  "blockHash": "<hex>",
  "blockHeight": 0,
  "merkleProof": { "index": 0, "nodes": ["<hex>", "*", "..."] },
  "format": "TSC"
}
```

`merkleProof` is a Merkle inclusion proof of `txid` under the block's Merkle root, expressed in the BRC-10/BRC-11 (TSC) model; a BRC-74 (BUMP) or BRC-62 (BEEF) encoding MAY be substituted by setting `format` accordingly. The SPV envelope is **not** part of the canonical proof bytes, so adding it never changes `proofHash` and never invalidates a previously issued certificate.

### Verification

A certificate is valid if and only if all of the following hold:

1. **Signature** — `verify(algorithm, payloadHash, signature, publicKey)` is true. *(offline)*
2. **Proof integrity** — the recomputed `proofHash` equals `certificate.proofHash`. *(offline)*
3. **Anchor** — one of:
   * **SPV (preferred):** `txid = reverse(SHA256(SHA256(rawTx)))` equals `anchor.txid`; the `OP_RETURN` read from `rawTx` matches the certificate fields; folding `merkleProof` from `txid` yields a root equal to the Merkle root of the block header for `spv.blockHash` at `spv.blockHeight`. The verifier trusts only a **block header**, obtained from any source it chooses (a synced header chain, or several independent sources cross-checked) — not a provider's word about the transaction (BRC-9).
   * **Direct:** read the `OP_RETURN` at `anchor.txid` from a chain provider (legacy certificates with no SPV envelope).

Steps 1–2 require no network. The proof-of-existence time is the block time of `anchor.txid`; `createdAt` is an advisory client field only.

### What a certificate proves

A specific public key signed a specific hash, and that proof was anchored on-chain at/by the block timestamp. It does **not** establish who submitted it (any party holding a valid `(hash, signature, publicKey)` triple may re-anchor it; the attestation remains valid), nor anything about the document's contents (a verifier needs the document to recompute the hash).

### Security considerations

* The SPV trust model reduces to obtaining a correct block header. A single header source is a single point of trust; a multi-source quorum, or a proof-of-work-validated header chain, removes it. Implementations SHOULD make the header source explicit.
* Provider-supplied data is self-checking: a raw transaction is accepted only if its double-SHA-256 equals the txid already held, so a provider cannot substitute different bytes.

## Implementations

A reference implementation (service, client SDK, and a dependency-light standalone verifier) is available, including:

* a standalone certificate verifier that depends only on the protocol's own modules (signature + `proofHash` + SPV anchor), never on the issuing service;
* a confirmation poller that attaches the SPV envelope once the anchoring transaction is mined; and
* published test vectors: a complete certificate-with-SPV-envelope golden vector, `txidFromRawTx` checked against the Bitcoin genesis coinbase transaction, and the Merkle fold checked against the real block-170 (two-transaction) proof.

The format has been demonstrated end-to-end on BSV mainnet: a certificate verifies fully offline against independently obtained block headers, with no trust in the issuing service.

## References

* BRC-9: [Simplified Payment Verification](/transactions/0009)
* BRC-10: [Merkle proof standardised format](/transactions/0010)
* BRC-11: [TSC Proof Format with Heights](/transactions/0011)
* BRC-12: [Raw Transaction Format](/transactions/0012)
* BRC-62: [Background Evaluation Extended Format (BEEF)](/transactions/0062)
* BRC-74: [BSV Unified Merkle Path (BUMP)](/transactions/0074)
* [RFC 6962: Certificate Transparency (Merkle trees)](https://www.rfc-editor.org/rfc/rfc6962)
* [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785)
* FIPS 204: Module-Lattice-Based Digital Signature Standard (ML-DSA)
* FIPS 205: Stateless Hash-Based Digital Signature Standard (SLH-DSA)


# Block Media Format (BMF) — Composable On-Chain Audio/Video

sun-dive (<https://github.com/sun-dive>)

## Abstract

Block Media Format (BMF) is an open format for **composable, on-chain-native audio/video**. Instead of one monolithic encoded file, a "video" is expressed as a small **timing manifest** that references independently-owned **media components** — each of which can be minted once on-chain, provenanced, and reused many times. BMF defines two file types: `.bmf` (the manifest / "recipe") and `.bmc` (a portable ZIP container bundling a `.bmf` with its media). BMF introduces **no new codec** — it carries standard WebP, FLAC and MP3 inside — and requires no IANA registration to use. It is the media-composition layer that sits above component-level token/provenance standards.

> The Abstract section should concisely describe your proposal at a high-level.

## Motivation

Conventional video is a single opaque file: content is duplicated wherever it recurs, and the individual parts (a loop, a stem, the master audio) carry no independent ownership. On a chain that can carry media payloads, each component can instead be its own on-chain asset with its own author and royalty.

BMF exists to make that structure expressible and playable:

1. **Store once, reference many.** A clip used at five moments is one stored component plus five cue lines — not five copies.
2. **Component-level provenance.** Every clip and the song is an owned on-chain asset; a composition is a *composition of owned parts*, each independently attributable and monetizable. This complements token/provenance standards (e.g. BRC-113, MPT[<sup>1</sup>](#footnote-1)).
3. **Fully on-chain composable video.** When a manifest references components by **txid**, the "video" is nothing but a tiny manifest; a player fetches each component from chain and sequences it — no fat file anywhere.
4. **Standard media, universal decode.** No bespoke codec; anything that plays WebP/FLAC/MP3 can implement a player.
5. **Tempo-native authoring.** Cues are absolute seconds, so scenes can be placed on the beat.

> The Motivation section should let people know the context for your proposal, and why it was written.

## Specification

1. **Two file types.**
   * `.bmf` — **Block Media Format**: the manifest — a timeline that references media components (by on-chain **txid**/outpoint or by local **filename**) with start-time cues.
   * `.bmc` — **Block Media Container**: a ZIP holding exactly one `.bmf` plus the media files it references by local name — the portable, self-contained form. A `.bmc` **contains** a `.bmf` + media; a standalone `.bmf` **references** on-chain content. One spec, two delivery forms.
2. **Manifest encodings.** A `.bmf` MAY be written in either encoding, and a conformant player SHOULD accept both:

   **A. Cue form (LRC-compatible).** References components by local filename. Lines are `[mm:ss.cc]<name>`, with optional `# key: value` headers:

   ```
   # bmf: 0
   # audio: supersonic.mp3
   # tempo: 110
   [00:00.00]forgotten-control-room.webp
   [00:04.36]power-low.webp
   [00:08.72]power-high.webp
   ```

   **B. JSON form.** Carries on-chain **txid** references (the pure on-chain form):

   ```json
   {
     "bmf": 0,
     "audio": { "tx": "6059dfef894d83be6df1ca858e7d644dfbc1256b7611f98abb1a1111765ba0e5", "name": "supersonic.mp3" },
     "tempo": 110,
     "scenes": [
       { "t": 0.00, "tx": "dff809f6ab5052ed96f7a75e2f9751e88cedcf9aca2e8211b9c46b1ec76de128", "name": "forgotten-control-room" },
       { "t": 4.36, "tx": "2722b170aace00cc992bf671dc458f30588e96ad4a5992308c46dc01f574e5bc", "name": "power-low" },
       { "t": 8.72, "tx": "2722b170aace00cc992bf671dc458f30588e96ad4a5992308c46dc01f574e5bc", "name": "power-high" }
     ]
   }
   ```
3. **Fields.**
   * `bmf` — format version integer (currently `0`).
   * `audio` — the master audio track, by `tx` (on-chain) and/or `name` (local).
   * `tempo` — OPTIONAL BPM metadata to aid beat-aligned authoring.
   * `scenes[]` — ordered scene cues: `t` = scene start in **seconds**; `tx` = on-chain reference (txid/outpoint of the minted component); `name` = filename/label (used inside a `.bmc`, or as a human label).
4. **Playback semantics.** A scene plays until the next scene's `t`. Looping media (e.g. animated WebP) **restarts from frame 0 on its cue**, so loops stay phase-locked to the beat.
5. **Resolution algorithm.** To resolve a `.bmf` a player MUST:
   1. Load the manifest (cue or JSON).
   2. Resolve `audio` and each scene reference: if `tx` is present, fetch that component **from chain** (raw tx → extract media payload); else read `name` from the accompanying `.bmc` / folder.
   3. Play the audio; at each cue time, switch to that scene and restart its loop; hold until the next cue.
6. **Media constraints.** Components MUST use standard, universally-decodable media — WebP (including animated and **variable-frame-rate**, where a long hold costs a single frame), FLAC, or MP3. BMF defines **no new codec**.
7. **MIME types (informal).** `.bmf` → `application/x.bmf` (or `application/vnd.blockmedia+json` when JSON); `.bmc` → `application/x.bmc` (or `application/vnd.blockmedia+zip`). The `x.` experimental tree is used today; a `vnd.` registration MAY follow if native OS/browser recognition is ever required.

> The Specification section of your proposal should stipulate all information needed to implement the standard, and make up the bulk of the document. Generally, people should be able to create a compatible implementation with only the specification.

## Implementations

1. **Authoring tool** — a local-first studio ("Pole Position") whose music-video timeline exports a `.bmc` (media files + a `.bmf` cue manifest).
2. **Player** — a smart-NFT player reads the manifest, resolves each component (by txid from chain, or by name from the container), and sequences the scenes to the audio, restarting loops on cue.
3. **Reference repository (MIT):** `https://github.com/sun-dive/block-media-format` — full v0 spec, examples, and the `.bmf` / `.bmc` definitions.
4. **First real-world components (BSV mainnet).** The "Supersonic" release ships its building blocks as individually-minted, provenanced, reusable on-chain assets — each resolvable today and composable in a `.bmf` by `{tx, name}` reference[<sup>2</sup>](#footnote-2):
   * *Forgotten Control Room* — an SOS-flicker loop BMF atom: `dff809f6ab5052ed96f7a75e2f9751e88cedcf9aca2e8211b9c46b1ec76de128`.
   * *Suited Up* — three tube-power loops published as ONE `.bmc` set, each member addressable by `{tx, name}`: `2722b170aace00cc992bf671dc458f30588e96ad4a5992308c46dc01f574e5bc` (members `power-low`, `power-medium`, `power-high`).
   * *Supersonic* master audio: `6059dfef894d83be6df1ca858e7d644dfbc1256b7611f98abb1a1111765ba0e5`.

> The Implementations section should contain information about places where the standard is implemented, or examples of its implementation.

## References

* 1: Merkle Proof Token (MPT) protocol — BRC-113, <https://github.com/bsv-blockchain/BRCs>
* 2: Block Media Format reference implementation and examples, <https://github.com/sun-dive/block-media-format>

> The References section should contain any footnotes used throughout the document.


# Frictionless On-Chain Onboarding via Pre-Funded Claimable Tokens

sun-dive (<https://github.com/sun-dive>)

## Abstract

This BRC specifies a protocol for onboarding a person who has **no wallet and no coins** directly into ownership of a real on-chain asset — by clicking a link. A publisher pre-funds a batch of **voucher outputs**; each voucher becomes a claim link. A recipient claims by having the **voucher fund** an on-chain acquisition (e.g. a covenant-token mint under BRC-146[<sup>1</sup>](#footnote-1)) while a key they generate **in the moment owns** the result — *payer and owner are decoupled*. The voucher keys are **deterministically derived from the publisher's private key**, so every issued link and every unclaimed voucher is recoverable from the publisher's key alone, with no separate backup. The blockchain "cold-start" problem — *you already need a wallet and coins to receive your first asset* — dissolves: the gifter pays; the recipient just claims.

> The Abstract section should concisely describe your proposal at a high-level.

## Motivation

The single largest barrier to blockchain adoption is the **cold start**. To receive your first on-chain asset today you must *already* have a wallet, *already* hold coins for the fee, and know enough not to lose either. Every one of those prerequisites sheds prospective users before they ever hold anything. "Airdrop" and claim-link schemes rarely fix it — they deliver a fungible balance the recipient still cannot move without gas, or they assume the recipient can already transact.

This protocol removes **all** of the prerequisites at once:

1. **The gifter pre-funds the entire claim** — network fee, any bond, and any enforced covenant royalties — so the recipient spends nothing.
2. **The recipient receives a real, owned, provenanced asset**, controlled by a key they generate at claim time — not a custodial IOU or a balance they must later fund to use.
3. **No prior wallet, no prior coins, no purchase** — just a link. A newcomer goes from *nothing* to *owning an on-chain asset* in a few clicks.

Critically, this uses **no new cryptographic primitive**. It is a *recombination* of standard ones — a P2PKH-funded output, a covenant-token acquisition, and a decoupling of the transaction's *payer* from its *owner*. Its power lives entirely in the **composition**, which is exactly why it is easy to overlook — an **open secret**. This BRC states it plainly so any wallet can implement it and so the pattern can be recognized for what it is: a general-purpose, trustless **onboarding primitive**. Deterministic voucher keys make it operationally safe at scale — a publisher can issue thousands of links and recover every unclaimed one (and its funds) from a single key.

> The Motivation section should let people know the context for your proposal, and why it was written.

## Specification

### 1. Roles and objects

* **Publisher** — the issuer/gifter, holding a private key and some coins.
* **Recipient** — the claimer, who MAY have no wallet and no coins.
* **Voucher** — a funded on-chain output plus the private key that controls it.
* **Asset** — the thing claimed: a covenant token (BRC-146[<sup>1</sup>](#footnote-1)) or any acquirable on-chain output whose acquisition transaction can be funded by an arbitrary input.

### 2. Deterministic voucher keys

Voucher keys are derived so that only the publisher can regenerate the batch:

```
voucherKey_i = SHA-256( publisherPrivKey (32 bytes, big-endian)
                        ‖ collectionRef  (32-byte reference to the asset/collection)
                        ‖ index_i        (4 bytes, little-endian) )
```

Because the derivation consumes the publisher's **private** key, no one else can reproduce the voucher keys — so every issued link **and** every unclaimed voucher is recoverable from `(publisherPrivKey, collectionRef)` plus the chain, with **no separate backup**. The one-way hash never reveals the publisher's key, and the resulting voucher WIF is intended to be shared in the link.

### 3. Issuing vouchers

The publisher creates `N` voucher outputs in **one** transaction: for `i` in `[start, start+N)`, an output of `fundEachSats` locked to `P2PKH(address(voucherKey_i))`[<sup>2</sup>](#footnote-2), plus change back to the publisher. `fundEachSats` MUST cover a single claim's network fee **plus** the asset's bond (if any) **plus** any enforced covenant fees (BRC-146 §6). Each `voucherKey_i` WIF becomes a claim link.

### 4. The claim — decoupled payer and owner

The recipient's wallet, given a voucher WIF (from the link):

1. **Generates a fresh recipient key** — the future owner.
2. **Builds the acquisition transaction** (e.g. the covenant's permissionless replicate branch, BRC-146 §6) in which:
   * the **voucher UTXO funds the spend** — the *voucher key* signs the funding input, paying the fee, bond, and any covenant royalties;
   * the **asset is owned by the recipient's fresh key** — payer ≠ owner;
   * change returns to the recipient.

The recipient therefore needs **no prior coins and no prior wallet**: the voucher pays, and a key created in the moment owns the result. This payer/owner decoupling is the crux — it is what lets a funded voucher deliver ownership to a brand-new key.

### 5. Recovery and reclaim

From the publisher's key alone, regenerate `voucherKey_i` for ascending `i` and gap-scan the chain:

* **funded and unspent** → a live, unclaimed link (rebuild it from the WIF);
* **funded and spent** → already claimed;
* **never funded** → the end of the batch (`nextIndex`).

Unclaimed voucher outputs are ordinary UTXOs the publisher MAY reclaim at any time. No local database is required — the batch is fully reconstructable from `(publisherPrivKey, collectionRef)` + chain.

### 6. Link payload (informal)

A claim link MUST carry the voucher **WIF** and enough context to identify the asset/collection (e.g. its `collectionRef`). Implementations MAY choose any link scheme (e.g. a `g=<wif>` query parameter). The WIF is a **bearer credential**: whoever redeems it first claims the asset.

### 7. Security considerations

* **Bearer semantics.** Anyone who obtains a live voucher WIF can claim it; deliver links over channels appropriate to their value.
* **Bounded exposure.** A voucher SHOULD hold only the amount needed for its intended claim, so a leaked link risks only that amount — which the publisher can also reclaim while unspent.
* **No key leakage.** The publisher's private key is never derivable from a voucher key or WIF (one-way hash).
* **Ownership integrity.** Because payer and owner are decoupled, the claimed asset is owned by the recipient's key, never by the voucher key; the voucher is spent purely as funding.

> The Specification section of your proposal should stipulate all information needed to implement the standard, and make up the bulk of the document. Generally, people should be able to create a compatible implementation with only the specification.

## Implementations

1. **Phar Lap** — an open-source (Open BSV License) smart-NFT wallet implements this end-to-end: `createGiftVouchers` (batch-funds deterministic vouchers in one tx), `deriveVoucherKey` (the derivation of §2), and `scanGiftVouchers` (the recover/reclaim of §5); recipients claim covenant editions (BRC-146) with **no prior wallet or coins**, each claim funded by its voucher while a freshly-generated recipient key takes ownership. Repository: `https://github.com/sun-dive/PharLap` (`src/editionBuilder.ts`).

> The Implementations section should contain information about places where the standard is implemented, or examples of its implementation.

## References

* 1: Miner-Enforced Resale-Royalty Covenant Tokens (OP\_PUSH\_TX) — BRC-146 (the covenant-token asset a voucher claims; its permissionless replicate branch is the acquisition transaction funded by the voucher).
* 2: Pay to Public Key Hash — BRC-16, <https://github.com/bsv-blockchain/BRCs> (the voucher output and funding-input template).


# Transaction Creation

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define a concise, transport-independent method by which applications can request the creation of Bitcoin transactions. We specify the use of [BRC-8](/transactions/0008) transaction envelopes, and provide an open-ended and extensible approach to output stipulation by the requesting application. We define the formats for request, response and error messages exchanged between application and wallet software.

## Motivation

The motivation behind this Bitcoin wallet transaction creation specification is to provide a standardized method for third-party applications to communicate with Bitcoin wallets. This specification aims to enable applications to request the creation of Bitcoin transactions in a concise and transport-independent way, and to allow wallets to focus on wallet and Bitcoin infrastructure, rather than application-specific functionality — conversely, applications can avoid re-inventing the wheel and the need to become their own Bitcoin wallet and infrastructure providers.

By standardizing the communication protocol between applications and wallets, this specification aims to simplify the development of Bitcoin-related applications, reduce development time and costs, and improve the user experience of Bitcoin-powered applications. Additionally, this specification aims to promote interoperability between different wallets and applications, making it easier for users to switch between wallets and for applications to work with multiple wallets.

Implementing this specification will provide a standard way for web and mobile applications to interface with on-device Bitcoin wallets, improving the security of Bitcoin-related operations and enabling users to better control their Bitcoin transactions. With this specification, applications can avoid the overhead of creating their own Bitcoin wallets, and wallets can focus on their core functionality of managing Bitcoin keys, signing transactions, and interacting with the Bitcoin network.

Overall, this specification aims to provide a simple, open-ended, and extensible approach to Bitcoin transaction creation, with a transport-independent method of communication that enables wallets and applications to work together seamlessly.

## Status Note

BRC-1 is historical. Current wallet transaction creation is specified by [BRC-100](/wallet/0100) `createAction`, `signAction`, and `abortAction`.

For new implementations:

* use `lockingScript` rather than the legacy `script` output field,
* use BEEF / Atomic BEEF rather than BRC-8 envelopes for transaction return data,
* use BRC-100 `inputBEEF`, `inputs`, `outputs`, `labels`, and `options` for the full action model,
* use BRC-100 HTTP substrate routes such as `/createAction` without the historical `/v1` prefix,
* and treat mAPI response fields as legacy.

The remainder of this document is retained to explain the origin of the wallet action model and for compatibility with older deployments.

## Pre-Requisites

In order to implement this specification, Bitcoin wallet software must be able to perform the following functions:

* Create Bitcoin transactions with any number of outputs that use arbitrary Bitcoin scripts and amounts.
* Prompt the user for acceptance of transactions requested by third-party applications.
* Unlock a sufficient number of UTXOs to fund the transaction and capture left-over change outputs.
* Provide merkle proofs on all inputs to build the [BRC-8](/transactions/0008)[<sup>1</sup>](#footnote-1) formatted response.

Wallets that meet these pre-requisites will be able to fulfill the requirements of this specification.

## Scope

The scope of this specification is the definition of standard JSON formats for requesting the creation of a Bitcoin transaction, providing the completed transaction once created, and any errors that prevent the transaction from being created.

The specification is intended to be transport-independent, so considerations related to how these messages are communicated, or matters related to the security or authenticity of such communications, are beyond the scope of this specification.

We have sought to keep the base version of this specification as minimal as possible, while still providing all the information needed by applications. Therefore, we have decided to exclude arbitrary input redemption from this specification, and move it to an extension — namely, [BRC-4](/wallet/0004). We have also chosen to exclude transaction output tracking from the base specification, moving it to [BRC-46](/wallet/0046).

## Specification

We specify that there are two relevant parties: an application and a Bitcoin wallet. The application is software that runs on an end-user device (such as a webpage, mobile app, or desktop app) that wants to make use of Bitcoin without building wallet infrastructure themselves. The wallet is software that runs on an end-user device that specializes in the management of a user's Bitcoin-related identity information, private keys, facilitates the transaction creation and signing process, and communicates with the Bitcoin network.

We specify that there exists some abstract communications channel (beyond the scope of this specification) that enables the application and the Bitcoin wallet to communicate in a secure and authenticated manner. This mechanism must facilitate the application making a request to the Bitcoin wallet, and the wallet providing a response back to the application.

We specify that JSON is the format that will be used for the messages exchanged in accordance with this specification. We further specify that there exists some out-of-band means for the application to indicate to the Bitcoin wallet that it intends to invoke and utilize this [BRC-1](/wallet/0001) protocol, as opposed to some other protocol.

> For example, if the underlying communications method was HTTP ([BRC-5](/wallet/0005)), the application could indicate its intention to use this protocol by sending requests to `/v1/createAction` instead of `/v1/foobar`. In effect, this means that **there is no need for JSON fields in this specification denoting the message type**, since both parties have already established that they are exchanging messages according to the [BRC-1](/wallet/0001) protocol. **Specific communication mechanisms that facilitate this are defined by other standards such as** [**BRC-5**](/wallet/0005)**,** [**BRC-6**](/wallet/0006) **and** [**BRC-7**](/wallet/0007)**.**

### Transaction Creation Request

We specify that the first message exchanged under this [BRC-1](/wallet/0001) protocol originates from the application, and constitutes a request by the application that a Bitcoin transaction be created by the Bitcoin wallet, as specified by the application.

The Transaction Creation Request comprises a JSON object with the following fields, all of which are required:

| Field         | Description                                                                                                   |
| ------------- | ------------------------------------------------------------------------------------------------------------- |
| `description` | A present-tense, human-readable description of the action being performed or represented by this transaction. |
| `outputs`     | An array of Output Objects.                                                                                   |

Output Objects are JSON objects that have the following fields:

| Field      | Description                                                                       |
| ---------- | --------------------------------------------------------------------------------- |
| `script`   | A hex-formatted Bitcoin output script, as defined in [BRC-14](/scripts/0014).     |
| `satoshis` | The number of satoshis that are to be in the output, given as a positive integer. |

This provides the wallet with enough information to create the transaction requested by the application. Here is an example of a simple Transaction Creation Request:

```json
{
  "description": "Pay John Galt 3,301 satoshis",
  "outputs": [{
    "script": "76a914b10f7d6c7fda3285e9b98297428ed814374cbd4088ac",
    "satoshis": 3301
  }]
}
```

In this example, the application is stipulating that one output should be created, containing `3301` satoshis, and that the output should be locked using a pay-to-public-key-hash ([BRC-16](/scripts/0016)) locking script.

The application also provides a simple description that can be used by the wallet when prompting the user for authorization to proceed with the creation of the transaction, or later when showing the transaction in a list.

### Transaction Creation Response

If the transaction is successfully created by the Bitcoin wallet according to the instructions provided by the application, we specify that the Bitcoin wallet assembles a [BRC-8](/transactions/0008) Transaction Envelope and returns it to the user. We specify that, in addition to the required [BRC-8](/transactions/0008) envelope fields, a `txid` field is also included in the returned Envelope Object for the convenience of the application.

For added clarity, we denote a table comprising the standard [BRC-8](/transactions/0008) Transaction Envelope fields with our additional `txid` field:

| Field           | Description                                                                                                                                                                                                                                                                         |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rawTx`         | The [BRC-12](/transactions/0012) transaction in hex format                                                                                                                                                                                                                          |
| `inputs`        | An object whose keys are the TXIDs of all inputs to this transaction and whose values are [BRC-8](/transactions/0008) envelopes for the respective transactions (the child envelopes are **not** required to contain the extra `txid` field), (required unless `proof` is provided) |
| `mapiResponses` | An array of objects where each object comprises a positive endorsement of the transaction from a Bitcoin miner (required unless `proof` is provided)                                                                                                                                |
| `proof`         | A [BRC-10](/transactions/0010) merkle proof for the transaction in JSON format (required unless `inputs` and `mapiResponses` are given)                                                                                                                                             |
| `txid`          | The Bitcoin TXID associated with this transaction                                                                                                                                                                                                                                   |

Here is an example of a simple Transaction Creation Response:

```json
{
  "rawTx": "01000000017c36b9ed1c6284079fa9b2111d47e934460d2af631ceda3ffc1bf0587129d1b7020000006b483045022100c655d8c0bfc895c861dda240010c23d5267fd4414894794d3f3591fb7d59fe090220627246ff28e1776c34fd09fbbcfc5bb2efa8090350b35192f1638dd9143ea2a8412102c6848b9988deb921e645fbac47f196e7bec44a6f8612874632240aadc4a35b5bffffffff03e50c0000000000001976a914b10f7d6c7fda3285e9b98297428ed814374cbd4088acc8000000000000001976a914652138f4b89dd615def2c3aa7e13bc1900b5cff588ac1f450000000000001976a91428e5b6157e4a72d8bc8d2374f7acec1388be52f988ac00000000",
  "inputs": {
    "b7d1297158f01bfc3fdace31f62a0d4634e9471d11b2a99f0784621cedb9367c": {
      "proof": {
        "index": 16349,
        "txOrId": "b7d1297158f01bfc3fdace31f62a0d4634e9471d11b2a99f0784621cedb9367c",
        "targetType": "header",
        "target": "00c0322ac50d5e7f9cb2f9be53a34f78fe1cdd7029a03eeeb50b780000000000000000002cb6066c3101dddf29a7853051487bada9adeb9807d2ab4360ca47d3c30581138d9a1264eb370d1802029180",
        "nodes": [
          "95733f96fa3a1f0ba779627d7dc62891f6eee8a13eb0e5a5610bf0d5d1686401",
          "ac95afde3cb7d2f639e3a36d07db7b0becc4bbcb393dce51e322adc38f78c5a1",
          "4054dae1209fa48797dc459239eb396b6220618c9a8bd93776f7485f14759fb7",
          "8f6327bf637241b3e5b029475f3bf5856b263248fa18272e04ec8dae35131351",
          "4da5d84ccde803ba4515ee1f8912b49c738bbc145850d1f88885474ff57d1f58",
          "ec1e6e5c0f50ac2eb8e493c1c372aedde359cbf706643a6bdfaa3aebf4996f01",
          "cedf75aa7314f898121bccdf27eb8b6769262324460a98bf84cdaa6a0379895e",
          "246a798ac6e7e3a5893cb24aa26ca92f71309f41200e1fafbad33a138ea709d6",
          "a5df0501e2c74677b0201fa2b2a6b9cf4156f8266bbc73767e4a784d0793d557",
          "774bdbf5a5992baf1aea1cb80afb68ed93d03aba37b16b57c9fb4cf596e522e6",
          "b621a1f5f431843ed450a09c9a09f245290db204ffe2068732b7c278f4fa2605",
          "93dd7a58841fa52e8d1ac8e5d4bef852450a716c79a603dd4b85772abd0cc0db",
          "57860c6a1f278979fc4540c98f43dc41d64c6f52dffa6aaaf7b07d206e8ca5b5",
          "5de27bdb3b0123c2149d3cc8a54cab926ddc50f93639b02be0885ff9c4365fc8",
          "5cdd1beb2c59b2dbf9be5790633c0a58ea274e2669f52226343648c1a1ca31cc",
          "560490ecae5c1d075dbaf6ee26b61bd758bfbadd09a6cee475156f1b0a3fc9c7"
        ]
      },
      "rawTx": "01000000011a348074d8fe477dec48fe3c07b493371c9fa8b5a49d5d0424916a65b65e77be020000006b4830450221009acfd97f48c819d0fe0ae59df2110eb8a7f6defe948bcf556562b4d1f5e7129602202586aa5f2cb767eba3978aa3185f09e8ec745fcb1111296ca42fb384f7355b6c412102466ef576171e0e0684081f2a99b5e6b5786dac80b5d6d64fec3b033628763a4fffffffff0394070000000000001976a914d75cf5dc3ce0767b005b71b2a8705809ba4b815088acc8000000000000001976a914931c8716ddfb85f4ea4bbdad395d27bd6323b57688acea520000000000001976a914dbdbf9c3216c9cf526e18bc9f976018881972fff88ac00000000"
    }
  },
  "mapiResponses": [
    {
      "payload": "{\"apiVersion\":\"\",\"timestamp\":\"2023-03-17T05:09:51.554Z\",\"txid\":\"737a1e90af9745da3f22ef74bc71a089c5e2a76e9662a0c9fa5b7cf94fe32e75\",\"returnResult\":\"success\",\"resultDescription\":\"\",\"minerId\":\"03ad780153c47df915b3d2e23af727c68facaca4facd5f155bf5018b979b9aeb83\",\"currentHighestBlockHash\":\"00000000000000000138b377cab18dc72cc7ac38d6631949c9694071855bcce8\",\"currentHighestBlockHeight\":783508,\"txSecondMempoolExpiry\":0}",
      "publicKey": "03ad780153c47df915b3d2e23af727c68facaca4facd5f155bf5018b979b9aeb83",
      "signature": "304402201529238a9cf64b02dad0d4573ef5a0780bddb2fe6d6afdca1473e340a6e1512202204845c0721bdb8b6b8ec0e255f120e5749c373a8e2fe13fed208c3154197846c0"
    },
    {
      "payload": "{\"apiVersion\":\"1.5.0\",\"timestamp\":\"2023-03-17T05:09:51.7263993Z\",\"txid\":\"737a1e90af9745da3f22ef74bc71a089c5e2a76e9662a0c9fa5b7cf94fe32e75\",\"returnResult\":\"success\",\"resultDescription\":\"\",\"minerId\":\"030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e\",\"currentHighestBlockHash\":\"00000000000000000138b377cab18dc72cc7ac38d6631949c9694071855bcce8\",\"currentHighestBlockHeight\":783508,\"txSecondMempoolExpiry\":0,\"warnings\":[],\"failureRetryable\":false}",
      "publicKey": "030d1fe5c1b560efe196ba40540ce9017c20daa9504c4c4cec6184fc702d9f274e",
      "signature": "3044022013546dbe4b9f402ae684f3ac51bbdef5ebe5a1b1abd646b04b9a35e94515a29e022041f4b047caa6d49d1fc4ff202646939d91abd661776d75d9e5dee6b2b355645a"
    }
  ],
  "txid": "737a1e90af9745da3f22ef74bc71a089c5e2a76e9662a0c9fa5b7cf94fe32e75"
}
```

### Transaction Creation Error

If the Bitcoin wallet is unable to fulfill the Transaction Creation Request for any reason, we specify that it should respond with a JSON-formatted Transaction Creation Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                                |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                       |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_NOT_SUFFICIENT_FUNDS"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                       |

One example of a Transaction Creation Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "You have denied permission for creating this Bitcoin transaction."
}
```

## Implementations

This specification has been implemented into various wallets and applications:

* The Babbage MetaNet Client implements [BRC-5](/wallet/0005), the local HTTP substrate including this specification. Transaction Creation Request objcts are sent to the client, which then returns either a Transaction Creation Response or a Transaction Creation Error, after prompting the user.
* Babbage has implemented a ToDo List application that creates Transaction Creation Request objects which are then received and processed by any wallet software running on a user device that implements this specification.
* The Ninja Bitcoin wallet, through its `getTransactionWithOutputs` function, accepts a Transaction Creation Request and returns either a Transaction Creation Response or a Transaction Creation Error.

## References

* 1: Transaction responses are [BRC-8 Transaction Envelopes](/transactions/0008)


# Data Encryption and Decryption

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We devise a method for applications to request the encryption and decryption of data. Specifically, we define a mechanism for data encryption within the [BRC-42](/key-derivation/0042) key derivation system, utilizing the [BRC-43](/key-derivation/0043) protocol and key ID scheme. During encryption, the sender derives their own child private key and the child public key of the recipient using the [BRC-42](/key-derivation/0042) process, then computes an ECDH shared secret between the child keys which is used in symmetric encryption with AES-256-GCM. The initialization vector, together with the ciphertext, are sent to the recipient. During decryption, the recipient computes their own private key and the public key of the sender, and uses ECDH to compute the same shared secret. The key is then used together with the provided initialization vector to decrypt the ciphertext. When no counterparty exists, we stipulate substitution for the sender's own public key in the child key derivation process.

## Motivation

The Bitcoin ecosystem has demonstrated a clear desire for wallets to support data encryption between parties[<sup>1</sup>](#footnote-1). This capability facilitates secure exchange of information, improves user privacy, and enables novel applications that require direct and secure communication between users. The unique economic incentives of micropayment-based applications, particularly those facilitated by Bitcoin wallets, make user privacy paramount, and have led to the adoption of end-to-end encryption by many applications[<sup>2</sup>](#footnote-2). While several encryption systems have been developed and integrated into various platforms, there is currently no standard methodology that supports both single-party and multi-party encryption, protocol-level permissions management, and leverages [BRC-42](/key-derivation/0042) key derivation. The [BRC-2](/wallet/0002) standard aims to fill this gap and provide a secure and standardized framework for encryption and decryption within the Bitcoin ecosystem.

## Status Note

The cryptographic construction in this document remains the basis for wallet `encrypt` and `decrypt`, but the BRC-1 message framing and legacy SDK references are historical. Current implementations expose these operations through [BRC-100](/wallet/0100), using `protocolID`, `keyID`, `counterparty`, `privileged`, `privilegedReason`, and `seekPermission` arguments.

## Specification

We start with the same constructs defined in [BRC-43](/key-derivation/0043): users with clients, protocols and applications. We stipulate the use of [BRC-43](/key-derivation/0043) invoice numbers in the context of [BRC-42](/key-derivation/0042) key derivation, and we build on top of the permissions architecture defined by [BRC-43](/key-derivation/0043).

When an application sends a message to a client requesting that data be encrypted, the message comprises:

* The [BRC-43](/key-derivation/0043) security level, protocol ID, key ID and counterparty to facilitate [BRC-42](/key-derivation/0042) key derivation and permissioning
* The data to encrypt

We stipulate the following process for encryption:

* The message sender begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The message sender uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive a child public key for the recipient
* The message sender uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive their own child private key
* The message sender computes the ECDH shared secret between the two derived child keys
* The resulting elliptic curve point's X and Y values are hashed with SHA256 to create an AES-256-GCM symmetric encryption key
* The resulting 256-bit value is used in conjunction with a randomly-generated 256-bit initialization vector to encrypt the message with AES-256-GCM
* The initialization vector is prepended to the ciphertext, and the combined value is returned by the client to the application over the abstract [BRC-1](/wallet/0001) communications substrate.

We stipulate the following process for message decryption:

* The recipient somehow comes to know the ciphertext (prepended with the initialization vector), the counterparty, the security level, protocol ID, and key ID. The mechanism for conveying this information to the recipient is beyond the scope of this specification.
* The recipient begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The recipient uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number, their own private key and the public key of the sender, to compute the sender's child public key
* The recipient uses the same process to compute his own child private key
* The recipient computes a shared secret between the two child keys, using the hash of the X and Y values as an AES-256-GCM symmetric key
* The recipient then uses the symmetric key to decrypt the ciphertext with the provided initialization vector

We build upon the abstract messaging layer first described in [BRC-1](/wallet/0001). Specifically, we define five new [BRC-1](/wallet/0001) messages to facilitate requests and responses for encryption and decryption, and error handling. For each of the messages, we stipulate that there exists some out-of-band mechanism for the parties to communicate which of the messages are being exchanged, removing the need for a message type field. Finally, specifically for the request messages, we stipulate that the message comprises a header and a payload, with the payload containing the data (ciphertext or plaintext), and the header containing the other information. For the response messages, no message header is defined and the payload simply contains the specified data.

### Encryption Request

The encryption request is a message sent by the [BRC-43](/key-derivation/0043) application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, or `self`                                                                                                                                                                                       |

The message payload comprises the data to encrypt.

### Encryption Response

The response message comprises a payload containing the encrypted ciphertext, prepended with the 32-byte initialization vector.

### Decryption Request

The decryption request is a message sent by the application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, or `self`                                                                                                                                                                                       |

The message payload comprises the data to decrypt, prepended with the 32-byte initialization vector.

### Decryption Response

The response message comprises a payload containing the decrypted plaintext.

### Cryptography Error

If the client is unable to fulfill the encryption or decryption requests for any reason, we specify that it should respond with a JSON-formatted Cryptography Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                    |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_DECRYPTION_FAILED"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                    |

One example of a Cryptography Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "You have denied permission for encrypting this data."
}
```

## Test Vectors

For compatibility with this encryption scheme, we stipulate the following:

A user who has the following identity private key...

```
6a2991c9de20e38b31d7ea147bf55f5039e4bbc073160f5e0d541d1f17e321b8
```

...which implies the followign identity public key for that user...

```
025ad43a22ac38d0bc1f8bacaabb323b5d634703b7a774c4268f6a09e4ddf79097
```

...should be able to use security level `2`, the `BRC2 Test` protocolID with keyID `42` and the following counterparty...

```
0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1
```

...to decrypt the message with the following ciphertext (with prepended initialization vector)...

```
[252, 203, 216, 184, 29, 161, 223, 212, 16, 193, 94, 99, 31, 140, 99, 43, 61, 236, 184, 67, 54, 105, 199, 47, 11, 19, 184, 127, 2, 165, 125, 9, 188, 195, 196, 39, 120, 130, 213, 95, 186, 89, 64, 28, 1, 80, 20, 213, 159, 133, 98, 253, 128, 105, 113, 247, 197, 152, 236, 64, 166, 207, 113, 134, 65, 38, 58, 24, 127, 145, 140, 206, 47, 70, 146, 84, 186, 72, 95, 35, 154, 112, 178, 55, 72, 124]
```

... and receive the following plaintext:

```
BRC-2 Encryption Compliance Validated!
```

...and to validate the message with the following HMAC...

```
[81, 240, 18, 153, 163, 45, 174, 85, 9, 246, 142, 125, 209, 133, 82, 76, 254, 103, 46, 182, 86, 59, 219, 61, 126, 30, 176, 232, 233, 100, 234, 14]
```

... the message whose HMAC is above as being:

```
BRC-2 HMAC Compliance Validated!
```

## Implementations

* This encryption capability is incorporated into current `bsv-blockchain/ts-sdk` BRC-100 wallet interfaces and `bsv-blockchain/wallet-toolbox` permission enforcement. Older Babbage SDK references are retained only for historical context.

## References

* 1: [MoneyButton Encryption](https://github.com/moneybutton/docs/blob/master/docs/mb-encryption.md)
* 2: [BaeMail](https://baemail.me/)


# Digital Signature Creation and Verification

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define a mechanism for requesting and receiving digital signatures over the abstract communications channel first described by [BRC-1](/wallet/0001). We rely on the [BRC-43](/key-derivation/0043) invoice numbering and permission scheme on top of [BRC-42](/key-derivation/0042) for key derivation, enabling the creation of private digital signatures that can only be verified by the counterparty. A signer derives their own child private key and uses it to compute an ECDSA signature, which is then communicated to the verified, together with the signer's identity key, the message, and the protocol ID and key ID used. The verifier uses this information to derive the corresponding child public key for the signer, which is then checked against the signature and message with ECDSA. Without the [BRC-42](/key-derivation/0042) shared secret, no third parties can discover the correct child public key, making then unable to validate the signature.

## Motivation

The increasing use of Bitcoin wallets has highlighted the need for a secure and interoperable digital signature standard that can be used across different applications. Digital signatures provide a vital aspect of data authentication, allowing for the verification of information and secure transactions, and are critical to ensuring the integrity of the Bitcoin network.

While other solutions have proposed digital signatures within a wallet, none of them have provided a unified and open standard that supports both privately and publicly verifiable signatures, while also incorporating proper [BRC-42](/key-derivation/0042) key derivation. The lack of such a standard has created a fragmentation within the ecosystem, with each application requiring its own solution, thus hindering interoperability and leading to duplicated effort.

To address this issue, the BRC-3 standard has been created to enable the creation and verification of digital signatures using [BRC-43](/key-derivation/0043) invoice numbering and permission scheme on top of [BRC-42](/key-derivation/0042) for key derivation. This allows for the creation of private digital signatures that can only be verified by the intended counterparty, while maintaining security against third-party tampering.

The BRC-3 standard not only provides a secure and interoperable solution for wallet implementations, but it also enables new use cases and experiences that were previously not possible. With a unified standard, different wallets can create and verify each other's digital signatures seamlessly, reducing friction and enabling greater innovation.

## Status Note

The key-derivation signature scheme in this document remains the basis for wallet `createSignature` and `verifySignature`, but the BRC-1 message framing and legacy SDK references are historical. Current implementations expose these operations through [BRC-100](/wallet/0100), using `protocolID`, `keyID`, `counterparty`, optional `privileged` controls, `seekPermission`, and byte-array payloads.

## Specification

We start with the same constructs defined in [BRC-43](/key-derivation/0043): users with clients, protocols and applications. We stipulate the use of [BRC-43](/key-derivation/0043) invoice numbers in the context of [BRC-42](/key-derivation/0042) key derivation, and we build on top of the permissions architecture defined by [BRC-43](/key-derivation/0043).

When an application sends a message to a client requesting that data be signed, the message comprises:

* The [BRC-43](/key-derivation/0043) security level, protocol ID, key ID and counterparty to facilitate [BRC-42](/key-derivation/0042) key derivation and permissioning
* The data to sign

We stipulate the following process for digital signature creation:

* The message signer begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The message signer uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive their own child private key
* The message signer computes the digital signature using ECDSA with their derived child private key (this specification is silent about ECDSA k-value utilization)

We stipulate the following process for message verification:

* The verifier somehow comes to know the signature, the counterparty (signer), the security level, protocol ID, and key ID. The mechanism for conveying this information to the verifier is beyond the scope of this specification.
* The verifier begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The verifier uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number, their own private key and the public key of the sender, to compute the signer's child public key
* The verifier uses ECDSA to verify the signature against the message using the signer's child public key

We build upon the abstract messaging layer first described in [BRC-1](/wallet/0001). Specifically, we define five new [BRC-1](/wallet/0001) messages to facilitate requests and responses for signatures and verification operations, and error handling. For each of the messages, we stipulate that there exists some out-of-band mechanism for the parties to communicate which of the messages are being exchanged, removing the need for a message type field. Finally, specifically for the request messages, we stipulate that the message comprises a header and a payload, with the payload containing the data (ciphertext or plaintext), and the header containing the other information. For the response messages, no message header is defined and the payload simply contains the specified data.

### Signature Creation Request

The signature creation request is a message sent by the [BRC-43](/key-derivation/0043) application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, `anyone` or `self`                                                                                                                                                                              |

The message payload comprises the data to sign.

### Signature Creation Response

The response message comprises the ECDSA digital signature in DER format.

### Signature Verification Request

The signature verification request is a message sent by the application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, `self` or `anyone`                                                                                                                                                                              |
| `signature`    | The DER-formatted signature for verification                                                                                                                                                                                                     |

The message payload comprises the data to verify.

### Signature Verification Response

The response message comprises a JSON payload containing the following fields:

| Field    | Description                                                             |
| -------- | ----------------------------------------------------------------------- |
| `result` | The value is `true` if the signature is valid, otherwise it is `false`. |

### Signature Error

If the client is unable to fulfill the signature creation or verification requests for any reason, we specify that it should respond with a JSON-formatted Signature Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                               |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                      |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_VERIFICATION_FAILED"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                      |

One example of a Signature Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "You have denied permission for signing this data."
}
```

## Test Vectors

For compatibility with this signature scheme, we stipulate the following:

Any user who knows the following identity private key (counterparty=**anyone**)...

```
0000000000000000000000000000000000000000000000000000000000000001
```

...should be able to use security level `2`, the `BRC3 Test` protocolID with keyID `42` and the following counterparty (signer)...

```
0294c479f762f6baa97fbcd4393564c1d7bd8336ebd15928135bbcf575cd1a71a1
```

...to verify the message with the following digital signature (DER format)...

```
[48, 68, 2, 32, 43, 34, 58, 156, 219, 32, 50, 70, 29, 240, 155, 137, 88, 60, 200, 95, 243, 198, 201, 21, 56, 82, 141, 112, 69, 196, 170, 73, 156, 6, 44, 48, 2, 32, 118, 125, 254, 201, 44, 87, 177, 170, 93, 11, 193, 134, 18, 70, 9, 31, 234, 27, 170, 177, 54, 96, 181, 140, 166, 196, 144, 14, 230, 118, 106, 105]
```

The message that was signed is:

```
BRC-3 Compliance Validated!
```

## Implementations

* This digital signature capability is incorporated into current `bsv-blockchain/ts-sdk` BRC-100 wallet interfaces and `bsv-blockchain/wallet-toolbox` permission enforcement. Older Babbage SDK references are retained only for historical context.


# Input Redemption

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define a mechanism by which [BRC-1](/wallet/0001) applications can denote UTXOs to be unlocked and redeemed by wallets as part of new Bitcoin transactions. We extend the message format defined by [BRC-1](/wallet/0001) with information about the inputs requested by the application, including all information needed for a wallet to check the veracity of any inputs being redeemed.

## Motivation

[BRC-1](/wallet/0001) defines a mechanism for an application to request the creation of a Bitcoin transaction by a wallet, but it is incomplete without a way for applications to consume and use tokens that previously existed. Allowing applications to unlock and redeem inputs as part of their transactions also facilitates their ability to update, extend or delete assets that are tokenized within Bitcoin UTXOs.

## Status Note

BRC-4 is historical. Current wallets consume application-provided inputs through [BRC-100](/wallet/0100) `createAction` with `inputBEEF`, `inputs[].outpoint`, `inputs[].unlockingScript` or `inputs[].unlockingScriptLength`, `inputs[].inputDescription`, and optional `sequenceNumber`. Extended BRC-8 input envelopes are deprecated for current implementations.

## Specification

We extend the [BRC-1](/wallet/0001) Transaction Creation Request message with an additional field, the `inputs` field. This is an object whose keys are the TXIDs of transactions that contain outputs which are to be spent as part of this transaction, and whose values comprise extended [BRC-8](/transactions/0008) transaction envelopes.

In addition to the normal envelope fields, we specify that these input envelopes contain an additional field called `outputsToRedeem`, which is an array of objects. Each of the objects comprises an output from the subject transaction that is to be redeemed and used as input to the transaction being requested by the application.

We specify that each of the objects in the array contains `index` and `unlockingScript`. The `index` value is an integer that denotes which output to redeem from the transaction, and the `unlockingScript` comprises a [BRC-14](/scripts/0014) hex-formatted input script.

We further specify that each of the elements in each of the `outputsToRedeem` arrays may contain an additional `spendingDescription` string that describes the redemption of the tokens being used.

### Example

Here is an example of a Transaction Creation Request object that contains an input:

```json
{
    "inputs": {
      "900b7e9ced44f8a7605bd4c1b7054b8fb958385998954d772820a8b81eabb56f": {
        "rawTx": "010000000135620d3a8fb626b763cb7b9a3c3197eda9bd6709ef1e4ebc2b359607800eaa95020000006b483045022100c3ec1792f1780e453c6ea692271bcc5388fb179d6455897f4b4200706e8b9f150220352cc2ff81a5c698e4626aec8976643134efab91ca1c80729670c2d007c77cfd4121037798f038cb7fc18b9d67baa87ed33122eb7be65b95b0dd304dc476c60043773dffffffff03e803000000000000c4210399322f558d92ced9c45d3bfe7dc5c01b830a4b61d71695ab0a1fa2cd90aba8b9ac2131546f446f44744b7265457a6248594b466a6d6f42756475466d53585855475a473462812d4eeb030fa496c2965f7e0f0b0e825d0547aceb7a9d4c76fd17e795753d8e2e0e82274ab36744a7968a43dc45b1cbdfab6c473045022100a58914da5346960ecb4de964f0f1e1be43a7645a11449c3a209f3fd69b34209b02205d4d4294d04c5f87fb16c2cdc3d9b41f9866fb3d7a690da08089c972d1393d816d75c8000000000000001976a91473a95c0b12e33b3d79f80508200a075bbab0906588ac4ea80200000000001976a91478daf10df51b3ea4c9292a72bf2e1e1d48ebe11288ac00000000",
      "proof": {
          "index": 2,
        "txOrId": "900b7e9ced44f8a7605bd4c1b7054b8fb958385998954d772820a8b81eabb56f",
        "targetType": "header",
        "target": "00000020ed40984238c0d6ed157cfd4fda4b284b48bb5c3b00b3172a2b030000000000006dfcabcc55b822548d10198ad1ddc62700947c8053213bf254b5229c92b9f448c6d32f64f13d051aa8201615",
        "nodes": [
            "9c97050e4c5fcc7f3ae7f7de83668d1968a626d70690654dab0398b75ca447a1",
          "fde37bbbec5cb48c085749aea7059412cc59c339bbc3e98960c081632382d538"
        ]
      },
      "outputsToRedeem": [
          {
            "index": 0,
          "unlockingScript": "473044022040ac4ef063c5139ef940860ec993dea2b38f2226cb841c231d60833d185934d50220760c48202fc150e3090ffbda4ccfe4ebce36715b4396d3555fb2f3dc03346e5cc2",
          "spendingDescription": "Complete a ToDo list item"
        }
      ]
    }
  },
  "description": "Complete a TODO task: \\"test\\""
}
```

## Implementations

This functionality is implemented by current `bsv-blockchain/ts-sdk` BRC-100 wallet interfaces and `bsv-blockchain/wallet-toolbox` storage/action processing.


# HTTP Wallet Communications Substrate

* Brayden Langley (<brayden@projectbabbage.com>)
* Ty Everett (<ty@projectbabbage.com>)

## Abstract

The Bitcoin Wallet HTTP Interface is a standard interface that enables applications to connect with Bitcoin wallets to facilitate certain functionality. The interface provides a unified way for applications to request the creation of a Bitcoin transaction, encryption, digital signature creation, and other features provided by the wallet. By standardizing the interface, applications can support multiple wallets and give the user greater control over their Bitcoin-related activities.

## Motivation

The motivation for this standard interface is to provide a common way for applications to connect with Bitcoin wallets. Currently, many Bitcoin wallets provide their own APIs, which makes it difficult for applications to support multiple wallets. The Bitcoin Wallet HTTP Interface standardizes the way that applications can interact with wallets, enabling them to be more easily integrated into various applications. This interface is designed to be flexible enough to support a range of wallets and use cases, while also providing a secure and standardized method of communication.

## Status Note

This document originally described the pre-BRC-100 localhost HTTP API. The `/v1/...` routes, mixed GET/POST method table, and legacy method names such as `createCertificate` and `findCertificates` are historical. Current interoperable implementations in `bsv-blockchain/ts-sdk` expose the [BRC-100](/wallet/0100) wallet interface over HTTP substrates without a versioned URL prefix.

Implementers SHOULD use the current substrates below for new work. The older route table is deprecated except where a deployment explicitly maintains backwards compatibility with legacy wallets.

## Specification

Current wallet HTTP interoperability is defined by two localhost substrates for the BRC-100 wallet interface:

1. **Binary wallet wire**
   * Default base URL: `http://localhost:3301`
   * Route: `POST /<BRC-100 methodName>`
   * Request body: `application/octet-stream`
   * Body framing: the BRC-100 wallet-wire parameter payload for the selected method, excluding the method call code and originator frame that are used by the client-side substrate to choose the route and `Origin` header.
   * Originator: conveyed through the HTTP `Origin` header when available.
2. **JSON wallet API**
   * Default base URL: `http://localhost:3321`
   * Secure development base URL: `https://localhost:2121`
   * Route: `POST /<BRC-100 methodName>`
   * Request body: JSON arguments for the BRC-100 method
   * Response body: JSON result
   * Originator: Node clients may set `Origin` and `Originator`; browser clients rely on the browser-managed `Origin` header.

No `/v1` prefix is used by the current `ts-sdk` HTTP wallet substrates.

### Standard HTTP Routes

Every current BRC-100 wallet method is addressed by its method name:

| Route                           | Request Method | Substrate           |
| ------------------------------- | -------------- | ------------------- |
| `/createAction`                 | POST           | Binary wire or JSON |
| `/signAction`                   | POST           | Binary wire or JSON |
| `/abortAction`                  | POST           | Binary wire or JSON |
| `/listActions`                  | POST           | Binary wire or JSON |
| `/internalizeAction`            | POST           | Binary wire or JSON |
| `/listOutputs`                  | POST           | Binary wire or JSON |
| `/relinquishOutput`             | POST           | Binary wire or JSON |
| `/getPublicKey`                 | POST           | Binary wire or JSON |
| `/revealCounterpartyKeyLinkage` | POST           | Binary wire or JSON |
| `/revealSpecificKeyLinkage`     | POST           | Binary wire or JSON |
| `/encrypt`                      | POST           | Binary wire or JSON |
| `/decrypt`                      | POST           | Binary wire or JSON |
| `/createHmac`                   | POST           | Binary wire or JSON |
| `/verifyHmac`                   | POST           | Binary wire or JSON |
| `/createSignature`              | POST           | Binary wire or JSON |
| `/verifySignature`              | POST           | Binary wire or JSON |
| `/acquireCertificate`           | POST           | Binary wire or JSON |
| `/listCertificates`             | POST           | Binary wire or JSON |
| `/proveCertificate`             | POST           | Binary wire or JSON |
| `/relinquishCertificate`        | POST           | Binary wire or JSON |
| `/discoverByIdentityKey`        | POST           | Binary wire or JSON |
| `/discoverByAttributes`         | POST           | Binary wire or JSON |
| `/isAuthenticated`              | POST           | Binary wire or JSON |
| `/waitForAuthentication`        | POST           | Binary wire or JSON |
| `/getHeight`                    | POST           | Binary wire or JSON |
| `/getHeaderForHeight`           | POST           | Binary wire or JSON |
| `/getNetwork`                   | POST           | Binary wire or JSON |
| `/getVersion`                   | POST           | Binary wire or JSON |

The method names, argument structures, result structures, and binary call codes are specified normatively in [BRC-100](/wallet/0100).

### JSON Code Example

```javascript
const httpResult = await makeHttpRequest(
  'http://localhost:3321/createAction',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      description: 'Create a wallet action',
      outputs: []
    })
  }
)
```

### Binary-Wire Code Example

```javascript
const httpResult = await makeHttpRequest(
  'http://localhost:3301/createAction',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/octet-stream'
    },
    body: walletWireParameterBytes
  }
)
```

## Implementation

Applications and wallets should implement the current BRC-100 method set and one or more of the current HTTP substrates above. The current reference implementation is the `bsv-blockchain/ts-sdk` wallet substrate implementation.


# XDM Wallet Communications Substrate

Ty Everett (<ty@projectbabbage.com>)

## Abstract

Cross-document messaging enables web-based applications to communicate with one another in a secure manner, without allowing the two applications to manipulate each other's DOM trees. It defines a mechanism by which messages can be sent and received, with browser-based attestation of the origin of each message. We define the framework and conventions for operating a [BRC-56](/wallet/0056) wallet over XDM, enabling a parent page that runs a wallet to communicate with one or multiple child pages that run applications.

## Motivation

[BRC-56](/wallet/0056) defines a suite of abstract messages used by applications and wallets to facilitate various Bitcoin and MetaNet operations that enable micropayments, protect user privacy and ensure secure authentication without the need for each application to maintain a separate user account. While [BRC-5](/wallet/0005) defines a method of using a [BRC-56](/wallet/0056) wallet over HTTP on the local machine, some mobile devices are unable to support running HTTP servers due to platform-specific limitations. Additionally, it is sometimes desirable for a MetaNet environment to run fully in the browser, enabling users to access their identities on devices or platforms where a desktop-based [BRC-5](/wallet/0005) client cannot be installed. This specification provides a ubiquitous communications substrate enabling the use of [BRC-56](/wallet/0056) functionality across a wide range of devices and deployment contexts, including fully in-browser experiences.

## Status Note

The XDM substrate concept remains relevant, but this document's BRC-56 method table is historical. Current XDM-style wallet bridges SHOULD expose the [BRC-100](/wallet/0100) wallet method set. Legacy call names such as `createCertificate` and `ninja.findCertificates` are deprecated for new implementations.

## Specification

We specify that the parent page runs the wallet, responding to messages from child pages. This has several advantages:

* The wallet can always pop-up any necessary permission popups or user-interactive authorization screens without interference from client pages
* When the user visits the parent page, they log in once, then they can access any applications after login
* Multiple applications, all running in the same parent page, can access and utilize the [BRC-56](/wallet/0056) wallet at once
* Reducing the number of wallets running in parallel reduces the chances of UTXO synchronization or corruption issues
* It is possible to run the parent wallet page locally, connecting to remote services only when required by specific applications
* If a specific application was the parent and the wallet was a child, failure of the parent page to respond could constitute failure of a user to access their identity or assets, which might otherwise be available through another application

### Application Message Sending and Processing

We start with the message relay interface defined by XDM. JavaScript code for sending messages from the application to the wallet is as follows:

```js
new Promise((resolve, reject) => {
  const id = 'abcdabcd' // get a random message ID
  window.addEventListener('message', async e => {
    if (e.data.type !== 'CWI' || !e.isTrusted || e.data.id !== id || e.data.isInvocation) return
    if (e.data.status === 'error') {
      const err = new Error(e.data.description)
      err.code = e.data.code
      reject(err)
    } else {
      resolve(e.data.result)
    }
  })
  window.parent.postMessage({
    type: 'CWI',
    isInvocation: true,
    id,
    call: 'getVersion',
    params: {}
  }, '*')
})
```

We stipulate that:

1. All messages (requests, responses and errors) have a `type` property equal to `CWI` (this value, which stands for Computing with Integrity, is historical)
2. A random message ID is generated by the application
3. The application listens for new, incoming response messages. As part of the listener, the application:
   * Drops any events without the correct `type` in the event data
   * Drops any events where `isTrusted` is not `true`
   * Drops any events where the event data contains the `isInvocation` flag, denoting any outgoing messages that were echoed back
   * Drops any events where the event data contains an `id` field other than the one generated by step 2
   * Handles any error messages if the event's data contains a `status` field equal to `error`, relying on the `code` and `description` fields to construct an appropriate error
   * Otherwise, if no errors are detected, the application is now free to make use of the `result` field from the event's data payload, which will be the response from the wallet as specified by the relevant BRC standard for the specific message being sent
4. With the listener in place and ready to process the response when it arrives, the application now constructs and sends the message to the wallet through the parent window:
   * Like all messages, the outgoing message contains a `type` field equal to `CWI`
   * An `isInvocation` flag is set to `true`, allowing listeners to easily drop outgoing messages rather than trying to process them as responses
   * The `id` that was generated in step 2 is included. The same `id` must be used by wallets when sending back the response
   * The `call` determines which message is being sent. Specific `call` values are defined below
   * The `params` field comprises the specific parameters as specified in BRCs that define specific message types

This application-side interface facilitates exchanging and receiving messages with the [BRC-56](/wallet/0056) wallet over XDM. We now proceed to how the wallet handles its side of the interaction.

### Wallet Message Receipt, Processing and Response

The wallet listens for incoming messages and replies to the originator with appropriate responses after obtaining permission from the user (if applicable) and processing the request. Some JavaScript code that implements this functionality is provided:

```js
window.addEventListener('message', async e => {
  if (e.data.type !== 'CWI' || !e.isTrusted || typeof e.data.call !== 'string') return

  // This is where the wallet will do its processing, based on `call` and `params`.

  // ... in a rudamentary implementation ...

  if (e.data.call === 'createAction') { // BRC-1
    try {
      let result = await doBRC1Thing({
        ...e.data.params,
        originator: e.origin // You might let BRC1Thing know which app is sending the request, for permission purposes
      })
      e.source.postMessage({
        type: 'CWI', result, id: e.data.id
      }, e.origin)
    } catch (error) {
      e.source.postMessage({
        type: 'CWI',
        id: e.data.id,
        status: 'error',
        code: error.code || 'ERR_UNKNOWN',
        description: error.message
      }, e.origin)
    }
  } else if (e.data.call === 'encrypt') { // BRC-2 encrypt
    try {
      let result = await doBRC2Thing({
        ...e.data.params,
        originator: e.origin // You might let BRC2Thing know which app is sending the request, for permission purposes
      })
      e.source.postMessage({
        type: 'CWI', result, id: e.data.id
      }, e.origin)
    } catch (error) {
      e.source.postMessage({
        type: 'CWI',
        id: e.data.id,
        status: 'error',
        code: error.code || 'ERR_UNKNOWN',
        description: error.message
      }, e.origin)
    }
  } // ... implement all functions ...

})
```

We stipulate, before any client applications are loaded which might send any messages to the wallet, that the wallet running on the parent page must bind a message event handler that:

1. Upon receipt of a message with an event data field `type` not equal to `CWI` will drop the message
2. Upon receipt of an untrusted message, or one without a `call` will drop the message
3. Upon receipt of a message with an unknown or unsupported `call` will proceed to step 6
4. Based on the `call` and `params` will perform the necessary steps as required by the relevant BRC specifications for the specific operation
5. Compose a response message and send it back to the originator, the response message comprising an event payload with the following fields:
   * A `type` value of `CWI`
   * The `id` that was specified by the application when the message was created
   * A `result` value that is the result of the operation being performed, as specified by the particular operation
6. In case of any errors with the operation, the wallet will instead send back a response message comprising an event payload with the following fields:
   * A `type` value of `CWI`
   * A `status` value of `error`
   * The `id` that was specified by the application when the message was created
   * A relevant `code` for the error, as specified by the particular operation in question
   * A human-readable `description` for the error

### Values for `call` Associated with Various Message Types

For each of the message pairs (request and response) incorporated into [BRC-56](/wallet/0056), we specify the existence of a corresponding XDM message pair with a specific `call` value:

| Message Pair                                    | `call` Value             | Specific Implementation Notes                                      |
| ----------------------------------------------- | ------------------------ | ------------------------------------------------------------------ |
| [BRC-1](/wallet/0001) Transaction Creation      | `createAction`           |                                                                    |
| [BRC-2](/wallet/0002) Encryption                | `encrypt`                |                                                                    |
| [BRC-2](/wallet/0002) Decryption                | `decrypt`                |                                                                    |
| [BRC-3](/wallet/0003) Signature Creation        | `createSignature`        |                                                                    |
| [BRC-3](/wallet/0003) Signature Verification    | `verifySignature`        |                                                                    |
| [BRC-53](/wallet/0053) Certificate Creation     | `createCertificate`      |                                                                    |
| [BRC-53](/wallet/0053) Certificate Verification | `proveCertificate`       |                                                                    |
| [BRC-56](/wallet/0056) HMAC Creation            | `createHmac`             |                                                                    |
| [BRC-56](/wallet/0056) HMAC Verification        | `verifyHmac`             |                                                                    |
| [BRC-56](/wallet/0056) Public Key Derivation    | `getPublicKey`           |                                                                    |
| [BRC-56](/wallet/0056) Certificate List         | `ninja.findCertificates` | This call name prefix is historical and retained for compatibility |
| [BRC-56](/wallet/0056) Version Request          | `getVersion`             |                                                                    |
| [BRC-56](/wallet/0056) Network Request          | `getNetwork`             |                                                                    |
| [BRC-56](/wallet/0056) Authentication Request   | `isAuthenticated`        |                                                                    |
| [BRC-56](/wallet/0056) Async Auth Request       | `waitForAuthentication`  |                                                                    |

## Implementations

Implementers of applications and wallets should create and process XDM messages in the manner described, using [BRC-100](/wallet/0100) method names and payloads for current interoperability. Older Babbage SDK references are retained only for historical context.

Implementation questions should be directed to the author.

One (crude) example of a deployed architecture in which a parent page uses XDM to communicate with child application pages, facilitating the operation of multiple client applications which communicate with the parent wallet, has been implemented by the Babbage team at [BabbageOS.com](https://babbageos.com).


# Window Wallet Communication Substrate

Brayden Langley (<brayden@projectbabbage.com>)

## Abstract

The Window Wallet Communication Substrate is a standardized interface that enables seamless integration between web applications and browser embedded Bitcoin wallets. It serves as a unified gateway for applications to access various wallet functionalities, including the creation of Bitcoin transactions, encryption, digital signature creation, and more. By establishing this standardized interface, applications gain the ability to support multiple wallet providers, enhancing flexibility and choice for users in managing their Bitcoin-related tasks. This interface empowers users with greater control and accessibility while maintaining compatibility across different applications and wallets.

## Motivation

The motivation behind this standard is to enable web browsers to directly integrate Bitcoin wallet functionality without relying on an additional application running on the client's device.

Although [BRC-5](/wallet/0005) defines a standard for local communication over HTTP, integrating wallet functionality in the browser eliminates the need for external wallet applications, reducing the overhead of inter-process communication and network requests.

This also eliminates the need for users to switch between multiple applications when approving permissions, creating transactions, etc. They can perform all wallet-related tasks within the web application they are already using, resulting in a more cohesive and convenient user experience.

## Status Note

The `window.CWI` substrate remains relevant, but this document's BRC-56 function table is historical. Current browser wallet injections SHOULD expose the [BRC-100](/wallet/0100) wallet method set. Legacy function names such as `window.CWI.createCertificate()` and `window.CWI.findCertificates` are deprecated for new implementations; use `acquireCertificate` and `listCertificates`.

## Specification

We define a specification for providing access to Bitcoin wallet functionality via the global window object that is directly available in all standard browser implementations.

Once the browser has verified that the user is authenticated, an object labeled `CWI` should be added to the window object to provide access to the standard wallet functionality as defined by [BRC-56](/wallet/0056).

### Standard CWI Functions Associated with Various Message Types

For each of the message pairs (request and response) incorporated into [BRC-56](/wallet/0056), we specify the existence of a corresponding message type with a specific function name:

| Message Type                                                                  | Window Function Name Value           |
| ----------------------------------------------------------------------------- | ------------------------------------ |
| [BRC-1 Transaction Creation](/wallet/0001#transaction-creation-request)       | `window.CWI.createAction()`          |
| [BRC-2 Encryption](/wallet/0002#encryption-request)                           | `window.CWI.encrypt()`               |
| [BRC-2 Decryption](/wallet/0002#decryption-request)                           | `window.CWI.decrypt()`               |
| [BRC-3 Signature Creation](/wallet/0003#signature-creation-request)           | `window.CWI.createSignature()`       |
| [BRC-3 Signature Verification](/wallet/0003#signature-verification-request)   | `window.CWI.verifySignature()`       |
| [BRC-53 Certificate Creation](/wallet/0053#certificate-creation-request)      | `window.CWI.createCertificate()`     |
| [BRC-53 Certificate Verification](/wallet/0053#certificate-proof-request)     | `window.CWI.proveCertificate()`      |
| [BRC-56 HMAC Creation](/wallet/0056#hmac-creation-request)                    | `window.CWI.createHmac()`            |
| [BRC-56 HMAC Verification](/wallet/0056#hmac-verification-request)            | `window.CWI.verifyHmac()`            |
| [BRC-56 Public Key Derivation](/wallet/0056#public-key-request)               | `window.CWI.getPublicKey()`          |
| [BRC-56 Certificate List](/wallet/0056#certificate-list-request)              | `window.CWI.findCertificates`        |
| [BRC-56 Version Request](/wallet/0056#client-version-request)                 | `window.CWI.getVersion()`            |
| [BRC-56 Network Request](/wallet/0056#bitcoin-network-request)                | `window.CWI.getNetwork()`            |
| [BRC-56 Authentication Request](/wallet/0056#authentication-status-request)   | `window.CWI.isAuthenticated()`       |
| [BRC-56 Async Auth Request](/wallet/0056#asynchronous-authentication-request) | `window.CWI.waitForAuthentication()` |

This will allow applications to call functions with the following syntax:

```javascript
window.CWI.<functionName>
```

### Parameter Format Specification

We specify that all *required* parameters are provided in an object to the CWI functions.

**Example Identity Key Request**

```javascript
const identityKey = await window.CWI.getPublicKey({ 
    identityKey: true 
})
```

**Example Encrypt Function**

```javascript
const ciphertext = await window.CWI.encrypt({
  plaintext: Buffer.from('Hello BRCs!'),
  protocolID: [0, 'Hello World'],
  keyID: '1'
})
```

## Implementation

Implementors of this BRC should follow the current wallet method set in [BRC-100](/wallet/0100), then expose those methods on the window object in a CWI-compatible object where this substrate is used.

<img src="/files/DFUBR9zkdfM4eLR1y8St" alt="" width="500">


# Wallet Transaction Output Tracking (Output Baskets)

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define an extension to [BRC-1](/wallet/0001) that enables a wallet to facilitate the tracking of specific application-defined transaction outputs within baskets. A new set of messages across the abstract messaging interface facilitates applications' access to unspent outputs stored in these baskets, with a permission system similar to that described in [BRC-43](/key-derivation/0043) employed to regulate access by applications. Spending an output stored in a basket removes it, while new outputs can be added by specifying their basket as part of [BRC-1](/wallet/0001) transaction creation requests.

## Motivation

Transaction outputs in Bitcoin take many forms, and serve many purposes. While [BRC-1](/wallet/0001) defines a way for applications to request the creation of transaction outputs by wallets, there is no way for applications to request that a wallet tracks these outputs. Enabling applications to request that wallets track outputs provides a number of advantages: First, applications may no longer need to rely on external data storage and retrieval systems, simplifying their architecture. Second, there is the potential to represent different types of Bitcoin-native tokens within specific baskets, and define protocols for manipulating specific types of tokens based on which baskets they are stored in. Finally, when permission to access a basket is decided by the user on a per-application basis, it facilitates a greater degree of control for users over their tokens, enabling multiple applications to access and use the same tokens.

## Status Note

This specification remains the conceptual basis for wallet output baskets, but the legacy BRC-1/BRC-8 message shapes below have been superseded by the [BRC-100](/wallet/0100) wallet interface. New implementations MUST use BRC-100 `createAction`, `internalizeAction`, `listOutputs`, and `relinquishOutput` semantics for basket operations.

The historical fields `includeEnvelope`, `amount`, `txid`, `vout`, `outputScript`, and `envelope` are deprecated in this context. Current wallet implementations return `outpoint`, `satoshis`, optional `lockingScript`, optional `customInstructions`, and optional [BRC-62](/transactions/0062) BEEF data through BRC-100.

## Specification

### Basket Naming

A basket name is an application-defined identifier for a wallet-managed set of tracked outputs. In current BRC-100 implementations, basket identifiers are normalized by trimming whitespace and lowercasing the value, and must be between 1 and 300 UTF-8 bytes.

Basket names beginning with `p` are reserved for future or installed permission modules as defined by [BRC-99](/wallet/0099). Wallets may also reserve implementation-internal admin basket names; wallet-toolbox uses `admin ...` baskets for permission-token storage and denies non-admin access.

### Inserting Outputs During Transaction Creation

Applications insert new transaction outputs into baskets by providing `basket` on a BRC-100 `createAction` output:

```json
{
  "description": "Create a todo token",
  "outputs": [
    {
      "lockingScript": "76a914...88ac",
      "satoshis": 1,
      "outputDescription": "Todo token",
      "basket": "todo tokens",
      "customInstructions": "{\"unlock\":\"context\"}",
      "tags": ["open"]
    }
  ]
}
```

The optional `customInstructions` value is retained by the wallet as application metadata for later spending. The optional `tags` array supports filtering and categorization within the basket.

### Internalizing Existing Outputs Into Baskets

Applications or services can import an existing Atomic BEEF transaction using BRC-100 `internalizeAction`. To insert a specific output into a basket, the output metadata uses:

```json
{
  "outputIndex": 0,
  "protocol": "basket insertion",
  "insertionRemittance": {
    "basket": "todo tokens",
    "customInstructions": "{\"unlock\":\"context\"}",
    "tags": ["open"]
  }
}
```

The only current `internalizeAction` protocol strings are `wallet payment` and `basket insertion`.

### Listing Outputs

Applications list basketed outputs with BRC-100 `listOutputs`.

| Field                       | Required | Description                                                             |
| --------------------------- | -------- | ----------------------------------------------------------------------- |
| `basket`                    | yes      | Basket whose tracked outputs should be returned                         |
| `tags`                      | no       | Tags used to filter outputs                                             |
| `tagQueryMode`              | no       | `any` or `all`; defaults to `any`                                       |
| `include`                   | no       | `locking scripts` or `entire transactions`                              |
| `includeCustomInstructions` | no       | Include stored custom instructions                                      |
| `includeTags`               | no       | Include output tags                                                     |
| `includeLabels`             | no       | Include labels on the containing actions                                |
| `limit`                     | no       | Maximum number of outputs to return; current validation defaults to 10  |
| `offset`                    | no       | Offset into the result set; negative offsets count back from the end    |
| `seekPermission`            | no       | Whether the wallet may seek user permission if needed; defaults to true |

Example:

```json
{
  "basket": "todo tokens",
  "tags": ["open"],
  "tagQueryMode": "all",
  "include": "locking scripts",
  "includeCustomInstructions": true,
  "includeTags": true,
  "limit": 25,
  "offset": 0
}
```

The BRC-100 response is:

```json
{
  "totalOutputs": 1,
  "outputs": [
    {
      "outpoint": "900b7e9ced44f8a7605bd4c1b7054b8fb958385998954d772820a8b81eabb56f.0",
      "satoshis": 1000,
      "lockingScript": "76a914...88ac",
      "spendable": true,
      "customInstructions": "{\"unlock\":\"context\"}",
      "tags": ["open"]
    }
  ]
}
```

When `include` is `entire transactions`, the response may also contain a `BEEF` property carrying transaction data needed to validate or inspect the returned outputs.

### Removing Outputs From Basket Tracking

Applications relinquish wallet tracking for a basketed output with BRC-100 `relinquishOutput`:

```json
{
  "basket": "todo tokens",
  "output": "900b7e9ced44f8a7605bd4c1b7054b8fb958385998954d772820a8b81eabb56f.0"
}
```

Relinquishment removes the wallet's basket tracking for the output; it is not a Bitcoin spend by itself.

### Permission Behavior

Wallets may require user authorization before allowing an originator to insert, list, or relinquish basket outputs. Current wallet-toolbox permission behavior is specified in [BRC-116](/wallet/0116), including grouped basket permissions, admin basket restrictions, and P-basket module delegation.

## Implementations

This functionality is implemented by current `bsv-blockchain/ts-sdk` BRC-100 wallet interfaces and `bsv-blockchain/wallet-toolbox` storage and permission layers.


# Submitting Received Payments to a Wallet

Brayden Langley (<brayden@projectbabbage.com>)

## Abstract

To ensure a seamless process for submitting payments to a wallet, there must be a standardized method by which this process takes place. Wallets that implement this standard will be able to support the direct submission of transactions from applications to a user's wallet along with the associated SPV information as defined by [BRC-9](/transactions/0009). The required message structure and fields are defined below in the [Payment Submission Message](#payment-submission-message) section, and the expected response to provide is defined in the [Payment Acknowledgment Message](#payment-acknowledgment-message) section. Wallets that choose to implement this specification will allow applications that facilitate the exchange of payments to be built in a way that is interoperable, extensible, and SPV compliant.

## Motivation

BRC-50 standardizes payment submission to a wallet, improving the user experience for incoming Bitcoin payments. This enables higher-layer applications to add funds to a user's wallet without needing to maintain their own external balance. By standardizing payment submission, developers can create interoperable, extensible, and SPV compliant applications. This ensures a seamless and secure payment process, benefiting both wallet providers and end-users.

## Status Note

BRC-50 is historical. Current incoming transaction submission is specified by [BRC-100](/wallet/0100) `internalizeAction`. For BRC-29 payments, the current `outputs[].protocol` value is `wallet payment`; for basket insertions, it is `basket insertion`. The legacy `submitDirectTransaction` route and BRC-8 envelope payload are deprecated for new implementations.

## Specification

For this specification, we assume that there exists a wallet that facilitates a channel by which communication can occur with an application, such as over HTTP as defined in [BRC-5](/wallet/0005). Based on this premise, we specify a standard by which payments can be submitted from an application to a wallet which then receives and processes the payment.

### Payment Submission Message

We define an extension to the abstract [BRC-1](/wallet/0001) messaging layer as the Payment Submission Message which comprises a JSON object with the following fields:

> Several of the same fields as defined in [BRC-29](/payments/0029) are present in this message. This standard essentially facilitates an implementation of [BRC-29](/payments/0029) over an abstract, wallet-to-application interface.

| Field               | Type / Required    | Description                                                                                                                                                                                                           |
| ------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`          | (string, required) | This field denotes that the JSON object comprises a payment message according to the given protocol (such as `3241645161d8` for [BRC-29](/payments/0029)).                                                            |
| `transaction`       | (object, required) | The [BRC-8](/transactions/0008) transaction envelope to submit, including key derivation information (the "Outputs Extension" as defined in [BRC-29](/payments/0029))                                                 |
| `senderIdentityKey` | (string, required) | The recipient will need to know the public identity key of the sender in order to validate the incoming payment. This field's value should be the 33-byte, compressed, hex-encoded secp256k1 public key of the sender |
| `derivationPrefix`  | (string, required) | This field denotes the payment-wide derivation prefix used by the sender when the keys for the payment UTXOs were derived                                                                                             |
| `note`              | (string, required) | Human-readable description for the transaction.                                                                                                                                                                       |
| `amount`            | (number, optional) | Amount of satoshis associated with the transaction. If provided, it is used to verify that the amount returned from processing the transaction matches the payment amount.                                            |

The transaction field is a JSON object that conforms to the [BRC-8](/transactions/0008), with the [BRC-29](/payments/0029) Outputs Extension. Only ***one*** transaction can be submitted per request.

This provides the wallet with enough information to verify and receive payments from an application.

Here is an *example* of a simple Payment Submission Message:

```json
{
  "protocol": "3241645161d8", // Simple Authenticated BSV P2PKH Payment Protocol
  "transaction": {
    "rawTx": "01000000017...",
    "inputs": {
        "b7d1297158f01bfc3fdace31f62a0d4634e9471d11b2a99f0784621cedb9367c": {
        "proof": {...},
        "rawTx": "01000000011..."
        }
    },
    "mapiResponses": [...],
    "txid": "737a1e90af9745da3f22ef74bc71a089c5e2a76e9662a0c9fa5b7cf94fe32e75"
  },
  "senderIdentityKey": "031d903f5b32a6121f29c59c547d2ea41ee8157ab0f0c2b5190be24a032816f827",
  "note": "Payment for spelling fix pull request.",
  "amount": 1033,
  "derivationPrefix:": "8217bb4e7fa0541e0f5e04fea764ab91"
}
```

### Payment Acknowledgment Message

The Payment Acknowledgment Message is the response that should be returned from the wallet to the calling application with the status of the request, and a reference number for the transaction submitted.

Here is an *example* response from a successful payment submission:

```json
{
  "reference": "cd5b1e4947e304476c788cd474fb579a"
}
```

### Payment Error Message

If an error occurs during submission, an internal error should be thrown which can be caught and handled by the application code.

## Implementation

Wallets can implement this specification by providing access to a `submitDirectTransaction` route over a given [BRC-1](/wallet/0001) communications substrate, such as [BRC-5](/wallet/0005), [BRC-6](/wallet/0006) or [BRC-7](/wallet/0007).

A specific implementation of this BRC can be seen in the Babbage [Dojo/Ninja](https://projectbabbage.com/docs/dojo) architecture.


# Certificate Creation and Revelation

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We define methods for an application to request that a wallet create and prove [BRC-52](/peer-to-peer/0052) identity certificates. We define a set of additional messages that extend the [BRC-1](/wallet/0001) application-to-wallet messaging layer with this functionality. We specify the functionality to be performed by the wallet as part of these processes, including a standard methodology for wallets to contact identity certificate certifiers over HTTP and carry out the signing of these documents. To keep users in control over how their data is processed and used, we allow for the wallet to obtain user consent prior to carrying out these operations.

## Motivation

The [BRC-52](/peer-to-peer/0052) identity certificate standard provides a decentralized, privacy-centric solution to digital identity, allowing users to selectively reveal their identity data. However, without a standard method for applications to create and prove these certificates, wallet support will be limited. BRC-53 provides a set of standardized methods for applications to request and interact with [BRC-52](/peer-to-peer/0052) certificates, enabling wider adoption and integration of [BRC-52](/peer-to-peer/0052) certificates within the ecosystem.

## Status Note

BRC-53 is historical as an application-to-wallet method set. Current wallet certificate behavior is specified by [BRC-100](/wallet/0100) and [BRC-52](/peer-to-peer/0052). New implementations use `acquireCertificate`, `listCertificates`, `proveCertificate`, and `relinquishCertificate`; the older `createCertificate` and `findCertificates` naming is deprecated.

## Specification

We define two new sets of messages to be sent over the abstract [BRC-1](/wallet/0001) messaging layer:

### Certificate Creation Request

An application may request a wallet to create a [BRC-52](/peer-to-peer/0052) certificate by providing the following parameters:

| Field                | Description                                                                       |
| -------------------- | --------------------------------------------------------------------------------- |
| `certificateType`    | The type of certificate to create.                                                |
| `fieldObject`        | An object containing the fields to be added to the certificate.                   |
| `certifierUrl`       | The URL of the certifier responsible for signing the certificate.                 |
| `certifierPublicKey` | The public identity key of the certifier responsible for signing the certificate. |

The wallet will then carry out the following steps:

1. Initialize a BRC-53 client with the primary identity key of the wallet.
2. Generate a client nonce.
3. Request the `validationKey` and `serialNumber` from the certifier by making a BRC-53-enabled HTTPS POST request to the `certifierUrl`'s `/initialRequest` endpoint with the client nonce.
4. Validate the received `serialNumber` and `validationKey` using the client and server nonces.
5. Encrypt the fields of the `fieldObject` using [BRC-2](/wallet/0002) encryption and store the concealed fields and encrypted field revelation keys in the fields and keyring objects, respectively.
6. Create a Certificate Signing Request (CSR) containing the certificate type, nonces, validation key, serial number, fields, and keyring.
7. Send the CSR to the certifier's `/signCertificate` endpoint via an HTTP POST request.
8. Receive and verify the signed certificate's authenticity.
9. Store the signed certificate in the wallet's data store (how the wallet stores its data is beyond the scope of this specification).

### Certificate Creation Response

The response sent back over the abstract messaging layer from the wallet to the application will constitute a valid, fully-signed [BRC-52](/peer-to-peer/0052) identity certificate with no keyring.

### Certificate Creation Error

If the Bitcoin wallet is unable to fulfill the Certificate Creation Request for any reason, we specify that it should respond with a JSON-formatted Certificate Creation Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                                  |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                         |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_CERTIFIER_REJECTED_CSR"`. |
| `description` | All errors must have a human-readable description field that describes the error. This allows the application to represent the error for the user.                                           |

One example of a Certificate Creation Error is given below:

```json
{
  "status": "error",
  "code": "ERR_CERTIFIER_REJECTED_CSR",
  "description": "The certifier has rejected the CSR and refused to sign the certificate."
}
```

### Certificate Proof Request

An application may request a wallet to prove a [BRC-52](/peer-to-peer/0052) certificate by providing the following parameters:

| Field                       | Description                                                                 |
| --------------------------- | --------------------------------------------------------------------------- |
| `certificate`               | The [BRC-52](/peer-to-peer/0052) certificate to be proven.                  |
| `fieldsToReveal`            | An array containing the names of the fields to be revealed to the verifier. |
| `verifierPublicIdentityKey` | The public identity key of the verifier.                                    |

The wallet will then carry out the following steps:

1. Verify the authenticity of the provided certificate.
2. Ensure that the application and the verifier have been granted access to the requested certificate fields (optional permissions checks).
3. Decrypt the encrypted field revelation keys using the wallet's primary identity key.
4. Encrypt the decrypted field revelation keys for the verifier using the `verifierPublicIdentityKey`.
5. Add the encrypted field revelation keys to the certificate field revelation keyring (as defined in [BRC-52](/peer-to-peer/0052)) object.
6. Attach the field revelation keyring to the certificate.
7. Return the certificate with the attached field revelation keyring for presentation to the verifier for field examination.

### Certificate Proof Response

The response sent back over the abstract messaging layer from the wallet to the application will constitute a valid, fully-signed [BRC-52](/peer-to-peer/0052) identity certificate with the attached field revelation keyring for the verifier.

### Certificate Proof Error

If the Bitcoin wallet is unable to fulfill the Certificate Proof Request for any reason, we specify that it should respond with a JSON-formatted Certificate Proof Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                    |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_PERMISSION_DENIED"`. |
| `description` | All errors must have a human-readable description field that describes the error. This allows the application to represent the error for the user.                                      |

One example of a Certificate Proof Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "The user denied the request to reveal these fields to this verifier at this time."
}
```

### Wallet-to-Certifier Interface

The wallet and certifier communicate in a standard way over a [BRC-31](/peer-to-peer/0031) protected HTTPS interface to facilitate the requesting and signing of a certificate. Here, we specify the requirements and fields for these communications.

We require that the wallet engages in the [BRC-31](/peer-to-peer/0031) authentication process with the certifier, and we further require that the wallet authenticate using the same key which is the subject of the certificate issuance process. This provides a way for the certifier to know that the person making the request is the person who will be receiving the identity certificate.

#### Initial Request

The wallet initiates the communication with the certifier by sending an initial request. The initial request includes a client nonce, generated randomly by the wallet. The request is sent to the /initialRequest endpoint using the HTTP POST method.

**Request fields**

* `clientNonce`: A randomly generated 32-byte string in base64 format.

The certifier processes the initial request and generates two nonces: `serialNonce` and `validationNonce`. The certifier calculates the `serialNumber` and `validationKey` by hashing the concatenation of `clientNonce` with the respective nonces. The certifier then returns these values in the response.

**Response fields**

* `validationKey`: A base64 encoded string calculated by hashing the concatenation of `clientNonce` and `validationNonce` using SHA256.
* `serialNumber`: A base64 encoded string calculated by hashing the concatenation of `clientNonce` and `serialNonce` using SHA256.
* `validationNonce`: A base64 encoded nonce generated by the certifier.
* `serialNonce`: A base64 encoded nonce generated by the certifier.

#### Certificate Signing Request

After receiving and validating the values from the initial request, the wallet creates a Certificate Signing Request (CSR) and sends it to the `/signCertificate` endpoint using the HTTP POST method.

**Request fields**

* `messageType`: The string "certificateSigningRequest".
* `type`: The type of certificate to create.
* `clientNonce`: The client nonce generated by the wallet during the initial request.
* `serverSerialNonce`: The serial nonce received from the certifier in the initial request.
* `serverValidationNonce`: The validation nonce received from the certifier in the initial request.
* `validationKey`: The validation key received from the certifier in the initial request.
* `serialNumber`: The serial number received from the certifier in the initial request.
* `fields`: An object containing encrypted fields of the certificate.
* `keyring`: An object containing encrypted field revelation keys, revealed from the subject to the certifier.

The certifier processes the CSR and signs the certificate using its private signing key. The signed certificate is then returned to the wallet in the response.

**Response fields**

* `status`: The status of the signing process, either "success" or "error".
* `description`: A description of the error, if applicable.
* `code`: An error code, if applicable.
* `certificate`: The signed [BRC-52](/peer-to-peer/0052) certificate, if the signing process is successful.

## Implementations

These processes are represented in current form by `bsv-blockchain/ts-sdk` BRC-100 certificate methods and `bsv-blockchain/wallet-toolbox` certificate storage and permission behavior. Legacy `createCertificate` references are retained only for historical context.


# Unified Abstract Wallet-to-Application Messaging Layer

* Ty Everett (<ty@projectbabbage.com>)
* Brayden Langley (<brayden@projectbabbage.com>)

## Abstract

The importance of a standard interface by which Bitcoin applications can communicate with wallets and underlying infrastructure cannot be overstated. We propose an identity interface facilitating the creation of Bitcoin transactions, the use of user-held keys for encryption and digital signatures, and the employment of identity certificates to authenticate users with their counterparties. We set a baseline standard and create a versioning system that facilitates continued expansion and extensibility over time.

## Motivation

Computing has long been subject to shortfalls in the areas of information centralization and architectural cross-compatibility. Users on the internet struggle with complex and insecure authentication systems which leave them vulnerable and leak their data. Websites rely on advertising to monetize their offerings, but ads warp the incentives of platforms and creators in ways that ultimately harm everyone involved. By defining a standard interface by which users can identify themselves, protect their data and engage in e-commerce with Bitcoin, this standard offers a solution to problems that have long plagued the existing model.

## Status Note

BRC-56 is historical. The current unified wallet interface is [BRC-100](/wallet/0100). New implementations should use BRC-100 method names, argument structures, return structures, and call codes.

Important current differences include:

* certificates use `acquireCertificate`, `listCertificates`, `proveCertificate`, and `relinquishCertificate`,
* `getNetwork` returns `mainnet` or `testnet`,
* authentication methods return structured result objects such as `{ "authenticated": true }`,
* wallet version strings are not constrained to the historical `0.3.x` range,
* and transaction data is BEEF / Atomic BEEF rather than BRC-8 plus mAPI.

## Specification

We specify a baseline standard for an abstract messaging layer which facilitates an identity interface between an application and an underlying MetaNet Client. As part of this messaging layer, we incorporate various message types defined by other BRC standards:

* [**BRC-1**](/wallet/0001)**:** We incorporate by reference the message types relating to Bitcoin transaction creation as specified by [BRC-1](/wallet/0001). This facilitates micropayment-based interactions as part of applications, enabling new monetization models that are not subject to the same problematic incentives as internet-based advertising.
* [**BRC-2**](/wallet/0002)**:** We incorporate by reference the message types relating to encryption as specified by [BRC-2](/wallet/0002). This facilitates user privacy and provides a secure foundation for user-to-user communication, enabling applications which protect privacy to emerge and gain traction.
* [**BRC-3**](/wallet/0003)**:** We incorporate by reference the message types relating to digital signatures as specified by [BRC-3](/wallet/0003). This facilitates generalized message sender attestation and endorsement, solving problems such as fake AI-generated content by allowing relevant parties or the entire world to check whether something has been endorsed by its purported originator.
* [**BRC-4**](/wallet/0004)**:** We incorporate by reference the extension to [BRC-1](/wallet/0001) as defined by [BRC-4](/wallet/0004), providing for the consumption and utilization of arbitrary UTXO-based Bitcoin tokens as part of transactions. This facilitates the creation and transfer between users of tokenized assets without restrictions beyond those set by their respective output locking scripts.
* [**BRC-46**](/wallet/0046)**:** We incorporate by reference the extension to [BRC-1](/wallet/0001) as defined by [BRC-46](/wallet/0046), providing for the tracking and management of Bitcoin UTXOs in baskets. This facilitates the storage and retrieval of single-party tokens and digital assets in the user's wallet while removing the need to rely on a single trusted application for asset management.
* [**BRC-50**](/wallet/0050)**:** We incorporate by reference the message types relating to incoming transaction submission as specified by [BRC-50](/wallet/0050), ensuring users have a way to receive funds into their wallets from within applications. This gives them a way to receive payment for the value they create as part of their interactions within applications.
* [**BRC-53**](/wallet/0053)**:** We incorporate by reference the message types relating to digital identity certificate creation and revelation, as specified by [BRC-53](/wallet/0053). This provides a way for users to maintain and selectively reveal their identities with specific counterparties in the digital world, facilitating an increased level of trust and accountability.

With these core components in place, we are left only with the need to specify a few other messages that are necessary to facilitate versioning, user network checking and user-to-wallet authentication status discovery. We now proceed to the specification of those auxiliary messages.

### HMACs

The introduction of HMACs are the most significant addition aside from the core message types. HMACs facilitate authenticating messages sent between users, and are generally useful for a wide range of applications.

We stipulate the following process for HMAC creation:

* The message sender begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The message sender uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive a child public key for the recipient
* The message sender uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive their own child private key
* The message sender computes the ECDH shared secret between the two derived child keys
* The resulting elliptic curve point's X and Y values are hashed with SHA256 to create a SHA-256 HMAC key
* The resulting 256-bit value is used to compute the SHA-256 HMAC for the message
* The HMAC value is returned by the client to the application over the abstract [BRC-1](/wallet/0001) communications substrate.

We stipulate the following process for HMAC verification:

* The recipient somehow comes to know the HMAC, the message, the counterparty, the security level, protocol ID, and key ID. The mechanism for conveying this information to the recipient is beyond the scope of this specification.
* The recipient begins by computing the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
* The recipient uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number, their own private key and the public key of the sender, to compute the sender's child public key
* The recipient uses the same process to compute his own child private key
* The recipient computes a shared secret between the two child keys, using the hash of the X and Y values as a SHA-256 HMAC key
* The recipient then uses the HMAC key to compute the HMAC of the message, checking that the computed value matches the provided value

#### HMAC Creation Request

The HMAC Creation Request is a message sent by the [BRC-43](/key-derivation/0043) application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, or `self`                                                                                                                                                                                       |

The message payload comprises the data to HMAC.

#### HMAC Creation Response

The response message comprises a payload containing the computed HMAC value.

#### HMAC Verification Request

The HMAC Verification Request is a message sent by the application to the client. It contains a header with the following information:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, or `self`                                                                                                                                                                                       |
| `hmac`         | This is the HMAC value that is to be verified, for the provided message                                                                                                                                                                          |

The message payload comprises the data to verify against the HMAC provided in the header.

#### HMAC Verification Response

The response message comprises a payload containing a boolean that indicates whether the HMAC was successfully verified.

#### HMAC Error

If the client is unable to fulfill the HMAC Creation or Verification Requests for any reason except failed verification, we specify that it should respond with a JSON-formatted HMAC Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                    |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_PERMISSION_DENIED"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                    |

One example of an HMAC Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "The user has denied permission for computing an HMAC over this data."
}
```

### Public Key and Certificate Retrieval

Facilitating access to public keys and certificates are a crucial part of the wallet messaging layer as they provide the necessary components for identity verification. [BRC-43](/key-derivation/0043) is used for defining the protocolID and keyID formats, and [BRC-52](/peer-to-peer/0052) defines the format of certificates requested.

#### Public Key Request

To request a public key, we define the following standard request fields:

| Field          | Description                                                                                                                                                                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `protocolID`   | The [BRC-43](/key-derivation/0043) security level and protocol ID represented as an array. For example, `[0, "hello world"]` represents a level-0 protocol with open permissions, while `[2, "document signing"]` represents a level 2 protocol. |
| `keyID`        | The [BRC-43](/key-derivation/0043) key ID                                                                                                                                                                                                        |
| `counterparty` | The [BRC-43](/key-derivation/0043) counterparty, `self`, or `anyone`                                                                                                                                                                             |
| `forSelf`      | Whether the derived child public key corresponds to a private key held by the current user. (optional, default false)                                                                                                                            |
| `identityKey`  | If true, the identity key will be returned, and no key derivation will be performed (optional, default false). Overrides `protocolID`, `keyID`, `counterparty`, and `forSelf`.                                                                   |

When the wallet receives this request from the application, after obtaining requisite permission from the user, we stipulate the following process for key derivation:

1. If `identityKey` is true, the wallet returns its root public identity key to the application without proceeding to the other steps.
2. The wallet checks the [BRC-43](/key-derivation/0043) invoice number based on the security level, protocol ID, and key ID
3. If `forSelf` is `false`, the wallet uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive a child public key for the counterparty, returning the public key.
4. Otherwise, if `forSelf` is `true`, the wallet uses [BRC-42](/key-derivation/0042) key derivation with the computed invoice number to derive their own child private key. The wallet then multiplies the private key by the generator point, G, to arrive at their own child public key, as would be derived by a counterparty. The computed public key is returned.

#### Public Key Response

The response message comprises a 33-byte, DER-encoded public key X coordinate value.

#### Public Key Error

If the client is unable to fulfill the Public Key Request for any reason, we specify that it should respond with a JSON-formatted Public Key Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                    |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_PERMISSION_DENIED"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                    |

One example of a Public Key Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "The user has denied permission for revealing their identity public key."
}
```

#### Certificate List Request

To request a list of [BRC-52](/peer-to-peer/0052) identity certificates belonging to the user, we define these standard request fields:

| Field        | Description                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `certifiers` | An array of the public keys for certifiers to filter certificates by: only certificates issued by these certifiers will be returned.                                                                                                                                                                                                                                                                                        |
| `types`      | The certificate types to filter certificates by, given as an object whose keys are types and whose values are, for historical reasons, arrays of fields to request from certificates of the given type. Note that unless all fields are returned, no one can validate the certificate signature. We therefore specify that if `true` is provided in place of an array of fields, that all fields should always be returned. |

When the wallet receives this message from the application, the wallet will ascertain based on user preferences and the information provided which certificates will be returned. **The wallet is by no means required to return all responsive certificates, if for example the user does not want to reveal a particular affiliation in a particular context.** The wallet composes a list, which may be empty, of responsive certificates.

#### Certificate List Response

The response from the wallet to the application comprises an array of [BRC-52](/peer-to-peer/0052) identity certificates, which may be empty.

#### Certificate List Error

If the client is unable to fulfill the Certificate List Request for any reason except an empty list, we specify that it should respond with a JSON-formatted HMAC Error. The fields for the object are specified as follows:

| Field         | Description                                                                                                                                                                             |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`      | This should always be a string comprising `"error"`.                                                                                                                                    |
| `code`        | A machine-readable error code. Extensions to this standard can define specific error codes and standardize additional fields. Codes are strings, for example `"ERR_PERMISSION_DENIED"`. |
| `description` | All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.                                    |

One example of a Certificate List Error is given below:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "The user has denied permission for obtaining a list of certificates."
}
```

### Network, Versioning and Authentication

Another important aspect of the wallet messaging layer is allowing applications the ability to check which Bitcoin network is in use, and whether or not a user has been authenticated.

#### Bitcoin Network Request

To determine which Bitcoin network a user's MetaNet Client is currently using, applications can make requests to a standardized endpoint to ensure compatibility.

We define that the request message has no content (beyond the fact of it being a Bitcoin Network Request) and simply returns a string of either `"test"` or `"main"` indicating the current network in use.

#### Client Version Request

We define the standard client version request to contain no parameters (beyond the fact of it being a Client Version Request) and to simply return the version of the MetaNet Client in use as a string.

For historical reasons, to denote compatibility with these specifications, we stipulate that the version should be in the range `0.3.x`. For example, `0.3.80`.

#### Authentication Status Request

The authentication status request requires no parameters and returns a boolean indicating whether the current user is "authenticated" or not.

The purpose of this functionality is to facilitate software where the user can "log in" once in their client, and never need a separate login for MetaNet applications. When not authenticated, the application should assume that none of the other functionality, aside from client version checks, will work. It provides a way for applications, when they load, to ensure that the communication system with the client is operational, as a sort of "ping" request that also ensures the user is set up with a wallet.

#### Asynchronous Authentication Request

The asynchronous authentication request requires no parameters and waits until the user is authenticated before returning a response of true.

## Implementations

Implementations should treat this document as historical context and implement [BRC-100](/wallet/0100) over a current substrate such as the binary wallet wire, JSON HTTP API, window CWI, XDM, React Native, or an implementation-specific bridge.

Some examples of communication substrates include:

* [BRC-5](/wallet/0005), for operating a wallet over HTTP on a local machine, enabling applications on that machine to interact with the running wallet.
* [BRC-6](/wallet/0006), for cross-document messaging between a parent page which runs a wallet and a set of child pages which run various applications.
* [BRC-7](/wallet/0007), for exposing an identity interface via the `window` object in web browsers.

The historical BRC-56 functions were implemented by earlier Babbage tooling. Current interoperable code should follow BRC-100 and the current `bsv-blockchain/ts-sdk` / `bsv-blockchain/wallet-toolbox` behavior.


# Transaction Labels and List Actions

* Ty Everett (<ty@projectbabbage.com>)

## Abstract

BRC-65 extends the functionality of BRC-56 by introducing the ability to label Bitcoin transactions when they are created using the BRC-1 Transaction Creation Request. This extension allows applications to organize and categorize transactions for different purposes. BRC-65 also introduces the capability to list labeled transactions, providing applications with an easy way to retrieve specific sets of transactions based on their labels. This standardization improves interoperability between wallets and applications and enhances the user experience by enabling the display of transaction lists relevant to specific categories or actions.

## Motivation

The motivation behind BRC-65 is to enhance the functionality of the Bitcoin wallet messaging layer defined in BRC-56 by introducing the ability to label transactions. This functionality allows applications to categorize and organize transactions based on specific criteria. By labeling transactions, applications can easily retrieve and display transaction lists that are relevant to specific actions or categories. This simplifies the user experience and enables users to quickly find, review, and analyze specific sets of transactions.

By defining a standard mechanism for labeling transactions and listing labeled transactions, BRC-65 promotes interoperability between wallets and applications. With this standardization, applications can expect consistent behavior across different wallets, making it easier for developers to create Bitcoin-powered applications without having to build custom wallet functionality. Furthermore, users can switch between wallets seamlessly without losing access to their labeled transactions.

## Status Note

BRC-65 remains the conceptual standard for wallet action labels, but its original BRC-56/BRC-1 message shapes have been superseded by [BRC-100](/wallet/0100). The singular `label` request field, `skip` pagination field, and [BRC-8](/transactions/0008) envelope response model are deprecated for current wallets.

New implementations MUST use BRC-100 `createAction`, `internalizeAction`, and `listActions` behavior.

## Specification

### Labels

The `labels` field is an optional array on BRC-100 `createAction` and `internalizeAction` calls. Labels are application-defined strings used to organize wallet actions.

Current BRC-100 validation normalizes labels by trimming whitespace and lowercasing the value. A label must be between 1 and 300 UTF-8 bytes. The older restriction to letters, numbers, and underscores only is no longer current.

Labels beginning with `p` are reserved for installed or future label permission modules as defined by [BRC-111](/wallet/0111). Unsupported P-label schemes MUST be rejected rather than treated as normal labels.

Example:

```json
{
  "description": "Pay John Galt 3,301 satoshis",
  "labels": ["payment", "personal"],
  "outputs": [
    {
      "lockingScript": "76a914b10f7d6c7fda3285e9b98297428ed814374cbd4088ac",
      "satoshis": 3301,
      "outputDescription": "Payment to John Galt"
    }
  ]
}
```

### List Actions

BRC-100 `listActions` retrieves wallet actions based on labels and optional inclusion flags:

| Field                              | Description                                                             |
| ---------------------------------- | ----------------------------------------------------------------------- |
| `labels`                           | Array of labels to filter actions by                                    |
| `labelQueryMode`                   | `any` or `all`; defaults to `any`                                       |
| `includeLabels`                    | Include action labels in returned actions                               |
| `includeInputs`                    | Include inputs in returned actions                                      |
| `includeInputSourceLockingScripts` | Include source locking scripts for inputs                               |
| `includeInputUnlockingScripts`     | Include unlocking scripts for inputs                                    |
| `includeOutputs`                   | Include outputs in returned actions                                     |
| `includeOutputLockingScripts`      | Include output locking scripts                                          |
| `limit`                            | Maximum number of actions to return; current validation defaults to 10  |
| `offset`                           | Offset into the result set; negative offsets count back from the end    |
| `seekPermission`                   | Whether the wallet may seek user permission if needed; defaults to true |

Example:

```json
{
  "labels": ["payment"],
  "labelQueryMode": "any",
  "includeLabels": true,
  "limit": 10,
  "offset": 0
}
```

The response is a BRC-100 `ListActionsResult`:

```json
{
  "totalActions": 42,
  "actions": [
    {
      "txid": "900b7e9ced44f8a7605bd4c1b7054b8fb958385998954d772820a8b81eabb56f",
      "satoshis": 3301,
      "status": "completed",
      "isOutgoing": true,
      "description": "Pay John Galt 3,301 satoshis",
      "labels": ["payment", "personal"]
    }
  ]
}
```

Current action status values are defined by BRC-100 and include `completed`, `unprocessed`, `sending`, `unproven`, `unsigned`, `nosend`, `nonfinal`, and `failed`.

### Permission Behavior

Wallets may require user authorization before an originator applies or queries labels. Current wallet-toolbox behavior, including grouped label checks and P-label module delegation, is specified in [BRC-116](/wallet/0116) and [BRC-111](/wallet/0111).

## Implementations

This functionality is implemented by current `bsv-blockchain/ts-sdk` BRC-100 wallet interfaces and `bsv-blockchain/wallet-toolbox` storage and permission layers.


# Output Basket Removal and Certificate Deletion

* Ty Everett (<ty@projectbabbage.com>)

## Abstract

This BRC extends [BRC-56](/wallet/0056) by adding the ability to remove specific outputs from a basket and delete digital certificates that are no longer required. Applications can request that a wallet remove outputs from a basket by providing the transaction ID (`txid`) and the output index (`vout`) along with the name of the basket. Certificates can be deleted by specifying the certifier, serial number, and certificate type. The wallet will authenticate the request and remove the requested output from the basket or delete the specified certificate. If the request is denied or the output or certificate cannot be found, the wallet will respond with an error message.

## Motivation

Applications need the ability to remove specific outputs from a basket when they are no longer needed, without having to spend them. Furthermore, certificates may need to be deleted when they expire or are no longer relevant. This functionality allows for tokens and certificates to be managed more efficiently within a wallet, improving user experience and reducing clutter.

## Status Note

BRC-66 is historical. Current output and certificate relinquishment is specified by [BRC-100](/wallet/0100): use `relinquishOutput({ basket, output })` for basketed outputs and `relinquishCertificate({ type, serialNumber, certifier })` for certificates. Legacy `txid`/`vout` request shapes and BRC-56 extension framing are deprecated for new implementations.

## Specification

This BRC introduces two new message types to the [BRC-56](/wallet/0056) messaging layer:

### Output Basket Removal Request

The Output Basket Removal Request message is sent by an application to request the removal of a specific output from a basket. It contains the following fields:

| Field    | Description                                                   |
| -------- | ------------------------------------------------------------- |
| `txid`   | The transaction ID of the output to be removed.               |
| `vout`   | The output index of the output to be removed.                 |
| `basket` | The name of the basket from which the output will be removed. |

The wallet will validate the request and remove the requested output from the specified basket if it exists. If the requested output does not exist or the user denies permission, the wallet will respond with an error.

### Output Basket Removal Response

The Output Basket Removal Response message is sent by the wallet in response to an Output Basket Removal Request. If the removal was successful, the response will contain a success message. Otherwise, if the request was denied or the output could not be found, an error message will be included.

### Output Basket Removal Error

If the wallet is unable to fulfill the Output Basket Removal Request for any reason, it will respond with a JSON-formatted Output Basket Removal Error. The fields for the error message are as follows:

| Field         | Description                                 |
| ------------- | ------------------------------------------- |
| `status`      | This should always be the string `"error"`. |
| `code`        | A machine-readable error code.              |
| `description` | A human-readable description of the error.  |

One example of an Output Basket Removal Error is as follows:

```json
{
  "status": "error",
  "code": "ERR_PERMISSION_DENIED",
  "description": "The user has denied permission to remove this output from the basket."
}
```

### Certificate Deletion Request

The Certificate Deletion Request message is sent by an application to request the deletion of a specific digital certificate. It contains the following fields:

| Field             | Description                                    |
| ----------------- | ---------------------------------------------- |
| `certifier`       | The certifier responsible for the certificate. |
| `serialNumber`    | The serial number of the certificate.          |
| `certificateType` | The type of the certificate to be deleted.     |

The wallet will validate the request and delete the specified certificate if it exists. If the certificate does not exist or the user denies permission, the wallet will respond with an error.

### Certificate Deletion Response

The Certificate Deletion Response message is sent by the wallet in response to a Certificate Deletion Request. If the deletion was successful, the response will contain a success message. Otherwise, if the request was denied or the certificate could not be found, an error message will be included.

### Certificate Deletion Error

If the wallet is unable to fulfill the Certificate Deletion Request for any reason, it will respond with a JSON-formatted Certificate Deletion Error. The fields for the error message are as follows:

| Field         | Description                                 |
| ------------- | ------------------------------------------- |
| `status`      | This should always be the string `"error"`. |
| `code`        | A machine-readable error code.              |
| `description` | A human-readable description of the error.  |

One example of a Certificate Deletion Error is as follows:

```json
{
  "status": "error",
  "code": "ERR_CERTIFICATE_NOT_FOUND",
  "description": "The specified certificate could not be found."
}
```

## Implementations

Implementations of this specification will need to extend the existing implementation of [BRC-56](/wallet/0056) to include the functionality for output basket removal and certificate deletion. The wallet will need to handle incoming request messages, validate them, and perform the specified operations. The application will need to handle the response and error messages accordingly.


# Group Permissions for App Access

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines the manifest declaration format for grouped wallet permissions. It allows an application to declare, in one place, the protocol access, spending authorization, basket access, and certificate disclosure capabilities it expects to request from the user.

The current interoperable manifest namespace is `metanet.groupPermissions`. Wallets may continue to read the legacy `babbage.groupPermissions` namespace for backwards compatibility, but it is deprecated.

## Motivation

As applications become more integrated with user data and cryptographic operations, permission prompts can become fragmented and repetitive. Grouped permission declarations let wallets present a single, coherent prompt instead of a series of unrelated one-off requests, improving both transparency and user comprehension.

## Status Note

This document defines the **grouped-permission declaration shape**.

The full runtime permission lifecycle, including manifest fetching, prompt routing, persistence, renewal, revocation, and interaction with counterparty trust (PACT), is authoritatively specified by [BRC-116](/wallet/0116). This document should therefore be read as the grouped-permission schema used by current BRC-100 wallet implementations, not as a complete permission-system specification by itself.

## Specification

### Canonical Manifest Location

Applications SHOULD serve a W3C web-app manifest at:

`https://{originator}/manifest.json`

For local development, wallets MAY use:

`http://localhost/.../manifest.json`

Wallets MAY fetch this manifest proactively, or lazily when the first protected operation is attempted. Current wallet-toolbox behavior supports both patterns.

### Namespace

The canonical grouped-permission declaration lives under:

`metanet.groupPermissions`

Wallets SHOULD continue to recognize:

`babbage.groupPermissions`

for backwards compatibility, but applications SHOULD migrate to `metanet`.

### `groupPermissions` Structure

The `groupPermissions` object may contain the following keys:

* `description`
* `protocolPermissions`
* `spendingAuthorization`
* `basketAccess`
* `certificateAccess`

Grouped permissions do **not** cover privileged-key usage. Privileged operations remain one-off permission decisions in current interoperable wallet behavior.

### Permission Types

There are four grouped permission categories:

1. **Protocol permissions** for BRC-43 scoped cryptographic operations.
2. **Spending authorization** for wallet spending on behalf of the originator.
3. **Basket access** for BRC-46 basket usage.
4. **Certificate access** for BRC-52 field revelation to a designated verifier.

### Protocol Permissions

Each element of `protocolPermissions` is an object with:

* `protocolID`: a BRC-43 protocol tuple `[securityLevel, protocolName]`
* `counterparty`: required when the declaration is for a specific Level 2 counterparty
* `description`: human-readable explanation for the user

Level 1 protocol declarations MAY omit `counterparty`.

Level 2 declarations used in grouped permission requests SHOULD name the specific counterparty they are about.

### Spending Authorization

`spendingAuthorization` is an object with:

* `amount`: the authorized monthly spend limit in satoshis
* `description`: human-readable explanation for the user

The older `duration` field is no longer part of current interoperable grouped-permission behavior and SHOULD be considered deprecated for new manifests.

### Basket Access

Each element of `basketAccess` is an object with:

* `basket`: the BRC-46 basket name
* `description`: human-readable explanation for the user

### Certificate Access

Each element of `certificateAccess` is an object with:

* `type`: the BRC-52 certificate type
* `fields`: the certificate fields requested for revelation
* `verifierPublicKey`: the verifier's compressed public key
* `description`: human-readable explanation for the user

## Current Interoperability Notes

* Wallets commonly fetch grouped permissions from `manifest.json` when the first protected operation is attempted, not only during `waitForAuthentication`.
* `metanet.groupPermissions` is canonical.
* `babbage.groupPermissions` is a deprecated backwards-compatibility fallback.
* Grouped permissions and PACT are distinct; PACT is defined in [BRC-116](/wallet/0116).

## Examples

1. **`protocolPermissions`**

   ```json
   {
     "protocolID": [2, "Convo"],
     "counterparty": "...",
     "description": "For encrypted messaging."
   }
   ```
2. **`spendingAuthorization`**

   ```json
   {
     "amount": 10000,
     "description": "For in-app purchases."
   }
   ```
3. **`basketAccess`**

   ```json
   {
     "basket": "BRC-46 Gold",
     "description": "For in-game items."
   }
   ```
4. **`certificateAccess`**

   ```json
   {
     "type": "...",
     "fields": ["Name", "DOB"],
     "verifierPublicKey": "...",
     "description": "For age verification."
   }
   ```
5. **Full `manifest.json`**

   ```json
   {
     "name": "My App",
     "short_name": "MyApp",
     "display": "standalone",
     "metanet": {
       "schemaVersion": 1,
       "groupPermissions": {
         "protocolPermissions": [
           {
             "protocolID": [2, "Convo"],
             "counterparty": "...",
             "description": "For encrypted messaging."
           }
         ],
         "spendingAuthorization": {
           "amount": 10000,
           "description": "For in-app purchases."
         },
         "basketAccess": [
           {
             "basket": "BRC-46 Gold",
             "description": "For in-game items."
           }
         ],
         "certificateAccess": [
           {
             "type": "...",
             "fields": ["Name", "DOB"],
             "verifierPublicKey": "...",
             "description": "For age verification."
           }
         ]
       }
     },
     "icons": []
   }
   ```

Applications SHOULD provide clear descriptions that help the user understand why each grouped permission is being requested. Wallets SHOULD interpret these declarations using the lifecycle and enforcement rules in [BRC-116](/wallet/0116).


# Extensible Proof-Type Format for Specific Key Linkage Claims

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This BRC proposes an extensible format for including zero-knowledge proofs (ZKPs) in specific key linkage revelations as per [BRC-69](/key-derivation/0069) Method 2. While [BRC-94](/key-derivation/0094) addresses limitations of [BRC-69](/key-derivation/0069) Method 1 through a Schnorr-based ZKP, there is currently no standardized method for provable specific key linkage claims in Method 2. Given the rapid evolution of ZKP technologies, this specification introduces a proof-type enumeration scheme to accommodate future proof mechanisms. By defining a flexible proof-type field, we allow for the inclusion of various ZKP schemes as they become available, ensuring that wallets and applications can adopt and support them over time, eventually converging on standardized formats.

## Motivation

We aim to provide a method for proving specific key linkage revelations under [BRC-69](/key-derivation/0069) Method 2. However, current ZKP techniques may not fully support this requirement, as these technologies are still maturing. This proposal allows us to proceed with standardizing wallet interactions and linkage proofs while accommodating future advancements in ZKP capabilities. By introducing an extensible proof-type field, we create a flexible mechanism for integrating new proof schemes as they emerge, fostering innovation and facilitating eventual convergence on standard proofs.

## Scope and Assumptions

This specification focuses on proofs of a specific computation: that a prover (Alice) has computed a shared secret between herself and a counterparty (Bob), and used it as a SHA-256-HMAC key over a defined invoice number to derive a specific linkage offset value. The approach is constrained to non-interactive proof schemes, as interactive proofs are impractical for our use case.

We make the following assumptions:

1. **Non-Interactive Proofs**: Only non-interactive proof schemes are considered, as per existing standards like [STARKs](https://eprint.iacr.org/2018/046.pdf).
2. **External Verification**: Proof verification is expected to be performed by external systems or verifiers, not within the wallet itself.
3. **Encrypted Proof Payloads**: Proof payloads are encrypted according to [BRC-72](/key-derivation/0072) to ensure confidentiality during transmission.

## Specification

### Integration with Existing Standards

We build on the existing standards:

* [**BRC-69**](/key-derivation/0069): Defines methods for revealing key linkages.
* [**BRC-72**](/key-derivation/0072): Specifies encryption of linkage information in transit using [BRC-2](/wallet/0002).
* [**BRC-94**](/key-derivation/0094): Provides a Schnorr-based ZKP for counterparty-level linkage revelation (Method 1).

This proposal extends these standards by defining an extensible proof-type format for specific key linkage claims (Method 2).

### Proof Encryption with BRC-72

All proofs are encrypted using the mechanism defined in [BRC-72](/key-derivation/0072).

* **Counterparty-Level Revelations**: Encrypted according to BRC-72, including both the shared secret and the Schnorr proof as per [BRC-94](/key-derivation/0094).
* **Specific Key Revelations**: Encrypted according to BRC-72, including the linkage offset and the proof payload defined herein.

### Proof Formats

#### Counterparty-Level Proof Format (BRC-94)

We define a binary format for the encrypted Schnorr proof payload for counterparty-level revelations:

| **Field** | **Bytes** | **Description**                                                  |
| --------- | --------- | ---------------------------------------------------------------- |
| `R`       | 33        | Nonce public key point `R` in compressed 33-byte DER format.     |
| `S'`      | 33        | Nonce shared secret point `S'` in compressed 33-byte DER format. |
| `z`       | Variable  | Response scalar `z` as a big-endian integer.                     |

This binary data is concatenated in the above order, then encrypted as per [BRC-72](/key-derivation/0072) alongside the shared secret, and returned by the wallet to the verifier.

#### Specific Key Proof Format (BRC-97)

We define a binary format for the encrypted payload for specific key linkage proofs:

| **Field**    | **Bytes** | **Description**                                                      |
| ------------ | --------- | -------------------------------------------------------------------- |
| `Proof-Type` | 1         | One-byte unsigned integer (0-255), indicating the proof scheme used. |
| `Proof`      | Variable  | Proof payload, format depends on `Proof-Type`.                       |

This binary data is assembled by first specifying the `Proof-Type`, followed by the `Proof` payload (if applicable), then encrypted as per [BRC-72](/key-derivation/0072) alongside the specific linkage offset, and returned by the wallet to the verifier.

### Proof-Type Enumeration

We introduce a proof-type numbering scheme:

* **Proof-Type 0**: Indicates no proof is provided. Verifiers must trust the prover when the proof-type is zero. The `Proof` payload is empty.
* **Proof-Types 1-255**: Reserved for future proof schemes. As new ZKP methods become available, they can be assigned unique proof-type identifiers within this range, along with their specific proof formats.

### Example: Proof-Type Zero

When `Proof-Type` is zero:

* The `Proof` payload is empty.
* The verifier receives the encrypted linkage offset but must trust the prover's claim, as there's no way to independently verify the correctness without a proof.

## Implementation Guidelines

### Wallet and Application Behavior

* **Wallets**: Should implement the ability to generate and include the `Proof-Type` and `Proof` payload in the encrypted data when performing specific key linkage revelations.
* **Verifiers**: Should be able to parse the `Proof-Type` field and handle the `Proof` payload accordingly, based on supported proof schemes.

### Future-Proofing

* As new ZKP methods are developed and standardized, new BRCs can define additional proof-types (1-255) and their corresponding proof formats.
* Wallets and verifiers should be designed to be extensible, allowing for the addition of new proof-types without significant changes to underlying architectures.

### Security Considerations

* **Confidentiality**: All proof payloads must be encrypted as per [BRC-72](/key-derivation/0072) to ensure that sensitive linkage information is protected during transit.
* **Trust**: When using Proof-Type 0 (no proof), verifiers must be aware that they are relying on the prover's honesty, as no independent verification is possible.

## Future Work

Future specifications may define new proof-types (1-255) along with their proof formats and verification methods. Potential avenues include:

* **Proof-Type 1**: Could be assigned to a specific ZKP scheme (e.g., Bulletproofs, STARKs, SNARKs) that is suitable for proving specific key linkage claims.
* **Standardization**: As the ecosystem converges on preferred proof schemes, updates to this BRC can formalize these proofs, promoting interoperability.

## References

* [BRC-2: BRC Encryption Standard](/wallet/0002)
* [BRC-69: Revealing Key Linkages](/key-derivation/0069)
* [BRC-72: Protecting BRC-69 Key Linkage Information in Transit](/key-derivation/0072)
* [BRC-93: Limitations of BRC-69 Key Linkage Revelation](/key-derivation/0093)
* [BRC-94: Verifiable Revelation of Shared Secrets Using Schnorr Protocol](/key-derivation/0094)
* [STARKs: Scalable, Transparent, and Post-Quantum Secure Computational Integrity](https://eprint.iacr.org/2018/046.pdf)

## Conclusion

This BRC provides a flexible and extensible framework for including proof schemes in specific key linkage revelations, accommodating future advancements in ZKP technology. By standardizing the proof-type field, we enable wallets and applications to adopt new proof mechanisms as they emerge, facilitating independent verification of specific key linkage claims while maintaining backward compatibility and fostering innovation in the BSV blockchain ecosystem.


# P Protocols: Allowing future wallet protocol permission schemes

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The [BRC-43](/key-derivation/0043) architecture for wallet protocol permissions enables a wide range of use cases. By reserving protocol identifiers and preventing their use by applications, we ensure compatibility with future standards, supporting the future development of new protocol permission schemes.

## Motivation

The motivation for this proposal is to future-proof the [BRC-43](/key-derivation/0043) architecture by enabling the seamless integration of new protocol permission schemes. By specifying reserved identifiers, we can ensure that new security and permission paradigms can be implemented without conflicts or unintended behavior.

## Specification

To accommodate future protocol permission schemes, wallets must reject operation requests using protocol IDs beginning with `p` (a lowercase “p” followed by a space), regardless of security level.

### Future Scheme Identifiers

Future permission schemes must define their ID formats as follows:

* Scheme IDs cannot contain spaces.
* Protocol IDs must start with `p` , followed by the scheme ID, a space, and the rest of the protocol identifier.

#### Example Format

A protocol ID like `p 222 xxxxx` could represent a specific invoice number (e.g., `2-p 222 xxxxx-kkkkk`), where:

* `2` indicates the [BRC-43](/key-derivation/0043) security level.
* `p` designates an alternative permission scheme.
* `222` identifies the permission scheme.
* `xxxxx` forms the remainder of the protocol ID under the alternative scheme.
* `kkkkk` denotes the key ID.

#### Protocol Parsing and Rules

Wallets must differentiate between standard and alternative permission schemes by recognizing the `p` prefix followed by a distinct, space-free scheme ID. To ensure unambiguous parsing.

Upon recognizing a protocol ID structured as `p <scheme ID> <rest of the ID>`, wallets may apply the specific rules defined by the scheme associated with the `scheme ID`. These rules could define:

* Permitted protocols and key IDs.
* Conditions for operation execution.
* Counterparty requirements and customizable permission attributes.

### Reserved Structure

To maintain clarity and prevent conflicts:

* Protocol IDs beginning with `p` must be reserved for future use.
* Wallets must reject operations involving such IDs unless they explicitly support the scheme ID.
* A space must immediately follow the scheme ID to separate it from other elements.

### Extensibility Beyond Current Paradigms

This specification allows future permission schemes to extend beyond current [BRC-43](/key-derivation/0043) models (e.g., security levels or [BRC-73](/wallet/0073) paradigms), enabling flexible and innovative wallet permissions that evolve with user and application needs.

## Conclusion

By reserving protocol IDs starting with `p` and specifying rules for future permission schemes, this specification ensures forward compatibility and robust wallet permission functionalities. It enables seamless integration of new schemes without disrupting existing applications or introducing parsing ambiguities.


# P Baskets: Allowing Future Wallet Basket and Digital Asset Permission Schemes

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The [BRC-46](/wallet/0046) architecture for digital assets stored within wallet baskets enables a wide range of use cases. However, it lacks support for more than a rudimentary permission system. Wallets can grant applications blanket access to basketed assets, or deny access entirely, but cannot make insightful decisions based on the specific assets stored, their output scripts, or the tokenized value they represent. To support the future development of new permission schemes covering fungible and non-fungible digital assets within wallets, we propose reserving certain basket identifiers to prevent their use by applications and ensure compatibility with future standards.

## Motivation

The motivation for this proposal is to future-proof the [BRC-46](/wallet/0046) architecture by enabling the seamless integration of new permission schemes applicable to assets stored in or retrieved from wallet-managed UTXO baskets. By specifying reserved identifiers, we can ensure that new security and permission paradigms can be implemented without conflicts or unintended behavior.

## Specification

To accommodate future basket permission schemes, wallets must reject any operation requests made under basket IDs beginning with `p` (a lowercase “p” followed by a space).

### Future Scheme Identifiers

Future permission schemes must define their ID formats, as follows:

* The scheme IDs cannot contain spaces.
* The basket IDs must start with `p` , followed by the scheme ID, a space, and the rest of the basket identifier.

#### Example Format

A basket ID such as `p dollarToken xxxxx` could represent a specific token type (e.g., tokenized dollars), where:

* `p` designates an alternative permission scheme.
* `dollarToken` identifies the permission scheme.
* `xxxxx` forms the basket ID under the alternative scheme.

#### Basket ID Parsing and Rules

Wallets must differentiate between standard and alternative permission schemes by recognizing the `p` prefix followed by a distinct, space-free scheme ID. To ensure unambiguous parsing.

Upon recognizing a basket ID structured as `p <scheme ID> <rest of the ID>`, wallets may apply the specific rules defined by the scheme associated with the `scheme ID`. These rules could define:

* Constraints based on specific locking scripts or script templates of UTXOs.
* The conditions under which operations can be executed.
* Mechanisms for allowing applications to access only a certain set number of only a specific asset type, according to the rules of some tokenization protocol, overlay service or script template.
* Specific counterparty requirements and other customizable permission attributes.

### Reserved Structure

To maintain clarity and prevent conflicts:

* Basket IDs beginning with `p` must be reserved for future use.
* Wallets must reject operations involving such IDs unless they explicitly support the scheme ID.
* A space must immediately follow the scheme ID to separate it from other elements.

### Extensibility Beyond Current Paradigms

This specification allows future permission schemes to extend beyond current [BRC-46](/wallet/0046) models (e.g. [BRC-73](/wallet/0073) paradigms), enabling flexible and innovative wallet permissions that evolve with user and application needs.

For example, a wallet could allow access to a maximum of 10 dollars of tokenized fiat money per month within an application.

## Conclusion

By reserving basket IDs starting with `p` and specifying rules for future permission schemes,this specification ensures forward compatibility and robust wallet permission functionalities. It enables seamless integration of new schemes without disrupting existing applications or introducing parsing ambiguities.


# Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface

* Ty Everett (<ty@projectbabbage.com>)
* Tone Engel (<tone@projectbabbage.com>)
* Brayden Langley (<brayden@projectbabbage.com>)

## Abstract

We define the BSV Blockchain's standard wallet-to-application interface. This interface defines a robust and secure communication protocol between BSV wallets and applications. This protocol, built on the MetaNet architectural principles, aims to standardize the interaction between wallets and decentralized applications in the BSV ecosystem. The interface is designed to be vendor-neutral, supporting a wide range of implementations, ensuring interoperability, and promoting openness across different wallet and app vendors.

## Motivation

Computing has long been subject to shortfalls in the areas of information centralization and architectural cross-compatibility. Users on the internet struggle with complex and insecure authentication systems which leave them vulnerable and leak their data. Websites rely on advertising to monetize their offerings, but ads warp the incentives of platforms and creators in ways that ultimately harm everyone involved. By defining a standard interface by which users can identify themselves, protect their data and engage in e-commerce with Bitcoin, this standard offers a solution to problems that have long plagued the existing model.

## Primary Objectives

1. **Standardization**: The interface provides a consistent and standardized API that ensures that any application can integrate with any compliant wallet without needing custom adaptations.
2. **Vendor-Neutrality**: The interface abstracts the underlying wallet implementation, allowing applications to work seamlessly with wallets from different vendors.
3. **Open Specification**: The interface is openly documented, encouraging community adoption, and providing a bedrock foundation atop which anyone can build with confidence, knowing the interface will remain constant.
4. **Security and Privacy**: Emphasizing secure handling of keys, transactions, and data through the use of secure cryptographic protocols and minimal exposure of sensitive information.
5. **Comprehensive Functionality**: The interface covers a broad range of functionalities, from transaction creation, signing, and broadcasting, to identity management, encryption, and digital signatures.

## Foundational Requirements

**SECTION TL;DR**: We use the BSV Blockchain. We use the secp256k1 elliptic curve. We use compressed, DER-formatted public keys. We use BKDS key derivation. We use [BRC-43](/key-derivation/0043) for security levels, protocol IDs, key IDs, and counterparties (with [BRC-44](/key-derivation/0044) protocols reserved for internal wallet use). We subscribe to the [BRC-45](/tokens/0045) idea that "Outputs are tokens". We use [BRC-46](/wallet/0046) output baskets for tracking tokens. For categorization and filtering purposes, we allow transactions to be given a set of labels, and outputs to be given a set of tags. We utilize the [BRC-67](/transactions/0067) rules for SPV validation. We use the BEEF standard outlined in [BRC-62](/transactions/0062) for representing transactions. For encryption and decryption, we use the methods described in [BRC-2](/wallet/0002). For creating digital signatures, we use the methods described in [BRC-3](/wallet/0003). For HMACs [BRC-56](/wallet/0056#hmacs), we derive symmetric keys as in [BRC-2](/wallet/0002), but then use them for HMAC operations instead of AES-GCM encryption. For the digital certificate structure and field encryption scheme, we use [BRC-52](/peer-to-peer/0052). For internalizing payment outputs that increase the user's wallet balance, we employ the key derivation protocol described within [BRC-29](/payments/0029). For revealing key linkages, we employ the two methods described within [BRC-69](/key-derivation/0069), and we protect this information as described in [BRC-72](/key-derivation/0072). We incorporate [BRC-97](/wallet/0097) for defining flexible proof-type fields to support emerging zero-knowledge proof (ZKP) schemes in specific key linkage revelations. We reserve specific protocol identifiers as described in [BRC-98](/wallet/0098) to ensure forward compatibility with future permissioned protocols. Similarly, we reserve basket identifiers as outlined in [BRC-99](/wallet/0099) to accommodate evolving wallet-managed asset permission schemes. These foundational prerequisites allow us to fully define and specify the behavior and functionality of the digital wallet system, ensuring future extensibility and compatibility across the BSV ecosystem.

### Section Overview

The Wallet Interface is built upon a set of essential foundational standards and protocols that define the underlying architecture, cryptographic operations, and key management systems required for a robust and coherent wallet-to-application interface within the BSV ecosystem. This section thoroughly explains these foundational requirements, incorporating relevant content from the preceding BRCs to provide a holistic understanding of the interface's structure.

### 1. **Key Derivation using BKDS with BRC-42**

At the heart of the Wallet Interface lies the [**BRC-42**](/key-derivation/0042)**: BSV Key Derivation Scheme**. This scheme defines how keys are derived between two interacting parties. BKDS leverages the secp256k1 elliptic curve and enables participants to derive multiple unique public-private key pairs from a shared master key. All cryptographic operations, including key derivation and identity handling, adhere to the BKDS as defined in [BRC-42](/key-derivation/0042). This specification does not support legacy key derivation schemes like BIP32 due to the foundational nature of BKDS in ensuring compatibility, privacy, and security across the BSV ecosystem. Wallets implementing this interface must migrate to BKDS and cannot rely on older derivation schemes.

**Identity Keys:**

* Each wallet has an everyday master private key and a corresponding master public key derived from the secp256k1 elliptic curve. The public key is known as the "identity key".
* Additionally, there's a whole secondary "privileged mode" keyring for sensitive operations, allowing these privileged keys to be treated with higher security than the user's everyday keyring.

**Key Derivation Process:**

* When deriving a key for a payment or exchange, the sender computes an elliptic curve Diffie-Hellman (ECDH) shared secret using their private key and the recipient's public key.
* The shared secret is then used to generate a scalar through HMAC and convert it to a point on the elliptic curve.
* This point is combined with the recipient's master public key to produce a child public key.

**Key Privacy:**

* No information about the derived private key is exposed until actually used. This ensures that only the recipient can derive the private key corresponding to the public key provided by the sender.

**Expanding Key Universes:**

* With BKDS, wallets can create a virtually unlimited number of unique key pairs through simple modifications of the derivation inputs (e.g., invoice numbers). This open-ended invoice numbering scheme is baked into the Wallet Interface, facilitating custom, flexible key derivation for transactions, signatures, and encryption.

### 2. **Security Levels and Protocol IDs with BRC-43**

[BRC-43](/key-derivation/0043) plays a vital role in organizing how keys are used and accessed in the wallet. This standard introduces **Security Levels, Protocol IDs, Key IDs, and Counterparties** rules that govern key derivation, permissions, and data access within standard wallets.

**Security Levels:**

* Security levels determine the required user permissions and access controls for a derived key:
  * **Level 0**: No restrictions, open access.
  * **Level 1**: Requires a level of user authorization that applies across all counterparties who use the same protocol.
  * **Level 2**: Restricts key usage to specific counterparties and requires individual permission for each.

**Protocol IDs & Key IDs:**

* Protocol IDs further define the usage context for a derived key, such as "Document Signing" or "Encryption".
* A Key ID is a unique identifier that differentiates specific keys under the same Protocol ID, allowing numerous derived keys under the same context but with different purposes.

**Counterparties:**

* Counterparties are entities with whom keys are shared (sender, receiver, etc.). For instance, the interface allows single-party self-derivations where a sender and receiver are the same (useful for internal key operations) and anyone-derivations denoted by the private key `1` (for public operations).

**Permission System:**

* Permission grants are managed transparently by the wallet and include expiration times, user notifications, and granularity based on the security level. This ensures that applications receive only the delineated keys they need and no more.

### 3. **Reserved Internal Protocols with BRC-44**

[BRC-44](/key-derivation/0044) discusses **Admin-reserved and Prohibited Key Derivation Protocols** that are exclusive to administrative use by the wallet. These reserved protocols prevent application access to key derivation operations that are inherently internal and crucial to wallet security and integrity.

**Internal Protocol Guidelines:**

* Any protocol ID that begins with `admin` is off-limits for external applications.
* The Wallet Interface requires that this reserved protocol space is never exposed to or invoked by third-party applications, maintaining the separation between user-facing operations and internal wallet functionalities.

### 4. **Tokenization of UTXOs with BRC-45**

[BRC-45](/tokens/0045) defines **UTXOs as Tokens**, asserting that Unspent Transaction Outputs (UTXOs) are the base units of tokenization within Bitcoin. This principle is integral to the interface, where UTXOs serve as tokens that can be managed within wallets using custom baskets, as discussed below.

**Transaction Outputs as Tokens:**

* UTXOs represent discrete token units that can be transferred directly from sender to recipient. Their validity and legitimacy can be independently verified by anyone receiving the transaction.
* The Wallet Interface relies on the UTXO model to enhance scalability, ensure decentralization, and promote trustlessness by enabling transparent and verifiable transactions without reliance on intermediaries.

**Simplified Payment Verification (SPV):**

* The wallet interface integrates transaction validation protocols that follow [BRC-67](/transactions/0067)'s SPV method. By validating UTXOs through matching `txid` and proof inclusion within the blockchain, wallets can quickly confirm that tokens are genuine without needing the entire chain. This eliminates the reliance on tracing tokens back to their genesis, addressing the Back To Genesis (BTG) problem. Instead, the Wallet Interface ensures validity and provenance by leveraging SPV proofs and state-based validation, allowing efficient token verification while maintaining scalability, reducing computational overhead, and preserving user privacy. As a result, tokens can be securely and rapidly transacted without the inefficiencies and complexities historically associated with the BTG issue.

### 5. **Tracking and Managing Outputs with BRC-46**

To facilitate complex tracking and interaction with UTXOs, [BRC-46](/wallet/0046) establishes **Wallet Transaction Output Tracking (Output Baskets)** within the interface.

**Baskets:**

* Baskets are conceptual containers for grouping UTXOs, creating an easy-to-manage structure for tracking specific outputs used across applications or protocols.
* A wallet must support basket management, including returning transaction outputs from a given basket, customizing outputs with relevant instructions, and spending or relinquishing them.

**Permissions:**

* Like with key derivation, permissioning is enforced for applications executing operations involving baskets. The security model here follows from [BRC-43](/key-derivation/0043), ensuring consistency across the system.
* Wallets must ensure that users have given consent before listing the outputs from a given basket, creating transactions that insert outputs into a basket, or internalizing transactions that facilitate output insertion into baskets.

### 6. **Transaction Verification with BRC-67 and BRC-62**

Authenticated transaction verification is critical in wallet operations, and this builds, in part, on [**BRC-67**](/transactions/0067)**: Simplified Payment Verification** and [**BRC-62**](/transactions/0062)**: Background Evaluation Extended Format (BEEF) Transactions**.

**BEEF Data Structure:**

* The BEEF format specified in [BRC-62](/transactions/0062) is optimized for SPV and designed for efficient data transmission, focusing on economy of information while retaining verification integrity.
* Wallets should utilize BEEF when constructing, communicating, and validating transactions. BEEF supports streaming validation, enabling the wallet to initiate transaction verification as soon as it starts receiving the data.

**Verification Steps:**

* [BRC-67](/transactions/0067) outlines the steps to be undertaken for verifying a transaction, including script validation, fee checking, sequence, and locktime examination. These checks ensure that transactions processed through the wallet interface are legitimate and contextually accurate.

**SPV Empowerment:**

* The SPV model allows lightweight wallet clients to verify the chain, making them resistant to fraud while not requiring them to store or access all blockchain data.

### 7. **Data Security with BRC-2 for Encryption and Decryption**

Security is paramount in the Wallet Interface, and [BRC-2](/wallet/0002) defines **Encryption and Decryption** operations encapsulated within the interface's cryptographic functionality.

**AES-256-GCM Encryption:**

* Wallets employ AES-256-GCM for symmetric encryption, where the derived shared secret between the sender's and recipient's child keys becomes the basis for the encryption key.

**Encryption & Decryption Process:**

* Wallets use their private keys and their counterparty's public keys (derived through BKDS) to encrypt data under a given protocol ID and key ID. Similarly, upon receipt, the recipient decrypts the data using their private key combined with the sender's public key.

**Confidentiality Assurance:**

* Encryption through this interface ensures the confidentiality of transmitted data and can be applied for user-specific actions like private document exchange (shielded with encryption keys derived from BKDS).

### 8. **Authorized Digital Signatures with BRC-3**

The Wallet Interface supports digital signing functionalities defined in [**BRC-3**](/wallet/0003)**: Digital Signature Creation and Verification**.

**Digital Signature Process:**

* [BRC-3](/wallet/0003) mandates use of ECDSA over secp256k1 keys.
* Derived child keys (from BKDS) are used to sign data based on the security levels, protocol IDs, and key IDs defined in [BRC-43](/key-derivation/0043).

**Private & Public Signatures:**

* Wallets can create private digital signatures intended for a specified receiver, by naming them as a counterparty.
* It's also possible to create publicly verifiable signatures, simply by naming `anyone` as the counterparty.

**Verification:**

* The signature is verifiable by the recipient using derived public keys. The recipient uses [BRC-43](/key-derivation/0043) mechanics to derive the corresponding public key and validates the signature using this key over the provided data.

### 9. **Identity Certificates and Selective Revelation with BRC-52**

[BRC-52](/peer-to-peer/0052) defines **Identity Certificates**, which incorporate selective revelation protocols that wallets must support.

**Certificate Structure:**

* Identity certificates encapsulate a subject's identity information, certified by a trusted entity, with fields selectively encryptable to preserve user privacy.

**Selective Revelation:**

* Wallets must facilitate keyring management for applications to reveal or withhold certificate fields selectively. The wallet keeps a copy of the master keyring, transmitting only the necessary revelation keys to authorized parties in an encrypted form.

**Revocation Mechanism:**

* Identity certificate revocation is implemented via UTXO tracking. If the UTXO tied to a revocation outpoint is spent, everyone considers the certificate invalid—this adds an additional layer of trust and decentralized authority, depending on the constraints placed upon the UTXO.

### 10. **Payment Internalization with BRC-29**

The Wallet Interface incorporates support for [**BRC-29**](/payments/0029)**: Simple Authenticated BSV P2PKH Payment Protocol**, which standardizes how payments are derived, handled, and internalized within the wallet.

**Payment Key Derivation:**

* **BKDS Integration:** Payments utilize keys derived through BKDS. They are based on a combination of transaction-specific data (such as a unique derivation prefix) and counterparty public keys. The derived keys are used to generate P2PKH scripts, ensuring that only the intended recipient can derive the corresponding private keys for spending.

**Internalization Process:**

* **Decoding and Deriving Keys:** Upon receiving a payment message, the wallet decodes the message, derives the necessary private keys using the provided `derivationPrefix` and `derivationSuffix`, checks the scripts match, and processes the UTXOs.
* **Baskets and Custom Instructions:** Non-P2PKH outputs with custom scripts can be directed into specific baskets, enabling organized tracking within the wallet. Custom instructions attached to UTXOs are also stored and can be used by applications for token history tracking or future spending.
* **SPV Verification:** Regardless of whether a payment increments a user's wallet balance via [BRC-29](/payments/0029), inserts outputs into custom baskets with tags and custom instructions, or both, the wallet always employs [BRC-67](/transactions/0067) to confirm the transaction's validity according to the rules of SPV.

**Flexible Payment Handling:**

* **Multiple Outputs and Transactions:** [BRC-29](/payments/0029) supports handling multiple outputs and even transactions within a single "payment." Each transaction output can be individually indexed with a unique derivation suffix, allowing the wallet to differentiate and manage multiple outputs efficiently, while all outputs within one given payment share a common derivation prefix.

### 11. **Auditability and Key Linkage Revelations with BRC-69 and BRC-72**

The Wallet Interface incorporates methods to enhance transparency and auditability through [**BRC-69**](/key-derivation/0069)**: Revealing Key Linkages**, while ensuring the protection of sensitive data during transit with [**BRC-72**](/key-derivation/0072)**: Protecting BRC-69 Key Linkage Information in Transit**.

**Key Linkage Revelations:**

* **BKDS Based Key Linkage:** [BRC-69](/key-derivation/0069) outlines two methods for revealing key linkages from BKDS derived keys. The first method allows wallets to reveal a root ECDH shared secret between a user's identity key and another counterparty's key, enabling anyone to link all interactions between them. The second method reveals the specific key offset for individual derived child keys, enabling audit trails while preserving privacy in other contexts.

**Protection of Linkage Information:**

* **BRC-72 Integration:** Protecting sensitive linkage data is paramount. [BRC-72](/key-derivation/0072) specifies mechanisms for encrypting key linkage revelations when they are in transit. This encryption is done using the [BRC-2](/wallet/0002) AES-256-GCM method, ensuring that only authorized verifiers (recipients) can decrypt and access the linkage data. This preserves privacy and security, even when key linkages must be revealed for audit or verification purposes.
* Additionally, [BRC-94](/key-derivation/0094) proofs help overcome the limitations described in [BRC-93](/key-derivation/0093). [BRC-97](/wallet/0097) allows for future zero-knowledge proof types to be used in the context of specific key linkage revelation as technology develops.

The foundational BRCs integrated into this Wallet Interface ensure security, scalability, and flexibility across operations performed within the BSV ecosystem. These frameworks from key derivation, rigorous cryptographic principles, selective identity verification, tokenization, secure payments, and audit trails through linkage revelations all combine to form a robust and future-proof digital wallet architecture. The next section defines the high-level structure of the wallet interface.

## Interface Structure

The interface comprises numerous methods that cater to different functional areas related to wallet operations and application needs. The methods are grouped for easier understanding:

### Transaction Operations:

* **Creation**: The `createAction` method creates a new Action, which is effectively a Bitcoin transaction augmented with descriptive metadata and optional categorization (labels). This method can either fully construct and sign a transaction or return a `signableTransaction` reference if some inputs must be signed or processed later. In current interoperable implementations, `createAction` is also used for wallet-managed batching and chained-send workflows such as `noSend`, `sendWith`, and change remixing. Implementers MUST therefore not assume that every valid `createAction` call explicitly carries one or more inputs or outputs in the request body. If `inputs` are provided, `inputBEEF` supplies supporting SPV and contextual information for those inputs. Every explicit input requires an `inputDescription`. If `basket` is not provided for an output, that output is considered untracked by the wallet and will not appear in `listOutputs`.
* **Signing**: `signAction` allows for signing and processing previously created transactions from the `createAction` method.
* **Aborting**: `abortAction` facilitates the cancellation of transactions that have not yet been completed.
* **Internalization**: `internalizeAction` enables wallets to accept and manage incoming transactions by parsing, tagging, and organizing outputs.
* **Listing**: `listActions` and `listOutputs` allow querying transactions and outputs based on specific criteria like labels, baskets, and tags. Labels can be attached to transactions to facilitate discovery via `listActions` and tags can be attached to outputs to similarly facilitate discovery via `listOutputs`. They are purely organizational tools and cannot be used as triggers or conditional hooks for other wallet operations. They serve only for later searching and categorizing actions.
* **Relinquishment:** `relinquishOutput` releases an output from a basket tracked by the wallet, even if it has yet to be spent.
* **Pre-Built Transactions**: If you already have a fully constructed transaction in BEEF format and simply need to internalize it into the wallet, `internalizeAction` is the appropriate method. `createAction` is meant for constructing new transactions within the wallet, not for just adding an existing transaction.
* **Tags vs. Labels**: Transaction-level labels categorize entire transactions (Actions) and are used with `listActions`. Output-level tags categorize individual outputs and are used with `listOutputs`. Both are organizational metadata fields and do not trigger special wallet logic or external processes. They exist solely for searching, filtering, and organizing data.

### Public Key Management:

* **Key Retrieval**: `getPublicKey` facilitates the retrieval of public keys, be they derived keys based on protocols or the user's main identity keys.
* **Key Linkage**: Methods `revealCounterpartyKeyLinkage` and `revealSpecificKeyLinkage` disclose key relationships, as specified in [BRC-69](/key-derivation/0069), with additional support for future zero-knowledge proof schemes outlined in [BRC-97](/wallet/0097). These are essential for identity verification and the auditing of interactions between parties.

### Cryptography Operations:

* **Encryption/Decryption**: `encrypt` and `decrypt` methods implement secure encryption and decryption of data using derived keys and consistent protocol definitions, enabling private exchanges of information between counterparties.
* **HMAC Operations**: `createHmac` and `verifyHmac` allow for the creation and verification of Hash-based Message Authentication Codes (HMAC) to ensure data integrity.
* **Signatures**: `createSignature` and `verifySignature` enable the creation and verification of digital signatures, both public and private, essential for validating the authenticity of transactions, documents, and data.

### Identity and Certificate Management:

* **Certificate Acquisition**: `acquireCertificate` allows the wallet to obtain identity certificates, either by directly saving them or through a standardized issuance protocol. Conversely, `relinquishCertificate` allows an old certificate to be removed.
* **Certificate Listing and Discovery**: `listCertificates`, `discoverByIdentityKey`, and `discoverByAttributes` enable querying of identity certificates owned by the user or others based on identity keys or specific attributes.
* **Proving Identity Certificates**: `proveCertificate` leverages selective revelation protocols defined in [BRC-52](/peer-to-peer/0052) while integrating future-proof proof schemes from [BRC-97](/wallet/0097). This provides enhanced flexibility and enables users to securely prove their identity or certified attributes to third parties when required.

### Blockchain and Network Data:

* **Blockchain Height**: `getHeight` retrieves the current height of the blockchain.
* **Block Header Retrieval**: `getHeader` retrieves the serialized block header at a specific block height.
* **Network and Version Information**: `getNetwork` and `getVersion` retrieve information about the network (mainnet or testnet) and the wallet's version.

### Authentication:

* **User Authentication**: `isAuthenticated` checks the user's authentication status, ensuring they've set up their wallet before operations are attempted.
* **Authentication Wait**: `waitForAuthentication` waits for the user to complete authentication and returns once the wallet has been fully set up.

## Data Types and Constraints

To ensure consistency and prevent errors, the interface defines various data types and associated constraints. A few key examples include:

### Boolean Types:

* `BooleanDefaultFalse`: Defaults to false if not provided.
* `BooleanDefaultTrue`: Defaults to true if not provided.

### Integer Types:

* `Byte`: An integer between 0 and 255.
* `PositiveIntegerOrZero`: A non-negative integer with an upper bound of 2^32 - 1.
* `PositiveIntegerMax10`: A positive integer between 1 and 10.
* `PositiveIntegerDefault10Max10000`: A positive integer that defaults to 10, and has an upper bound of 10000.
* `SatoshiValue`: Represents a value in Satoshis, ranging between 1 and 2.1 \* 10^15.

### String Types:

* `ISOTimestampString`: Represents an ISO 8601 format timestamp.
* `HexString`: A string containing hexadecimal characters.
* `Base64String`: A string in standard base64 encoded format.
* **Specialized Strings**: Defined for specific fields, including transactions, descriptions, version strings, certificate field names, etc.

Current interoperable SDK validation measures string limits in **UTF-8 bytes**, not JavaScript character count. Historical type names in this document that end with `Characters` should therefore be interpreted as byte-length limits for interoperability purposes.

## Error Handling and Validation

### Errors are raised using a uniform structure containing:

* `status`: Denotes the presence of a failure (always "error").
* `code`: A short machine-readable string representing the specific error or fault.
* `description`: A human-readable explanation of the error.
* `context`: (Optional) Additional contextual data relevant to the error---often binary or debug information.

When errors occur, they must be communicated and thrown such that they preserve these structural elements. Specific instantiations or realizations of this interface, comprising APIs or transport mechanisms for messages between wallets and applications, must specify how these errors are communicated.

### Recommended Validation Steps:

Wallets must apply all specified rules and logical validation procedures to all methods within the specification. For example, in the case of `createAction`, wallets should:

* Verify that `description` is present.
* Permit request shapes used for interoperable batching flows such as `noSend`, `sendWith`, or wallet-managed remix/change handling, even when no explicit inputs or outputs are supplied.
* Check that if `inputs` are provided, the `inputDescription` field is also provided for each input.
* Validate that if `inputBEEF` is provided, it corresponds logically to the `inputs`.
* Ensure that labels, tags, and basket names comply with the defined format and size constraints. Current interoperable SDK validation trims and lowercases these identifiers before enforcing length limits.
* If any constraints are not met, or if conflicting parameters are detected (e.g., invalid `noSendChange usage`), the wallet should return a structured error as previously defined.

### Parameter Errors:

If parameters are missing, malformed, or conflict with each other, the wallet should return an error with an appropriate error code and a human-readable `description`. For instance:

* Missing required fields: Return an error code like `ERR_MISSING_PARAMETER` and a description stating which parameter is missing.
* Invalid numeric ranges or string lengths: Return `ERR_INVALID_PARAMETER_VALUE`.
* Conflicts between `inputs` and `inputBEEF`: Return `ERR_CONFLICTING_PARAMETERS`.

These are examples; wallets and implementations are free to use their own naming conventions for error codes as long as they provide a clear `description` field.

## Usage and Best Practices

* **Interoperability**: Since the interface is vendor-neutral, developers should ensure they comply fully with the defined types, constraints, and method contracts, allowing their wallets and applications to interface smoothly with others.
* **Use of Privileged Mode**: Methods related to the use of keys include options that allow an alternative "privileged access" mode to be used. When implemented, a secondary and more secure set of keys is used instead of the primary ones. This should only be invoked when necessary, and requires proper justification to be provided.
* **Request Originators and Permissions**: The interface ensures that operations like key derivation, signing, encryption, certificate field revelation, and transaction creation are conducted with proper authorization by incorporating the request's originator. The wallet can then authenticate the originator and seek user permission if necessary.

## Restrictions on Protocol and Basket Namespaces

Protocol IDs and basket names are used to control access to data and assets, respectively. In order to ensure that consistent rules apply across wallet implementations, and to ensure that appropriate reservations are made for future permissions architectures (see [BRC-98](/wallet/0098) and [BRC-99](/wallet/0099)), we specify the rules that apply to these namespaces here:

### Rules for Protocol Names

Protocol IDs:

* Must be at least 5 characters.
* Generally must not exceed 400 characters (except for the `specific linkage revelation` protocol, which is allowed to be up to 430 characters, since it's the only protocol that encapsulates anotherr full protocol name within itself).
* Must not contain multiple consecutive spaces (e.g., " ").
* Must only contain lowercase letters, numbers, and spaces.
* Must not end with `protocol` (this is redundant).
* Must not start with `p` (allows for future "specially permissioned" protocols). Specified in [BRC-98](/wallet/0098).

Key IDs:

* Must be at least one byte in length.
* Must not exceed 800 bytes in length.

### Rules for Basket Names

Basket names:

* Must be at least 5 characters.
* Must be no more than 400 characters.
* Must only contain lowercase letters, numbers, and spaces.
* Must not end with `basket` (this is redundant).
* Must not contain consecutive spaces.
* Must not start with `admin` (allows the wallet to manage assets internal to its operations).
* Must not be `default` (some wallets have historically used this for internal operations).
* Must not start with `p` (allows for future "specially permissioned" baskets). Specified in [BRC-99](/wallet/0099).

## The Wallet Interface

This interface is specified in TypeScript as follows:

```typescript
/**
 * @typedef {boolean} BooleanDefaultFalse
 * Represents an optional boolean parameter, which defaults to `false` if not provided.
 * @remarks
 * Default values are not enforced at the type level. Ensure that implementations explicitly assign the default value.

 */
export type BooleanDefaultFalse = boolean

/**
 * @typedef {boolean} BooleanDefaultTrue
 * Represents an optional boolean parameter, which defaults to `true` if not provided.
 * @remarks
 * Default values are not enforced at the type level. Ensure that implementations explicitly assign the default value.
 */
export type BooleanDefaultTrue = boolean

/**
 * @typedef {number} Byte
 * Represents an integer from 0 to 255 (inclusive).
 * @minimum 0
 * @maximum 255
 */
export type Byte = number

/**
 * @typedef {number} PositiveIntegerOrZero
 * A positive integer, includes zero and has an upper bound of `2^32 - 1`.
 * @minimum 0
 * @maximum 4294967295
 * @remarks
 * TypeScript cannot enforce numeric ranges. Validate at runtime if the value is within the specified range.
 */
export type PositiveIntegerOrZero = number

/**
 * @typedef {number} PositiveInteger
 * A positive integer that excludes zero, and has an upper bound of `2^32 - 1`.
 * @minimum 1
 * @maximum 4294967295
 * @remarks
 * TypeScript cannot enforce numeric ranges. Validate at runtime if the value is within the specified range.
 */
export type PositiveInteger = number

/**
 * @typedef {number} PositiveIntegerMax10
 * A positive integer that excludes zero, and has an upper bound of 10.
 * @minimum 1
 * @maximum 10
 * @remarks
 * TypeScript cannot enforce numeric ranges. Validate at runtime if the value is within the specified range.
 */
export type PositiveIntegerMax10 = number

/**
 * @typedef {number} PositiveIntegerDefault10Max10000
 * A positive integer that defaults to 10, and has an upper bound of 10000.
 * @minimum 1
 * @default 10
 * @maximum 10000
 * @remarks
 * Default values are not enforced at the type level. Validate at runtime if the value is within the specified range and assign the default if omitted.
 */
export type PositiveIntegerDefault10Max10000 = number

/**
 * @typedef {number} SatoshiValue
 * Represents a value in Satoshis, constrained by the max supply of Bitcoin (2.1 * 10^15 Satoshis).
 * @minimum 1
 * @maximum 2100000000000000
 * @remarks
 * TypeScript cannot enforce numeric ranges. Validate at runtime if the value is within the specified range.
 */
export type SatoshiValue = number

/**
 * @typedef {string} ISOTimestampString
 * Represents an ISO timestamp string.
 * @remarks
 * Ensure runtime validation to confirm the string adheres to the ISO 8601 standard.
 */
export type ISOTimestampString = string

/**
 * @typedef {string} HexString
 * A string containing only hexadecimal characters (0-9, a-f).
 * @remarks
 * TypeScript does not enforce format or case. Validate at runtime to ensure the string is properly formatted.
 */
export type HexString = string

/**
 * @typedef {HexString} TXIDHexString
 * Represents a transaction ID, enforced to be exactly 64 characters in length and in hexadecimal format.
 * @length 64
 * @remarks
 * TypeScript cannot enforce string length. Validate at runtime for length and format compliance.
 */
export type TXIDHexString = HexString

/**
 * @typedef {string} OutpointString
 * Represents a transaction ID and output index pair. The TXID is given as a hex string followed by a period "." and then the output index is given as a decimal integer.
 * @remarks
 * Validate at runtime to ensure the correct format: `<TXID>.<index>`.
 */
export type OutpointString = string

/**
 * @typedef {HexString} PubKeyHex
 * Represents a compressed DER secp256k1 public key, exactly 66 hex characters (33 bytes) in length.
 * @length 66
 * @remarks
 * TypeScript does not enforce length or format. Validate at runtime to ensure the string is 66 characters long and valid hexadecimal.
 */
export type PubKeyHex = HexString

/**
 * @typedef {string} Base64String
 * A standard base64 encoded string.
 * @remarks
 * Validate at runtime to ensure the string adheres to the Base64 format.
 */
export type Base64String = string

/**
 * @typedef {string} OriginatorDomainNameString
 * Represents the fully qualified domain name (FQDN) of the application that originates the request.
 * @remarks
 * Validate at runtime to ensure the string conforms to FQDN formatting rules.
 */
export type OriginatorDomainNameString = string

/**
 * @typedef {string & { minLength: 5, maxLength: 50 }} DescriptionString5to50Characters
 * A string used for short descriptions, such as `privilegedReason` values, with a length between 5 and 50 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type DescriptionString5to50Characters = string

/**
 * @typedef {string & { minLength: 5, maxLength: 2000 }} ActionDescriptionString5to2000Characters
 * A string used for action, input, output, and internalization descriptions, with a length between 5 and 2000 characters.
 * @remarks
 * Current interoperable `ts-sdk` validation accepts this wider range for transaction-facing descriptions.
 */
export type ActionDescriptionString5to2000Characters = string

/**
 * @typedef {string & { maxLength: 300 }} BasketStringUnder300Characters
 * A string for naming baskets, with a maximum length of 300 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type BasketStringUnder300Characters = string

/**
 * @typedef {string & { maxLength: 300 }} OutputTagStringUnder300Characters
 * A string for tagging outputs, with a maximum length of 300 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type OutputTagStringUnder300Characters = string

/**
 * @typedef {string & { maxLength: 300 }} LabelStringUnder300Characters
 * A string for labeling transactions, with a maximum length of 300 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type LabelStringUnder300Characters = string

/**
 * @typedef {Byte[] | Uint8Array} BEEF
 * A byte array indicating transaction data in BEEF(BRC-62) format.
 * @remarks
 * Current interoperable implementations accept either a numeric byte array or a `Uint8Array`.
 */
export type BEEF = Byte[] | Uint8Array

/**
 * @typedef {Byte[] | Uint8Array} AtomicBEEF
 * A byte array indicating transaction data in Atomic BEEF(BRC-95) format.
 * @remarks
 * Current interoperable implementations accept either a numeric byte array or a `Uint8Array`.
 */
export type AtomicBEEF = Byte[] | Uint8Array

/**
 * @typedef {string & { minLength: 5, maxLength: 400 }} ProtocolString5To400Characters
 * A protocol identifier with a length between 5 and 400 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type ProtocolString5To400Characters = string

/**
 * @typedef {string & { maxLength: 800 }} KeyIDStringUnder800Characters
 * Represents a key identifier string, with a maximum length of 800 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type KeyIDStringUnder800Characters = string

/**
 * @typedef {string & { maxLength: 50 }} CertificateFieldNameUnder50Characters
 * Represents a certificate field name with a maximum length of 50 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type CertificateFieldNameUnder50Characters = string

/**
 * @typedef {string & { maxLength: 100 }} EntityNameStringMax100Characters
 * Represents a trusted entity name with a maximum length of 100 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type EntityNameStringMax100Characters = string

/**
 * @typedef {string & { maxLength: 500 }} EntityIconURLStringMax500Characters
 * Represents a trusted entity icon URL with a maximum length of 500 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type EntityIconURLStringMax500Characters = string

/**
 * @typedef {string & { minLength: 7, maxLength: 30 }} VersionString7To30Characters
 * Represents a version string, with a length between 7 and 30 characters.
 *
 * The format is [vendor]-[major].[minor].[patch]
 * @remarks
 * Validate at runtime for format and length compliance.
 */
export type VersionString7To30Characters = string

/**
 * @typedef {string & { minLength: 10, maxLength: 40 }} ErrorCodeString10To40Characters
 * Represents a machine-readable error code string, with a length between 10 and 40 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type ErrorCodeString10To40Characters = string

/**
 * @typedef {string & { minLength: 20, maxLength: 200 }} ErrorDescriptionString20To200Characters
 * Represents a human-readable error description string, with a length between 20 and 200 characters.
 * @remarks
 * TypeScript cannot enforce length constraints. Validate at runtime for length compliance.
 */
export type ErrorDescriptionString20To200Characters = string

/**
 * The Wallet interface defines a wallet capable of various tasks including transaction creation and signing,
 * encryption, decryption, identity certificate management, identity verification, and communication
 * with applications as per the BRC standards. This interface allows applications to interact with
 * the wallet for a range of functionalities aligned with the Babbage architectural principles.
 */
export interface Wallet {
  /**
   * Creates a new Bitcoin transaction based on the provided inputs, outputs, labels, locks, and other options.
   *
   * @param {Object} args - The arguments required to create the transaction.
   * @param {ActionDescriptionString5to2000Characters} args.description - A human-readable description of the action represented by this transaction.
   * @param {BEEF} [args.inputBEEF] - BEEF data associated with the set of input transactions from which UTXOs will be consumed.
   * @param {Array<Object>} [args.inputs] - An optional array of input objects used in the transaction.
   * @param {OutpointString} args.inputs[].outpoint - The outpoint being consumed.
   * @param {HexString} args.inputs[].unlockingScript - The unlocking script needed to release the specified UTXO.
   * @param {ActionDescriptionString5to2000Characters} args.inputs[].inputDescription - A description of this input for contextual understanding of what it consumes.
   * @param {PositiveIntegerOrZero} [args.inputs[].sequenceNumber] - An optional sequence number applied to the input.
   * @param {PositiveInteger} [args.inputs[].unlockingScriptLength] - Length of the unlocking script, in case it will be provided later using `signAction`.
   * @param {Array<Object>} [args.outputs] - An optional array of output objects for the transaction.
   * @param {HexString} args.outputs[].lockingScript - The locking script that dictates how the output can later be spent.
   * @param {SatoshiValue} args.outputs[].satoshis - Number of Satoshis that constitute this output.
   * @param {ActionDescriptionString5to2000Characters} args.outputs[].outputDescription - Description of what this output represents.
   * @param {BasketStringUnder300Characters} [args.outputs[].basket] - Name of the basket where this UTXO will be held, if tracking is desired.
   * @param {string} [args.outputs[].customInstructions] - Custom instructions attached onto this UTXO, often utilized within application logic to provide necessary unlocking context or track token histories.
   * @param {OutputTagStringUnder300Characters[]} [args.outputs[].tags] - Tags assigned to the output for sorting or filtering.
   * @param {PositiveIntegerOrZero} [args.lockTime] - Optional lock time for the transaction.
   * @param {PositiveInteger} [args.version] - Optional transaction version specifier.
   * @param {LabelStringUnder300Characters[]} [args.labels] - Optional labels providing additional categorization for the transaction.
   * @param {Object} [args.options] - Optional settings modifying transaction processing behavior.
   * @param {BooleanDefaultTrue} [args.options.signAndProcess] - Optional. If true and all inputs have unlockingScripts, the new transaction will be signed and handed off for processing by the network; result `txid` and `tx` are valid and `signableTransaciton` is undefined. If false or an input has an unlockingScriptLength, result `txid` and `tx` are undefined and `signableTransaction` is valid.
   * @param {BooleanDefaultTrue} [args.options.acceptDelayedBroadcast] - Optional. If true, the transaction will be sent to the network by a background process; use `noSend` and `sendWith` options to batch chained transactions. If false, the transaction will be broadcast to the network and any errors returned in result; note that rapidly sent chained transactions may still fail due to network propagation delays.
   * @param {'known'} [args.options.trustSelf] - Optional. If `known`, input transactions may omit supporting validity proof data for TXIDs known to this wallet or included in `knownTxids`.
   * @param {TXIDHexString[]} [args.options.knownTxids] - Optional. When working with large chained transactions using `noSend` and `sendWith` options, include TXIDs of inputs that may be assumed to be valid even if not already known by this wallet.
   * @param {BooleanDefaultFalse} [args.options.returnTXIDOnly] - Optional. If true, only a TXID will be returned instead of a transaction.
   * @param {BooleanDefaultFalse} [args.options.noSend] - Optional. If true, the transaction will be constructed but not sent to the network. Supports the creation of chained batches of transactions using the `sendWith` option.
   * @param {Array<OutPoint>} [args.options.noSendChange] - Optional. Valid when `noSend` is true. May contain `noSendChange` outpoints previously returned by prior `noSend` actions in the same batch of chained actions.
   * @param {Array<TXIDHexString>} [args.options.sendWith] - Optional. Sends a batch of actions previously created as `noSend` actions to the network; either synchronously if `acceptDelayedBroadcast` is true or by a background process.
   * @param {BooleanDefaultTrue} [args.options.randomizeOutputs] — optional. When set to false, the wallet will avoid randomizing the order of outputs within the transaction.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise returns different structures based on the outcome: error response, response with TXID, response with transaction, or info about signable transaction (partial BEEF and reference number).
   */
  createAction: (
    args: {
      description: ActionDescriptionString5to2000Characters
      inputBEEF?: BEEF
      inputs?: Array<{
        outpoint: OutpointString
        unlockingScript?: HexString
        unlockingScriptLength?: PositiveInteger
        inputDescription: ActionDescriptionString5to2000Characters
        sequenceNumber?: PositiveIntegerOrZero
      }>
      outputs?: Array<{
        lockingScript: HexString
        satoshis: SatoshiValue
        outputDescription: ActionDescriptionString5to2000Characters
        basket?: BasketStringUnder300Characters
        customInstructions?: string
        tags?: OutputTagStringUnder300Characters[]
      }>
      lockTime?: PositiveIntegerOrZero
      version?: PositiveIntegerOrZero
      labels?: LabelStringUnder300Characters[]
      options?: {
        signAndProcess?: BooleanDefaultTrue
        acceptDelayedBroadcast?: BooleanDefaultTrue
        trustSelf?: 'known'
        knownTxids?: TXIDHexString[]
        returnTXIDOnly?: BooleanDefaultFalse
        noSend?: BooleanDefaultFalse
        noSendChange?: OutpointString[]
        sendWith?: TXIDHexString[]
        randomizeOutputs?: BooleanDefaultTrue
      }
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    txid?: TXIDHexString
    tx?: AtomicBEEF
    noSendChange?: OutpointString[]
    sendWithResults?: Array<{
      txid: TXIDHexString
      status: 'unproven' | 'sending' | 'failed'
    }>
    signableTransaction?: {
      tx: AtomicBEEF
      reference: Base64String
    }
  }>

  /**
   * Signs a transaction previously created using `createAction`.
   *
   * @param {Object} args - Arguments to sign the transaction.
   * @param {Record<PositiveIntegerOrZero, Object>} args.spends - Map of input indexes to the corresponding unlocking script and optional sequence number.
   * @param {HexString} args.spends[].unlockingScript - The unlocking script for the corresponding input.
   * @param {PositiveIntegerOrZero} [args.spends[].sequenceNumber] - The sequence number of the input.
   * @param {Base64String} args.reference - Reference number returned from the call to `createAction`.
   * @param {Object} [args.options] - Optional settings modifying transaction processing behavior.
   * @param {BooleanDefaultTrue} [args.options.acceptDelayedBroadcast] - Optional. If true, transaction will be sent to the network by a background process; use `noSend` and `sendWith` options to batch chained transactions. If false, transaction will be broadcast to the network and any errors returned in result; note that rapidly sent chained transactions may still fail due to network propagation delays.
   * @param {BooleanDefaultFalse} [args.options.returnTXIDOnly] - Optional. If true, only a TXID will be returned instead of a transaction.
   * @param {BooleanDefaultFalse} [args.options.noSend] - Optional. If true, the transaction will be constructed but not sent to the network. Supports the creation of chained batches of transactions using the `sendWith` option.
   * @param {Array<TXIDHexString>} [args.options.sendWith] - Optional. Sends a batch of actions previously created as `noSend` actions to the network; either synchronously if `acceptDelayedBroadcast` is true or by a background process.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise returns an error response or a response with either the completed transaction or TXID.
   */
  signAction: (
    args: {
      spends: Record<
        PositiveIntegerOrZero,
        {
          unlockingScript: HexString
          sequenceNumber?: PositiveIntegerOrZero
        }
      >
      reference: Base64String
      options?: {
        acceptDelayedBroadcast?: BooleanDefaultTrue
        returnTXIDOnly?: BooleanDefaultFalse
        noSend?: BooleanDefaultFalse
        sendWith?: TXIDHexString[]
      }
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    txid?: TXIDHexString
    tx?: AtomicBEEF
    sendWithResults?: Array<{
      txid: TXIDHexString
      status: 'unproven' | 'sending' | 'failed'
    }>
  }>

  /**
   * Aborts a transaction that is in progress and has not yet been finalized or sent to the network.
   *
   * @param {Object} args - Arguments to identify the transaction that needs to be aborted.
   * @param {Base64String} args.reference - Reference number for the transaction to abort.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object indicating the abortion result (either success or error).
   */
  abortAction: (
    args: {
      reference: Base64String
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ aborted: true }>

  /**
   * Lists all transactions matching the specified labels.
   *
   * @param {Object} args - Arguments to specify how to filter or retrieve transactions.
   * @param {LabelStringUnder300Characters[]} args.labels - An array of labels used to filter actions.
   * @param {'any' | 'all'} [args.labelQueryMode] - Specifies how to match labels (default is any which matches any of the labels).
   * @param {BooleanDefaultFalse} [args.includeLabels] - Whether to include transaction labels in the result set.
   * @param {boolean} [args.includeInputs] - Whether to include input details in the result set.
   * @param {boolean} [args.includeInputSourceLockingScripts] - Whether to include input source locking scripts in the result set.
   * @param {boolean} [args.includeInputUnlockingScripts] - Whether to include input unlocking scripts in the result set.
   * @param {boolean} [args.includeOutputs] - Whether to include output details in the result set.
   * @param {boolean} [args.includeOutputLockingScripts] - Whether to include output locking scripts in the result set.
   * @param {PositiveIntegerDefault10Max10000} [args.limit] - The maximum number of transactions to retrieve.
   * @param {PositiveIntegerOrZero} [args.offset] - Number of transactions to skip before starting to return the results.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object containing actions, their metadata, inputs, and outputs if applicable, or an error object.
   */
  listActions: (
    args: {
      labels: LabelStringUnder300Characters[]
      labelQueryMode?: 'any' | 'all'
      includeLabels?: BooleanDefaultFalse
      includeInputs?: BooleanDefaultFalse
      includeInputSourceLockingScripts?: BooleanDefaultFalse
      includeInputUnlockingScripts?: BooleanDefaultFalse
      includeOutputs?: BooleanDefaultFalse
      includeOutputLockingScripts?: BooleanDefaultFalse
      limit?: PositiveIntegerDefault10Max10000
      offset?: PositiveIntegerOrZero
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    totalActions: PositiveIntegerOrZero
    actions: Array<{
      txid: TXIDHexString
      satoshis: SatoshiValue
      status:
        | 'completed'
        | 'unprocessed'
        | 'sending'
        | 'unproven'
        | 'unsigned'
        | 'nosend'
        | 'nonfinal'
        | 'failed'
      isOutgoing: boolean
      description: ActionDescriptionString5to2000Characters
      labels?: LabelStringUnder300Characters[]
      version: PositiveIntegerOrZero
      lockTime: PositiveIntegerOrZero
      inputs?: Array<{
        sourceOutpoint: OutpointString
        sourceSatoshis: SatoshiValue
        sourceLockingScript?: HexString
        unlockingScript?: HexString
        inputDescription: ActionDescriptionString5to2000Characters
        sequenceNumber: PositiveIntegerOrZero
      }>
      outputs?: Array<{
        outputIndex: PositiveIntegerOrZero
        satoshis: SatoshiValue
        lockingScript?: HexString
        spendable: boolean
        outputDescription: ActionDescriptionString5to2000Characters
        basket: BasketStringUnder300Characters
        tags: OutputTagStringUnder300Characters[]
        customInstructions?: string
      }>
    }>
  }>

  /**
   * Submits a transaction to be internalized and optionally labeled, outputs paid to the wallet balance, inserted into baskets, and/or tagged.
   *
   * @param {Object} args - Arguments required to internalize the transaction.
   * @param {BEEF} args.tx - Atomic BEEF-formatted transaction to internalize.
   * @param {Array<Object>} args.outputs - Metadata about outputs, processed differently based on payment or insertion types.
   * @param {PositiveIntegerOrZero} args.outputs[].outputIndex - Index of the output within the transaction.
   * @param {'wallet payment' | 'basket insertion'} args.outputs[].protocol - Specifies whether the output is a payment (to be received into the wallet balance) or an insert operation (into a particular basket).
   * @param {Object} [args.outputs[].paymentRemittance] - Remittance data, structured accordingly for the payment operation.
   * @param {Base64String} args.outputs[].paymentRemittance.derivationPrefix - Payment-level derivation prefix used by the sender for key derivation (for payments).
   * @param {Base64String} args.outputs[].paymentRemittance.derivationSuffix - Specific output-level derivation suffix used by the sender for key derivation (for payments).
   * @param {PubKeyHex} args.outputs[].paymentRemittance.senderIdentityKey - Public identity key of the sender (for payments).
   * @param {Object} [args.outputs[].insertionRemittance] - Remittance data, structured accordingly for the insertion operation.
   * @param {BasketStringUnder300Characters} args.outputs[].insertionRemittance.basket - Basket in which to place the output (for insertions).
   * @param {string} [args.outputs[].insertionRemittance.customInstructions] - Optionally provided custom instructions attached to the output (for insertions).
   * @param {OutputTagStringUnder300Characters[]} [args.outputs[].insertionRemittance.tags] - Tags attached to the output (for insertions).
   * @param {ActionDescriptionString5to2000Characters} args.description - Human-readable description of the transaction being internalized.
   * @param {LabelStringUnder300Characters[]} [args.labels] - Optional labels associated with this transaction.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object indicating the success of the operation or an error object.
   */
  internalizeAction: (
    args: {
      tx: AtomicBEEF
      outputs: Array<{
        outputIndex: PositiveIntegerOrZero
        protocol: 'wallet payment' | 'basket insertion'
        paymentRemittance?: {
          derivationPrefix: Base64String
          derivationSuffix: Base64String
          senderIdentityKey: PubKeyHex
        }
        insertionRemittance?: {
          basket: BasketStringUnder300Characters
          customInstructions?: string
          tags?: OutputTagStringUnder300Characters[]
        }
      }>
      description: ActionDescriptionString5to2000Characters
      labels?: LabelStringUnder300Characters[]
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ accepted: true }>

  /**
   * Lists the spendable outputs kept within a specific basket, optionally tagged with specific labels.
   *
   * @param {Object} args - Arguments detailing the query for listing spendable outputs.
   * @param {BasketStringUnder300Characters} args.basket - The associated basket name whose outputs should be listed.
   * @param {OutputTagStringUnder300Characters[]} [args.tags] - Filter outputs based on these tags.
   * @param {'all' | 'any'} [args.tagQueryMode] - Filter mode, defining whether all or any of the tags must match. By default, any tag can match.
   * @param {'locking scripts' | 'entire transactions'} [args.include] - Whether to include locking scripts with each output or an aggregated top-level BEEF spanning the returned outputs. By default, unless specified, neither are returned.
   * @param {BooleanDefaultFalse} [args.includeCustomInstructions] - Whether custom instructions should be returned in the result.
   * @param {BooleanDefaultFalse} [args.includeTags] - Whether the tags associated with the output should be returned.
   * @param {BooleanDefaultFalse} [args.includeLabels] - Whether the labels associated with the transaction containing the output should be returned.
   * @param {PositiveIntegerDefault10Max10000} [args.limit] - Optional limit on the number of outputs to return.
   * @param {number} [args.offset] - If positive or zero: number of outputs to skip before returning results, oldest first. If negative: outputs are returned newest first and `-1` denotes the newest output.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @returns {Promise<Object>} The promise returns an output listing or an error object.
   */
  listOutputs: (
    args: {
      basket: BasketStringUnder300Characters
      tags?: OutputTagStringUnder300Characters[]
      tagQueryMode?: 'all' | 'any'
      include?: 'locking scripts' | 'entire transactions'
      includeCustomInstructions?: BooleanDefaultFalse
      includeTags?: BooleanDefaultFalse
      includeLabels?: BooleanDefaultFalse
      limit?: PositiveIntegerDefault10Max10000
      offset?: number
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    totalOutputs: PositiveIntegerOrZero
    BEEF?: BEEF
    outputs: Array<{
      outpoint: OutpointString
      satoshis: SatoshiValue
      lockingScript?: HexString
      spendable: true
      customInstructions?: string
      tags?: OutputTagStringUnder300Characters[]
      labels?: LabelStringUnder300Characters[]
    }>
  }>

  /**
   * Relinquish an output out of a basket, removing it from tracking without spending it.
   *
   * @param {Object} args - Arguments identifying the output in the basket.
   * @param {BasketStringUnder300Characters} args.basket - The associated basket name where the output should be removed.
   * @param {OutpointString} args.output - The output that should be removed from the basket.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise returns an indication of successful removal or an error object.
   */
  relinquishOutput: (
    args: {
      basket: BasketStringUnder300Characters
      output: OutpointString
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ relinquished: true }>

  /**
   * Retrieves a derived or identity public key based on the requested protocol, key ID, counterparty, and other factors.
   *
   * @param {Object} args - Arguments to specify which public key to retrieve.
   * @param {BooleanDefaultFalse|true} [args.identityKey] - Use true to retrieve the current user's own identity key, overriding any protocol ID, key ID, or counterparty specified.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - The security level and protocol string used for key derivation.
   * @param {KeyIDStringUnder800Characters} args.keyID - The key ID used for key derivation.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - The public key of the counterparty involved in the key derivation process.
   * @param {BooleanDefaultFalse} [args.forSelf] - Whether to return the public key derived from the current user's own identity (as opposed to the counterparty's identity).
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to an object containing the public key, or an error response.
   */
  getPublicKey: (
    args: {
      identityKey?: true
      protocolID?: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID?: KeyIDStringUnder800Characters
      privileged?: BooleanDefaultFalse
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      forSelf?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ publicKey: PubKeyHex }>

  /**
   * Reveals the key linkage between ourselves and a counterparty, to a particular verifier, across all interactions with the counterparty.
   *
   * @param {Object} args - Contains information about counterparty, verifier, and whether the operation is privileged.
   * @param {PubKeyHex} args.counterparty - The public key of the counterparty involved in the linkage.
   * @param {PubKeyHex} args.verifier - The public key of the verifier requesting the linkage information.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to the key linkage, or an error response.
   */
  revealCounterpartyKeyLinkage: (
    args: {
      counterparty: PubKeyHex
      verifier: PubKeyHex
      privilegedReason?: DescriptionString5to50Characters
      privileged?: BooleanDefaultFalse
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    prover: PubKeyHex
    verifier: PubKeyHex
    counterparty: PubKeyHex
    revelationTime: ISOTimestampString
    encryptedLinkage: Byte[]
    encryptedLinkageProof: Byte[]
  }>

  /**
   * Reveals the key linkage between ourselves and a counterparty, to a particular verifier, with respect to a specific interaction.
   *
   * @param {Object} args - The object defining the counterparty, verifier, protocol, and keyID for which linkage should be revealed.
   * @param {PubKeyHex} args.counterparty - The public key of the counterparty involved in the linkage.
   * @param {PubKeyHex} args.verifier - The public key of the verifier requesting the linkage information.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - The security level and protocol string associated with the linkage information to reveal.
   * @param {KeyIDStringUnder800Characters} args.keyID - The key ID associated with the linkage information to reveal.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise returns the requested linkage information, or an error object.
   */
  revealSpecificKeyLinkage: (
    args: {
      counterparty: PubKeyHex
      verifier: PubKeyHex
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      privileged?: BooleanDefaultFalse
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    prover: PubKeyHex
    verifier: PubKeyHex
    counterparty: PubKeyHex
    protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
    keyID: KeyIDStringUnder800Characters
    encryptedLinkage: Byte[]
    encryptedLinkageProof: Byte[]
    proofType: Byte
  }>

  /**
   * Encrypts the provided plaintext data using derived keys, based on the protocol ID, key ID, counterparty, and other factors.
   *
   * @param {Object} args - Information needed for encryption, including the plaintext, protocol ID, and key ID.
   * @param {Byte[]} args.plaintext - Array of bytes constituting the plaintext data to be encrypted.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - The security level and protocol string under which the data should be encrypted.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID under which the encryption will be performed.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public key of the counterparty (if two-party encryption is desired).
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to the encrypted ciphertext bytes or an error if encryption fails.
   */
  encrypt: (
    args: {
      plaintext: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ ciphertext: Byte[] }>

  /**
   * Decrypts the provided ciphertext using derived keys, based on the protocol ID, key ID, counterparty, and other factors.
   *
   * @param {Object} args - Contains the ciphertext, protocol ID, and key ID required to decrypt the data.
   * @param {Byte[]} args.ciphertext - Encrypted bytes, including the initialization vector, for decryption.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - Security level and protocol string that were used during the encryption of the ciphertext.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID used during the encryption of the ciphertext.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public identity key of the counterparty for the encryption operation.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to the decryption result, containing the plaintext data or an error.
   */
  decrypt: (
    args: {
      ciphertext: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ plaintext: Byte[] }>

  /**
   * Creates an HMAC (Hash-based Message Authentication Code) based on the provided data, protocol, key ID, counterparty, and other factors.
   *
   * @param {Object} args - Arguments containing the data, protocol ID, and key ID to generate the HMAC from.
   * @param {Byte[]} args.data - Input data (in bytes) for which the HMAC needs to be created.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - Security level and protocol string to be used during the HMAC operation.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID to be used in the HMAC operation.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public identity key of the counterparty if the operation encompasses a two-party interaction.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to an object containing the generated HMAC bytes, or an error if the creation fails.
   */
  createHmac: (
    args: {
      data: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ hmac: Byte[] }>

  /**
   * Verifies an HMAC (Hash-based Message Authentication Code) based on the provided data, protocol, key ID, counterparty, and other factors.
   *
   * @param {Object} args - Arguments containing the HMAC data, protocol ID, and key ID needed for verification.
   * @param {Byte[]} args.data - The input data whose HMAC is to be verified.
   * @param {Byte[]} args.hmac - Byte array representing the HMAC value to be verified.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - Security level and protocol string to be used during the HMAC operation.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID to be used during the HMAC operation.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public identity key of the counterparty if the operation encompasses a two-party interaction.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to an object confirming whether the HMAC was valid or an error.
   */
  verifyHmac: (
    args: {
      data: Byte[]
      hmac: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ valid: true }>

  /**
   * Creates a digital signature for the provided data or hash using a specific protocol, key, and optionally considering privilege and counterparty.
   *
   * @param {Object} args - Arguments to specify data, protocol, key ID, and privilege for creating the signature.
   * @param {Byte[]} [args.data] - Data to be signed using the derived private key with ECDSA. Required unless directly signing a hash.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - Security level and protocol string to be used during the signing operation.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID to be used during the signing operation.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public identity key of the counterparty if the operation encompasses a two-party interaction.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {Byte[]} [args.hashToDirectlySign] - Sign a pre-hashed value in situations where data can't or shouldn't be revealed, whether due to its size or for privacy.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise will resolve to an object containing the DER-encoded ECDSA signature, or an error on failure.
   */
  createSignature: (
    args: {
      data?: Byte[]
      hashToDirectlySign?: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ signature: Byte[] }>

  /**
   * Verifies a digital signature for the provided data or hash using a specific protocol, key, and optionally considering privilege and counterparty.
   *
   * @param {Object} args - Arguments specifying the data, signature, protocol, and key ID.
   * @param {Byte[]} [args.data] - The data originally signed, which is required for verification unless directly verifying a hash.
   * @param {Byte[]} args.signature - The DER-encoded ECDSA signature to validate.
   * @param {[0 | 1 | 2, ProtocolString5To400Characters]} args.protocolID - Security level and protocol string to be used during signature verification.
   * @param {KeyIDStringUnder800Characters} args.keyID - Key ID to be used during signature verification.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {PubKeyHex | 'self' | 'anyone'} [args.counterparty] - Public identity key of the counterparty if the operation encompasses a two-party interaction.
   * @param {BooleanDefaultFalse} [args.forSelf] - Whether the signature to be verified was created by this user rather than the counterparty.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {Byte[]} [args.hashToDirectlyVerify] - Optional field to verify the signature against a precomputed hash instead of data.
   * @param {BooleanDefaultTrue} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Default true, will return an error rather than proceed if set to false.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to a boolean object indicating whether the signature was valid or an error message.
   */
  verifySignature: (
    args: {
      data?: Byte[]
      hashToDirectlyVerify?: Byte[]
      signature: Byte[]
      protocolID: [0 | 1 | 2, ProtocolString5To400Characters]
      keyID: KeyIDStringUnder800Characters
      privilegedReason?: DescriptionString5to50Characters
      counterparty?: PubKeyHex | 'self' | 'anyone'
      forSelf?: BooleanDefaultFalse
      privileged?: BooleanDefaultFalse
      seekPermission?: BooleanDefaultTrue
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ valid: true }>

  /**
   * Acquires an identity certificate, whether by acquiring one from the certifier or by directly receiving it.
   *
   * @param {Object} args - Contains the type of certificate, certifier information, and fields of the certificate to be provided, among other details.
   * @param {Base64String} args.type - Type identifier for the certificate.
   * @param {PubKeyHex} args.certifier - The public identity key of the certifier.
   * @param {'issuance' | 'direct'} args.acquisitionProtocol - Specifies the acquisition process, set to either 'issuance' or 'direct'.
   * * @param {Record<CertificateFieldNameUnder50Characters, string>} args.fields - The fields included within the certificate.
   * @param {Base64String} [args.serialNumber] - Serial number of the certificate to acquire (required when the acquisition protocol is direct).
   * @param {string} [args.revocationOutpoint] - Reference for an outpoint comprising a Bitcoin token that, if ever spent, marks the certificate as invalid (required when the acquisition protocol is direct).
   * @param {HexString} [args.signature] - Signature over the certificate (required when the acquisition protocol is direct).
   * @param {string} [args.certifierUrl] - URL of the certifier where certificate acquisition requests will be sent (required when the acquisition protocol is issuance).
   * @param {PubKeyHex | 'certifier'} [args.keyringRevealer] - The public identity key of the entity revealing the keyring to the user, if different from the certifier (required when the acquisition protocol is direct).
   * @param {Record<CertificateFieldNameUnder50Characters, Base64String>} [args.keyringForSubject] - Keyring revealing all certificate fields to the subject (required when the acquisition protocol is direct).
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object containing the acquired certificate, or an error object.
   */
  acquireCertificate: (
    args: {
      type: Base64String
      certifier: PubKeyHex
      acquisitionProtocol: 'direct' | 'issuance'
      fields: Record<CertificateFieldNameUnder50Characters, string>
      serialNumber?: Base64String
      revocationOutpoint?: OutpointString
      signature?: HexString
      certifierUrl?: string
      keyringRevealer?: PubKeyHex | 'certifier'
      keyringForSubject?: Record<
        CertificateFieldNameUnder50Characters,
        Base64String
      >
      privileged?: BooleanDefaultFalse
      privilegedReason?: DescriptionString5to50Characters
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    type: Base64String
    subject: PubKeyHex
    serialNumber: Base64String
    certifier: PubKeyHex
    revocationOutpoint: OutpointString
    signature: HexString
    fields: Record<CertificateFieldNameUnder50Characters, string>
  }>

  /**
   * Lists identity certificates belonging to the user, filtered by certifier(s) and type(s).
   *
   * @param {Object} args - Arguments used to filter or limit the list of certificates returned by the request.
   * @param {PubKeyHex[]} args.certifiers - An array of public keys for specific certifiers (filters by these certifiers).
   * @param {Base64String[]} args.types - An array of certificate types issued by any of the specified certifiers, which should be returned.
   * @param {PositiveIntegerDefault10Max10000} [args.limit] - Maximum number of certificates to return.
   * @param {PositiveIntegerOrZero} [args.offset] - Number of records to skip before starting to return results.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object containing certificates or an error response.
   */
  listCertificates: (
    args: {
      certifiers: PubKeyHex[]
      types: Base64String[]
      limit?: PositiveIntegerDefault10Max10000
      offset?: PositiveIntegerOrZero
      privileged?: BooleanDefaultFalse
      privilegedReason?: DescriptionString5to50Characters
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    totalCertificates: PositiveIntegerOrZero
    certificates: Array<{
      type: Base64String
      subject: PubKeyHex
      serialNumber: Base64String
      certifier: PubKeyHex
      revocationOutpoint: OutpointString
      signature: HexString
      fields: Record<CertificateFieldNameUnder50Characters, string>
    }>
  }>

  /**
   * Proves select fields of an identity certificate, as specified, when requested by a verifier.
   *
   * @param {Object} args - Arguments including the certificate, fields to reveal, and verifier's public key.
   * @param {Object} args.certificate - The specific identity certificate being proven.
   * @param {Base64String} args.certificate.type - The type of the certificate to be proven.
   * @param {PubKeyHex} args.certificate.subject - Public key belonging to the certificate's subject.
   * @param {Base64String} args.certificate.serialNumber - Unique serial number of the certificate.
   * @param {PubKeyHex} args.certificate.certifier - Public key of the certifier who issued the certificate.
   * @param {OutpointString} args.certificate.revocationOutpoint - The outpoint used to confirm that the certificate has not been revoked.
   * @param {HexString} args.certificate.signature - Certificate signature by the certifier's private key.
   * @param {Record<CertificateFieldNameUnder50Characters, string>} args.certificate.fields - All the encrypted fields present in the certificate.
   * @param {CertificateFieldNameUnder50Characters[]} args.fieldsToReveal - Array of field names that need to be revealed to the verifier.
   * @param {PubKeyHex} args.verifier - Public key of the verifier, to whom the key revelations will be made.
   * @param {BooleanDefaultFalse} [args.privileged] - Whether this is a privileged request.
   * @param {DescriptionString5to50Characters} [args.privilegedReason] - Reason provided for privileged access, required if this is a privileged operation.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to a keyring for the verifier or an error object.
   */
  proveCertificate: (
    args: {
      certificate: {
        type: Base64String
        subject: PubKeyHex
        serialNumber: Base64String
        certifier: PubKeyHex
        revocationOutpoint: OutpointString
        signature: HexString
        fields: Record<CertificateFieldNameUnder50Characters, string>
      }
      fieldsToReveal: CertificateFieldNameUnder50Characters[]
      verifier: PubKeyHex
      privileged?: BooleanDefaultFalse
      privilegedReason?: DescriptionString5to50Characters
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    keyringForVerifier: Record<
      CertificateFieldNameUnder50Characters,
      Base64String
    >
  }>

  /**
   * Relinquishes an identity certificate, removing it from the wallet regardless of whether the revocation outpoint has become spent.
   *
   * @param {Object} args - Contains the type of certificate, certifier, and serial number for relinquishment.
   * @param {Base64String} args.type - Type identifier for the certificate.
   * @param {PubKeyHex} args.certifier - The public identity key of the certifier.
   * @param {Base64String} args.serialNumber - Serial number of the certificate to relinquish.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an indication of successful relinquishment or an error object.
   */
  relinquishCertificate: (
    args: {
      type: Base64String
      serialNumber: Base64String
      certifier: PubKeyHex
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{ relinquished: true }>

  /**
   * Discovers identity certificates, issued to a given identity key by a trusted entity.
   *
   * @param {Object} args - Arguments for requesting the discovery based on the identity key.
   * @param {PubKeyHex} args.identityKey - Identity key used to filter and discover certificates.
   * @param {PositiveIntegerDefault10Max10000} [args.limit] - Maximum number of certificates to return in the response.
   * @param {PositiveIntegerOrZero} [args.offset] - Skip this number of records before starting to provide results.
   * @param {BooleanDefaultFalse} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Current SDK validation defaults this discovery flag to false, returning an error rather than prompting unless set to true.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to the list of certificates discovered or an error object.
   */
  discoverByIdentityKey: (
    args: {
      identityKey: PubKeyHex
      limit?: PositiveIntegerDefault10Max10000
      offset?: PositiveIntegerOrZero
      seekPermission?: BooleanDefaultFalse
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    totalCertificates: PositiveIntegerOrZero
    certificates: Array<{
      type: Base64String
      subject: PubKeyHex
      serialNumber: Base64String
      certifier: PubKeyHex
      revocationOutpoint: OutpointString
      signature: HexString
      fields: Record<CertificateFieldNameUnder50Characters, Base64String>
      certifierInfo: {
        name: EntityNameStringMax100Characters
        iconUrl: EntityIconURLStringMax500Characters
        description: DescriptionString5to50Characters
        trust: PositiveIntegerMax10
      }
      publiclyRevealedKeyring: Record<
        CertificateFieldNameUnder50Characters,
        Base64String
      >
      decryptedFields: Record<CertificateFieldNameUnder50Characters, string>
    }>
  }>

  /**
   * Discovers identity certificates belonging to other users, where the documents contain specific attributes, issued by a trusted entity.
   *
   * @param {Object} args - Attributes and optional parameters used to discover certificates.
   * @param {Record<CertificateFieldNameUnder50Characters, string>} args.attributes - The attributes used to discover the certificates.
   * @param {PositiveIntegerDefault10Max10000} [args.limit] - Optional limit on the number of results returned.
   * @param {PositiveIntegerOrZero} [args.offset] - Starts retrieval of results after the specified number of records.
   * @param {BooleanDefaultFalse} [args.seekPermission] — Whether to seek permission from the user for this operation if required. Current SDK validation defaults this discovery flag to false, returning an error rather than prompting unless set to true.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to a list of matching certificates or an error object.
   */
  discoverByAttributes: (
    args: {
      attributes: Record<CertificateFieldNameUnder50Characters, string>
      limit?: PositiveIntegerDefault10Max10000
      offset?: PositiveIntegerOrZero
      seekPermission?: BooleanDefaultFalse
    },
    originator?: OriginatorDomainNameString
  ) => Promise<{
    totalCertificates: PositiveIntegerOrZero
    certificates: Array<{
      type: Base64String
      subject: PubKeyHex
      serialNumber: Base64String
      certifier: PubKeyHex
      revocationOutpoint: OutpointString
      signature: HexString
      fields: Record<CertificateFieldNameUnder50Characters, Base64String>
      certifierInfo: {
        name: EntityNameStringMax100Characters
        iconUrl: EntityIconURLStringMax500Characters
        description: DescriptionString5to50Characters
        trust: PositiveIntegerMax10
      }
      publiclyRevealedKeyring: Record<
        CertificateFieldNameUnder50Characters,
        Base64String
      >
      decryptedFields: Record<CertificateFieldNameUnder50Characters, string>
    }>
  }>

  /**
   * Checks the authentication status of the user.
   *
   * @param {Object} args - Empty object, as no parameters are needed.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object indicating whether the user is authenticated or an error response.
   */
  isAuthenticated: (
    args: {},
    originator?: OriginatorDomainNameString
  ) => Promise<{ authenticated: boolean }>

  /**
   * Continuously waits until the user is authenticated, returning the result once confirmed.
   *
   * @param {Object} args - Not used, pass an empty object.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The final result indicating that the user is authenticated or an error object.
   */
  waitForAuthentication: (
    args: {},
    originator?: OriginatorDomainNameString
  ) => Promise<{ authenticated: true }>

  /**
   * Retrieves the current height of the blockchain.
   *
   * @param {Object} args - Empty object as no other parameters are necessary.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to an object indicating the current height or an error on failure.
   */
  getHeight: (
    args: {},
    originator?: OriginatorDomainNameString
  ) => Promise<{ height: PositiveInteger }>

  /**
   * Retrieves the block header of a block at a specified height.
   *
   * @param {Object} args - Contains the height parameter needed to retrieve the block header.
   * @param {PositiveInteger} args.height - Specifies the height at which the block header needs to be retrieved.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an 80-byte block header or an error if it cannot be retrieved.
   */
  getHeaderForHeight: (
    args: { height: PositiveInteger },
    originator?: OriginatorDomainNameString
  ) => Promise<{ header: HexString }>

  /**
   * Retrieves the Bitcoin network the client is using (mainnet or testnet).
   *
   * @param {Object} args - No arguments required, pass an empty object.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} The promise resolves to an object indicating whether the client is using the mainnet or testnet.
   */
  getNetwork: (
    args: {},
    originator?: OriginatorDomainNameString
  ) => Promise<{ network: 'mainnet' | 'testnet' }>

  /**
   * Retrieves the current version string of the wallet.
   *
   * @param {Object} args - Empty argument object.
   * @param {OriginatorDomainNameString} [originator] - Fully-qualified domain name (FQDN) of the application that originated the request.
   * @returns {Promise<Object>} Resolves to an object containing the version string of the wallet, or an error.
   */
  getVersion: (
    args: {},
    originator?: OriginatorDomainNameString
  ) => Promise<{ version: VersionString7To30Characters }>
}
```

## ABI Specification

This section defines the Application Binary Interface (ABI) specification for the Wallet Interface, detailing the binary communication protocol used between applications and wallets over a Wallet Wire. It provides a comprehensive description of how method calls are structured, how data is serialized and deserialized, and how errors and origins are handled within the protocol. This specification ensures that all implementations conform to a standardized binary protocol, enabling interoperability across different platforms and vendors.

### Overview

The Wallet ABI defines a binary protocol for communication between an application and the user's digital wallet. Each message transmitted over the Wallet Wire consists of a structured binary frame that includes the method call code, originator information, parameters, and return values. The protocol is designed to be efficient, minimizing the data transmitted while ensuring all necessary information is accurately conveyed.

### Message Structure

Every message sent to the wallet follows this general structure:

* **Call Code (1 byte):** An unsigned integer representing the method being invoked.
* **Originator Length (1 byte):** The length of the originator domain name in bytes.
* **Originator (variable length):** The UTF-8 encoded fully qualified domain name (FQDN) of the application originating the request.
* **Parameters (variable length):** Method-specific parameters serialized according to the rules defined in this specification.

Responses from the wallet consist of:

* **Error Code (1 byte):** A byte indicating success (`0`) or an error code (`1-255`).
* **Response Data (variable length):** If `Error Code` is `0`, this contains the serialized return values. If an error occurred, it contains the serialized error message and optional stack trace.

### Call Codes

Each method in the Wallet Interface is assigned a unique call code. The call codes are defined as follows:

| Call Code | Method Name                    |
| --------- | ------------------------------ |
| 1         | `createAction`                 |
| 2         | `signAction`                   |
| 3         | `abortAction`                  |
| 4         | `listActions`                  |
| 5         | `internalizeAction`            |
| 6         | `listOutputs`                  |
| 7         | `relinquishOutput`             |
| 8         | `getPublicKey`                 |
| 9         | `revealCounterpartyKeyLinkage` |
| 10        | `revealSpecificKeyLinkage`     |
| 11        | `encrypt`                      |
| 12        | `decrypt`                      |
| 13        | `createHmac`                   |
| 14        | `verifyHmac`                   |
| 15        | `createSignature`              |
| 16        | `verifySignature`              |
| 17        | `acquireCertificate`           |
| 18        | `listCertificates`             |
| 19        | `proveCertificate`             |
| 20        | `relinquishCertificate`        |
| 21        | `discoverByIdentityKey`        |
| 22        | `discoverByAttributes`         |
| 23        | `isAuthenticated`              |
| 24        | `waitForAuthentication`        |
| 25        | `getHeight`                    |
| 26        | `getHeaderForHeight`           |
| 27        | `getNetwork`                   |
| 28        | `getVersion`                   |

### Originator Handling

The originator is the fully qualified domain name (FQDN) of the application making the request. It is included in each message to allow the wallet to:

* Identify the requesting application.
* Apply appropriate permissions and access controls.
* Record audit logs for security and compliance.

The originator is serialized as follows:

* **Originator Length (1 byte):** The length of the originator string in bytes.
* **Originator (variable length):** UTF-8 encoded bytes representing the originator's FQDN.

### Error Handling

Errors are communicated using a structured format, ensuring consistent and detailed information about any issues that occur during method execution. This format enables precise error reporting, seamless debugging, and interoperability across implementations:

* **Error Code (1 byte):**
  * `0`: Indicates success; the method executed without errors.
  * `1-255`: Indicates an error occurred; the specific code may correspond to predefined error types or be used for custom error categorization.

If an error occurs (`Error Code` is non-zero), the following fields are included in the response:

* **Error Message Length (VarInt):** The length of the error message in bytes.
* **Error Message (variable length):** UTF-8 encoded string describing the error.
* **Stack Trace Length (VarInt):** The length of the stack trace in bytes (optional, `-1` if absent).
* **Stack Trace (variable length):** UTF-8 encoded string containing the stack trace (optional).

### Data Types and Serialization

The following data types are used in the binary protocol:

* **Byte:** An unsigned 8-bit integer (`0` to `255`).
* **Int8:** A signed 8-bit integer (`-128` to `127`).
* **UInt8:** An unsigned 8-bit integer (`0` to `255`).
* **VarInt:** A variable-length integer used for lengths and counts.
* **UTF-8 String:** A string prefixed with its length (as a `VarInt`), followed by UTF-8 encoded bytes.
* **Byte Array:** A sequence of bytes prefixed with its length (as a `VarInt`).

#### VarInt Encoding

Variable-length integers (`VarInt`) are used to efficiently represent integer values. The encoding follows the Bitcoin protocol's `VarInt` format:

* For values from `0` to `0xFC` (inclusive), a single byte represents the value.
* For larger values, a marker byte indicates the length:
  * `0xFD`: Next two bytes are the value as a little-endian unsigned integer.
  * `0xFE`: Next four bytes are the value as a little-endian unsigned integer.
  * `0xFF`: Next eight bytes are the value as a little-endian unsigned integer.

### Method Calls Specification

Each method call has specific parameter and return value formats. The following sections detail the serialization and deserialization process for each method.

#### 1. `createAction`

* **Call Code:** `1`

**Parameters**

| Field         | Type                           | Description                                                                 |
| ------------- | ------------------------------ | --------------------------------------------------------------------------- |
| `description` | **UTF-8 String**               | A human-readable description of the action represented by this transaction. |
| `inputBEEF`   | **VarInt Length + Byte Array** | BEEF data associated with inputs (optional). Use `VarInt` `-1` if absent.   |
| `inputs`      | **VarInt Length + Array**      | An array of input objects (optional). Use `VarInt` `-1` if absent.          |
| `outputs`     | **VarInt Length + Array**      | An array of output objects (optional). Use `VarInt` `-1` if absent.         |
| `lockTime`    | **VarInt**                     | Transaction lock time (optional). Use `-1` to indicate absence.             |
| `version`     | **VarInt**                     | Transaction version (optional). Use `-1` to indicate absence.               |
| `labels`      | **VarInt Length + Array**      | An array of labels (optional). Use `VarInt` `-1` if absent.                 |
| `options`     | **Int8 Flag + Options Struct** | Options object (optional). If present, `1`; else, `0`.                      |

**Inputs Array:**

For each input:

| Field                   | Type                               | Description                                                                                                                                       |
| ----------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `outpoint`              | **Byte Array (32 bytes + VarInt)** | 32-byte TXID followed by `VarInt` output index.                                                                                                   |
| `unlockingScript`       | **VarInt Length + Byte Array**     | Unlocking script (optional). If present, provide length and data. If absent, use `VarInt` `-1` and then provide `unlockingScriptLength` (VarInt). |
| `unlockingScriptLength` | **VarInt**                         | Length of the unlocking script if `unlockingScript` is absent, `VarInt` `-1` otherwise.                                                           |
| `inputDescription`      | **UTF-8 String**                   | Description of this input.                                                                                                                        |
| `sequenceNumber`        | **VarInt**                         | Sequence number (optional). Use `-1` to indicate absence.                                                                                         |

**Outputs Array:**

For each output:

| Field                | Type                           | Description                                               |
| -------------------- | ------------------------------ | --------------------------------------------------------- |
| `lockingScript`      | **VarInt Length + Byte Array** | Locking script.                                           |
| `satoshis`           | **VarInt**                     | Amount in satoshis.                                       |
| `outputDescription`  | **UTF-8 String**               | Description of this output.                               |
| `basket`             | **UTF-8 String**               | Basket name (optional). If absent, `VarInt` `-1`.         |
| `customInstructions` | **UTF-8 String**               | Custom instructions (optional). If absent, `VarInt` `-1`. |
| `tags`               | **VarInt Length + Array**      | Array of tags (optional). If absent, `VarInt` `-1`.       |

* For each tag:
  * **UTF-8 String**

**Options Struct:**

| Field                    | Type                      | Description                                              |
| ------------------------ | ------------------------- | -------------------------------------------------------- |
| `signAndProcess`         | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `acceptDelayedBroadcast` | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `trustSelf`              | **Int8**                  | `1` if `'known'`, `-1` if not provided.                  |
| `knownTxids`             | **VarInt Length + Array** | Array of TXIDs (optional). If absent, `VarInt` `-1`.     |
| `returnTXIDOnly`         | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `noSend`                 | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `noSendChange`           | **VarInt Length + Array** | Array of outpoints (optional). If absent, `VarInt` `-1`. |
| `sendWith`               | **VarInt Length + Array** | Array of TXIDs (optional). If absent, `VarInt` `-1`.     |
| `randomizeOutputs`       | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided.   |

* For each TXID:
  * **Byte Array (32 bytes)**
* For each outpoint:
  * **Byte Array (32 bytes + VarInt)**

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:**

Depending on the outcome, the response may include:

| Field                 | Type                                       | Description                                                                   |
| --------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| `txid`                | **Int8 Flag + Byte Array (32 bytes)**      | If present, `1` followed by 32-byte TXID. Else, `0`.                          |
| `tx`                  | **Int8 Flag + VarInt Length + Byte Array** | If present, `1` followed by transaction data (AtomicBEEF). Else, `0`.         |
| `noSendChange`        | **VarInt Length + Array**                  | Array of outpoints (optional). If absent, `VarInt` `-1`.                      |
| `sendWithResults`     | **VarInt Length + Array**                  | Array of structures containing `txid` and `status`. If absent, `VarInt` `-1`. |
| `signableTransaction` | **Int8 Flag + Struct**                     | If present, `1` followed by signable transaction data. Else, `0`.             |

* For each outpoint:
  * **Byte Array (32 bytes + VarInt)**
* For each `sendWithResults` entry:
  * **`txid`**: **Byte Array (32 bytes)**
  * **`status`**: **Int8** (`1` for `'unproven'`, `2` for `'sending'`, `3` for `'failed'`)

**Signable Transaction Struct:**

| Field       | Type                           | Description                                      |
| ----------- | ------------------------------ | ------------------------------------------------ |
| `tx`        | **VarInt Length + Byte Array** | Transaction data in AtomicBEEF format.           |
| `reference` | **VarInt Length + Byte Array** | Reference identifier as a Base64-encoded string. |

#### 2. `signAction`

* **Call Code:** `2`

**Parameters**

| Field       | Type                           | Description                                            |
| ----------- | ------------------------------ | ------------------------------------------------------ |
| `spends`    | **VarInt Count + Map**         | Map of input indexes to spend information.             |
| `reference` | **VarInt Length + Byte Array** | Reference number as a Base64-encoded string.           |
| `options`   | **Int8 Flag + Options Struct** | Options object (optional). If present, `1`; else, `0`. |

* For each spend:
  * **`inputIndex`**: **VarInt**
  * **`unlockingScript`**: **VarInt Length + Byte Array**
  * **`sequenceNumber`**: **VarInt** (optional). Use `-1` to indicate absence.

**Options Struct:**

Same as in the `createAction` method, but only applicable fields:

| Field                    | Type                      | Description                                            |
| ------------------------ | ------------------------- | ------------------------------------------------------ |
| `acceptDelayedBroadcast` | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided. |
| `returnTXIDOnly`         | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided. |
| `noSend`                 | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided. |
| `sendWith`               | **VarInt Length + Array** | Array of TXIDs (optional). If absent, `VarInt` `-1`.   |

* For each TXID:
  * **Byte Array (32 bytes)**

#### Return Values

* **Error Code (1 byte):** `0` on success.
* **Response Data:**

| Field             | Type                                       | Description                                                                   |
| ----------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| `txid`            | **Int8 Flag + Byte Array (32 bytes)**      | If present, `1` followed by 32-byte TXID. Else, `0`.                          |
| `tx`              | **Int8 Flag + VarInt Length + Byte Array** | If present, `1` followed by transaction data (AtomicBEEF). Else, `0`.         |
| `sendWithResults` | **VarInt Length + Array**                  | Array of structures containing `txid` and `status`. If absent, `VarInt` `-1`. |

#### 3. `abortAction`

* **Call Code:** `3`

**Parameters**

| Field       | Type                       | Description                            |
| ----------- | -------------------------- | -------------------------------------- |
| `reference` | VarInt Length + Byte Array | Reference identifier (Base64-encoded). |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:** None.

#### 4. `listActions`

* **Call Code:** `4`

**Parameters**

| Field                              | Type                  | Description                                                      |
| ---------------------------------- | --------------------- | ---------------------------------------------------------------- |
| `labels`                           | VarInt number + Array | Array of labels to filter actions.                               |
| `labelQueryMode`                   | Int8                  | `1` for `'any'`, `2` for `'all'`, `-1` if not provided.          |
| `includeLabels`                    | Int8                  | `1` for `true`, `0` for `false`, `-1` if not provided.           |
| `includeInputs`                    | Int8                  | Same as above.                                                   |
| `includeInputSourceLockingScripts` | Int8                  | Same as above.                                                   |
| `includeInputUnlockingScripts`     | Int8                  | Same as above.                                                   |
| `includeOutputs`                   | Int8                  | Same as above.                                                   |
| `includeOutputLockingScripts`      | Int8                  | Same as above.                                                   |
| `limit`                            | VarInt                | Maximum number of actions to return. Use `-1` if not provided.   |
| `offset`                           | VarInt                | Number of actions to skip. Use `-1` if not provided.             |
| `seekPermission`                   | Int8                  | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:**

| Field          | Type   | Description                                          |
| -------------- | ------ | ---------------------------------------------------- |
| `totalActions` | VarInt | Total number of actions matching the query.          |
| `actions`      | Array  | Serialized array of action objects (details follow). |

**Action Object:**

For each action:

| Field         | Type                  | Description                                                                     |
| ------------- | --------------------- | ------------------------------------------------------------------------------- |
| `txid`        | Byte Array (32 bytes) | Transaction ID.                                                                 |
| `satoshis`    | VarInt                | Amount in satoshis.                                                             |
| `status`      | Int8                  | Status code (see below).                                                        |
| `isOutgoing`  | Int8                  | `1` for `true`, `0` for `false`.                                                |
| `description` | UTF-8 String          | Description of the action.                                                      |
| `labels`      | VarInt Length + Array | Array of labels (if `includeLabels` is `true`, zero-length otherwise).          |
| `version`     | VarInt                | Transaction version.                                                            |
| `lockTime`    | VarInt                | Transaction lock time.                                                          |
| `inputs`      | VarInt Length + Array | Array of input objects (if `includeInputs` is `true`, zero-length otherwise).   |
| `outputs`     | VarInt Length + Array | Array of output objects (if `includeOutputs` is `true`, zero-length otherwise). |

**Status Codes:**

* `1`: `'completed'`
* `2`: `'unprocessed'`
* `3`: `'sending'`
* `4`: `'unproven'`
* `5`: `'unsigned'`
* `6`: `'nosend'`
* `7`: `'nonfinal'`
* `8`: `'failed'`

**Input Object:**

For each input:

| Field                 | Type                       | Description                                        |
| --------------------- | -------------------------- | -------------------------------------------------- |
| `sourceOutpoint`      | Byte Array                 | Outpoint (TXID + output index).                    |
| `sourceSatoshis`      | VarInt                     | Amount in satoshis of the source output.           |
| `sourceLockingScript` | VarInt Length + Byte Array | Locking script of the source output (if included). |
| `unlockingScript`     | VarInt Length + Byte Array | Unlocking script (if included).                    |
| `inputDescription`    | UTF-8 String               | Description of the input.                          |
| `sequenceNumber`      | VarInt                     | Sequence number.                                   |

**Output Object:**

For each output:

| Field                | Type                       | Description                                  |
| -------------------- | -------------------------- | -------------------------------------------- |
| `outputIndex`        | VarInt                     | Index of the output within the transaction.  |
| `satoshis`           | VarInt                     | Amount in satoshis.                          |
| `lockingScript`      | VarInt Length + Byte Array | Locking script (if included).                |
| `spendable`          | Int8                       | `1` for `true`, `0` for `false`.             |
| `outputDescription`  | UTF-8 String               | Description of the output.                   |
| `basket`             | UTF-8 String               | Basket name (if present, else `-1`).         |
| `tags`               | VarInt Length + Array      | Array of tags (if present, else `-1`).       |
| `customInstructions` | UTF-8 String               | Custom instructions (if present, else `-1`). |

#### 5. `internalizeAction`

* **Call Code:** `5`

**Parameters**

| Field            | Type                       | Description                                                      |
| ---------------- | -------------------------- | ---------------------------------------------------------------- |
| `tx`             | VarInt Length + Byte Array | BEEF-formatted transaction.                                      |
| `outputs`        | VarInt Length + Array      | Array of output objects to internalize.                          |
| `labels`         | VarInt Length + Array      | Array of labels (optional). If absent, `-1`.                     |
| `description`    | UTF-8 String               | Description of the action.                                       |
| `seekPermission` | Int8                       | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Output Object:**

For each output:

| Field                 | Type   | Description                                               |
| --------------------- | ------ | --------------------------------------------------------- |
| `outputIndex`         | VarInt | Index of the output within the transaction.               |
| `protocol`            | Int8   | `1` for `'wallet payment'`, `2` for `'basket insertion'`. |
| `paymentRemittance`   | Struct | Remittance data for payments (if `protocol` is `1`).      |
| `insertionRemittance` | Struct | Remittance data for insertions (if `protocol` is `2`).    |

**Payment Remittance Struct:**

| Field               | Type                       | Description                       |
| ------------------- | -------------------------- | --------------------------------- |
| `senderIdentityKey` | Byte Array (33 bytes)      | Sender's compressed public key.   |
| `derivationPrefix`  | VarInt Length + Byte Array | Base64-encoded derivation prefix. |
| `derivationSuffix`  | VarInt Length + Byte Array | Base64-encoded derivation suffix. |

**Insertion Remittance Struct:**

| Field                | Type                  | Description                                      |
| -------------------- | --------------------- | ------------------------------------------------ |
| `basket`             | UTF-8 String          | Basket name.                                     |
| `customInstructions` | UTF-8 String          | Custom instructions (optional). If absent, `-1`. |
| `tags`               | VarInt Length + Array | Array of tags. If absent, length `0`.            |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:** None.

#### 6. `listOutputs`

* **Call Code:** `6`

**Parameters**

| Field                       | Type                  | Description                                                                                                                       |
| --------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `basket`                    | UTF-8 String          | Basket name.                                                                                                                      |
| `tags`                      | VarInt Length + Array | Array of tags to filter by (optional). If absent, length `0`.                                                                     |
| `tagQueryMode`              | Int8                  | `1` for `'all'`, `2` for `'any'`, `-1` if not provided.                                                                           |
| `include`                   | Int8                  | `1` for `'locking scripts'`, `2` for `'entire transactions'`, `-1` if not provided.                                               |
| `includeCustomInstructions` | Int8                  | `1` for `true`, `0` for `false`, `-1` if not provided.                                                                            |
| `includeTags`               | Int8                  | Same as above.                                                                                                                    |
| `includeLabels`             | Int8                  | Same as above.                                                                                                                    |
| `limit`                     | VarInt                | Maximum number of outputs to return. Use `-1` if not provided.                                                                    |
| `offset`                    | Signed integer        | If positive or zero, skips outputs oldest-first. If negative, counts from the newest output where `-1` denotes the newest output. |
| `seekPermission`            | Int8                  | `1` for `true` (default), `0` for `false`, `-1` if not provided.                                                                  |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:**

| Field          | Type                  | Description                                                                                       |
| -------------- | --------------------- | ------------------------------------------------------------------------------------------------- |
| `totalOutputs` | VarInt                | Total number of outputs matching the query.                                                       |
| `BEEF`         | Byte Array (optional) | Present when `include` is `'entire transactions'`; aggregated BEEF spanning the returned outputs. |
| `outputs`      | Array                 | Serialized array of output objects (details follow).                                              |

**Output Object:**

For each output:

| Field                | Type                       | Description                                     |
| -------------------- | -------------------------- | ----------------------------------------------- |
| `outpoint`           | Byte Array                 | Outpoint (TXID + output index).                 |
| `satoshis`           | VarInt                     | Amount in satoshis.                             |
| `lockingScript`      | VarInt Length + Byte Array | Locking script (if included).                   |
| `spendable`          | Int8                       | Always `1` (indicates the output is spendable). |
| `customInstructions` | UTF-8 String               | Custom instructions (if included).              |
| `tags`               | VarInt Length + Array      | Array of tags (if included).                    |
| `labels`             | VarInt Length + Array      | Array of labels (if included).                  |

#### 7. `relinquishOutput`

* **Call Code:** `7`

**Parameters**

| Field    | Type         | Description                                   |
| -------- | ------------ | --------------------------------------------- |
| `basket` | UTF-8 String | Basket name.                                  |
| `output` | Byte Array   | Outpoint (TXID + output index) to relinquish. |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:** None.

#### 8. `getPublicKey`

* **Call Code:** `8`

**Parameters**

| Field              | Type                       | Description                                                         |
| ------------------ | -------------------------- | ------------------------------------------------------------------- |
| `identityKey`      | UInt8                      | `1` to retrieve the identity key, `0` otherwise.                    |
| `protocolID`       | Struct                     | Protocol ID (if `identityKey` is `0`).                              |
| `keyID`            | UTF-8 String               | Key ID (if `identityKey` is `0`).                                   |
| `counterparty`     | Byte Array or UInt8        | Counterparty public key, or `11` for `'self'`, `12` for `'anyone'`. |
| `privileged`       | Int8                       | `1` for `true`, `0` for `false`, `-1` if not provided.              |
| `privilegedReason` | Int8 Length + UTF-8 String | Reason for privileged access (optional).                            |
| `forSelf`          | Int8                       | `1` for `true`, `0` for `false`, `-1` if not provided.              |
| `seekPermission`   | Int8                       | `1` for `true` (default), `0` for `false`, `-1` if not provided.    |

**Protocol ID Struct:**

| Field            | Type         | Description                        |
| ---------------- | ------------ | ---------------------------------- |
| `securityLevel`  | UInt8        | Security level (`0`, `1`, or `2`). |
| `protocolString` | UTF-8 String | Protocol identifier string.        |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data:**

| Field       | Type                  | Description                          |
| ----------- | --------------------- | ------------------------------------ |
| `publicKey` | Byte Array (33 bytes) | Compressed DER-formatted public key. |

#### 9. `revealCounterpartyKeyLinkage`

* **Call Code:** `9`

**Parameters**

| Field              | Type                      | Description                                            |
| ------------------ | ------------------------- | ------------------------------------------------------ |
| `privileged`       | **Int8**                  | `1` for `true`, `0` for `false`, `-1` if not provided. |
| `privilegedReason` | **UTF-8 String**          | The privileged reason string (Optional).               |
| `counterparty`     | **Byte Array (33 bytes)** | The counterparty's compressed secp256k1 public key.    |
| `verifier`         | **Byte Array (33 bytes)** | The verifier's compressed secp256k1 public key.        |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field                   | Type                           | Description                                                  |
| ----------------------- | ------------------------------ | ------------------------------------------------------------ |
| `prover`                | **Byte Array (33 bytes)**      | The prover's (user's) compressed public key.                 |
| `verifier`              | **Byte Array (33 bytes)**      | The verifier's compressed public key.                        |
| `counterparty`          | **Byte Array (33 bytes)**      | The counterparty's compressed public key.                    |
| `revelationTime`        | **UTF-8 String**               | ISO 8601 timestamp string indicating the time of revelation. |
| `encryptedLinkage`      | **VarInt Length + Byte Array** | The encrypted linkage data.                                  |
| `encryptedLinkageProof` | **VarInt Length + Byte Array** | The encrypted linkage proof data.                            |

#### 10. `revealSpecificKeyLinkage`

* **Call Code:** `10`

**Parameters**

| Field                      | Type                                                         | Description                           |
| -------------------------- | ------------------------------------------------------------ | ------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                       |
| `verifier`                 | **Byte Array (33 bytes)**                                    | The verifier's compressed public key. |

**Key-Related Parameters:**

| Field              | Type                               | Description                                                                                           |
| ------------------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `protocolID`       | **Protocol ID Struct**             | Security level and protocol string (see Protocol ID Struct from call code `8`).                       |
| `keyID`            | **UTF-8 String**                   | Key ID used for key derivation.                                                                       |
| `counterparty`     | **Byte Array (33 bytes) or UInt8** | Counterparty's compressed public key, `11` for `'self'`, `12` for `'anyone'`, or `0` if not provided. |
| `privileged`       | **Int8**                           | `1` for `true`, `0` for `false`, `-1` if not provided.                                                |
| `privilegedReason` | **UTF-8 String**                   | The privileged reason string (Optional).                                                              |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field                   | Type                           | Description                                                                                        |
| ----------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------- |
| `prover`                | **Byte Array (33 bytes)**      | The prover's compressed public key.                                                                |
| `verifier`              | **Byte Array (33 bytes)**      | The verifier's compressed public key.                                                              |
| `counterparty`          | **Byte Array (33 bytes)**      | The counterparty's compressed public key.                                                          |
| `protocolID`            | **Protocol ID Struct**         | The security level and protocol string used for key derivation, first described in call code `8`). |
| `keyID`                 | **UTF-8 String**               | The key ID used for key derivation.                                                                |
| `encryptedLinkage`      | **VarInt Length + Byte Array** | The encrypted linkage data.                                                                        |
| `encryptedLinkageProof` | **VarInt Length + Byte Array** | The encrypted linkage proof data.                                                                  |
| `proofType`             | **UInt8 Number**               | The type of proof generated                                                                        |

#### 11. `encrypt`

* **Call Code:** `11`

**Parameters**

| Field                      | Type                                                         | Description                                                      |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                  |
| `plaintext`                | **VarInt Length + Byte Array**                               | The plaintext data to be encrypted.                              |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field        | Type           | Description                       |
| ------------ | -------------- | --------------------------------- |
| `ciphertext` | **Byte Array** | The encrypted data (AES-256-GCM). |

#### 12. `decrypt`

* **Call Code:** `12`

**Parameters**

| Field                      | Type                                                         | Description                                                      |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                  |
| `ciphertext`               | **VarInt Length + Byte Array**                               | The ciphertext data to be decrypted.                             |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field       | Type           | Description         |
| ----------- | -------------- | ------------------- |
| `plaintext` | **Byte Array** | The decrypted data. |

#### 13. `createHmac`

* **Call Code:** `13`

**Parameters**

| Field                      | Type                                                         | Description                                                      |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                  |
| `data`                     | **VarInt Length + Byte Array**                               | The data over which to compute the HMAC.                         |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field  | Type           | Description              |
| ------ | -------------- | ------------------------ |
| `hmac` | **Byte Array** | The computed HMAC value. |

#### 14. `verifyHmac`

* **Call Code:** `14`

**Parameters**

| Field                      | Type                                                         | Description                                                      |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                  |
| `hmac`                     | **Byte Array**                                               | The HMAC value to verify.                                        |
| `data`                     | **VarInt Length + Byte Array**                               | The data over which the HMAC was computed.                       |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` if the HMAC is valid, non-zero error code otherwise.
* **Response Data:** Nothing extra on successful verification.

#### 15. `createSignature`

* **Call Code:** `15`

**Parameters**

| Field                      | Type                                                         | Description                                                      |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                  |
| `dataTypeFlag`             | **UInt8**                                                    | `1` if signing `data`, `2` if signing `hashToDirectlySign`.      |
| `data`                     | **VarInt Length + Byte Array**                               | The data to be signed (if `dataTypeFlag` is `1`).                |
| `hashToDirectlySign`       | **Byte Array (32 bytes)**                                    | The hash to directly sign (if `dataTypeFlag` is `2`).            |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field       | Type           | Description                      |
| ----------- | -------------- | -------------------------------- |
| `signature` | **Byte Array** | The DER-encoded ECDSA signature. |

#### 16. `verifySignature`

* **Call Code:** `16`

**Parameters**

| Field                      | Type                                                         | Description                                                                |
| -------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------- |
| **Key-Related Parameters** | See **Key-Related Parameters** as defined in call code `10`. |                                                                            |
| `forSelf`                  | **Int8**                                                     | `1` for verifying own signature, `0` otherwise, `-1` if not provided.      |
| `signature`                | **VarInt Length + Byte Array**                               | The DER-encoded ECDSA signature to verify.                                 |
| `dataTypeFlag`             | **UInt8**                                                    | `1` if verifying over `data`, `2` if over `hashToDirectlyVerify`.          |
| `data`                     | **VarInt Length + Byte Array**                               | The data over which the signature was computed (if `dataTypeFlag` is `1`). |
| `hashToDirectlyVerify`     | **Byte Array (32 bytes)**                                    | The hash over which the signature was computed (if `dataTypeFlag` is `2`). |
| `seekPermission`           | Int8                                                         | `1` for `true` (default), `0` for `false`, `-1` if not provided.           |

**Return Values**

* **Error Code (1 byte):** `0` if the signature is valid, non-zero error code otherwise.
* **Response Data:** Nothing extra if successfully verified.

#### 17. `acquireCertificate`

* **Call Code:** `17`

**Parameters**

| Field                 | Type                            | Description                                              |
| --------------------- | ------------------------------- | -------------------------------------------------------- |
| `type`                | **Byte Array (Base64 encoded)** | The certificate type identifier.                         |
| `certifier`           | **Byte Array (33 bytes)**       | The certifier's compressed public key.                   |
| `fields`              | **VarInt number + Map**         | Map of `fieldName` to `fieldValue` (both UTF-8 strings). |
| `privileged`          | **Int8**                        | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `privilegedReason`    | **Int8 Length + UTF-8 String**  | The reason for the privileged request (Optional).        |
| `acquisitionProtocol` | **UInt8**                       | `1` for `'direct'`, `2` for `'issuance'`.                |

Depending on `acquisitionProtocol`, include additional fields:

**If `acquisitionProtocol` is `'direct'` (`1`):**

| Field                | Type                               | Description                                                    |
| -------------------- | ---------------------------------- | -------------------------------------------------------------- |
| `serialNumber`       | **Byte Array (Base64 encoded)**    | The certificate serial number.                                 |
| `revocationOutpoint` | **Byte Array (32 bytes) + VarInt** | The revocation outpoint (TXID + output index).                 |
| `signature`          | **VarInt Length + Byte Array**     | The certifier's signature over the certificate data.           |
| `keyringRevealer`    | **Byte Array (33 bytes) or UInt8** | The revealer's compressed public key, or `11` for 'certifier'. |
| `keyringForSubject`  | **VarInt number of entries + Map** | Map of `fieldName` to keyring values.                          |

**If `acquisitionProtocol` is `'issuance'` (`2`):**

| Field          | Type             | Description                       |
| -------------- | ---------------- | --------------------------------- |
| `certifierUrl` | **UTF-8 String** | The certifier's URL for issuance. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field         | Type           | Description                                                                  |
| ------------- | -------------- | ---------------------------------------------------------------------------- |
| `certificate` | **Byte Array** | The serialized certificate binary data (format described in call code `19`). |

#### 18. `listCertificates`

* **Call Code:** `18`

**Parameters**

| Field              | Type                           | Description                                                                |
| ------------------ | ------------------------------ | -------------------------------------------------------------------------- |
| `certifiers`       | **VarInt Length + Array**      | Array of certifier public keys (each 33 bytes).                            |
| `types`            | **VarInt Length + Array**      | Array of certificate types (where each entry is a byte array of 32 bytes). |
| `limit`            | **VarInt**                     | Maximum number of certificates to return, `-1` if not provided.            |
| `offset`           | **VarInt**                     | Number of certificates to skip, `-1` if not provided.                      |
| `privileged`       | **Int8**                       | `1` for `true`, `0` for `false`, `-1` if not provided.                     |
| `privilegedReason` | **Int8 Length + UTF-8 String** | The privileged reason string (Optional)                                    |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field               | Type       | Description                                         |
| ------------------- | ---------- | --------------------------------------------------- |
| `totalCertificates` | **VarInt** | Total number of certificates matching the criteria. |
| `certificates`      | **Array**  | Array of certificate binary data (see below).       |

Each certificate in the `certificates` array is serialized as:

* **VarInt Length + Byte Array** representing the certificate binary data (the certificate binary format as described in `proveCertificate` method).

#### 19. `proveCertificate`

* **Call Code:** `19`

**Parameters**

| Field              | Type                           | Description                                              |
| ------------------ | ------------------------------ | -------------------------------------------------------- |
| `certificate`      | **Certificate Struct**         | The certificate data (see **Certificate Struct** below). |
| `fieldsToReveal`   | **VarInt Length + Array**      | Array of `fieldName` strings (UTF-8) to reveal.          |
| `verifier`         | **Byte Array (33 bytes)**      | The verifier's compressed public key.                    |
| `privileged`       | **Int8**                       | `1` for `true`, `0` for `false`, `-1` if not provided.   |
| `privilegedReason` | **Int8 Length + UTF-8 String** | The privileged reason (Optional).                        |

**Certificate Struct:**

| Field                | Type                            | Description                                   |
| -------------------- | ------------------------------- | --------------------------------------------- |
| `type`               | **Byte Array (Base64 encoded)** | Certificate type identifier.                  |
| `subject`            | **Byte Array (33 bytes)**       | Subject's compressed public key.              |
| `serialNumber`       | **Byte Array (Base64 encoded)** | Certificate serial number.                    |
| `certifier`          | **Byte Array (33 bytes)**       | Certifier's compressed public key.            |
| `revocationOutpoint` | **Byte Array**                  | Revocation outpoint (TXID + output index).    |
| `signature`          | **VarInt Length + Byte Array**  | Certificate signature.                        |
| `fields`             | **VarInt Length + Map**         | Map of `fieldName` to encrypted `fieldValue`. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

| Field                | Type                              | Description                           |
| -------------------- | --------------------------------- | ------------------------------------- |
| `keyringForVerifier` | **VarInt number of fields + Map** | Map of `fieldName` to keyring values. |

#### 20. `relinquishCertificate`

* **Call Code:** `20`

**Parameters**

| Field          | Type                            | Description                        |
| -------------- | ------------------------------- | ---------------------------------- |
| `type`         | **Byte Array (Base64 encoded)** | Certificate type identifier.       |
| `serialNumber` | **Byte Array (Base64 encoded)** | Certificate serial number.         |
| `certifier`    | **Byte Array (33 bytes)**       | Certifier's compressed public key. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data:** None.

#### 21. `discoverByIdentityKey`

* **Call Code:** `21`

**Parameters**

| Field            | Type                      | Description                                                                                                                         |
| ---------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `identityKey`    | **Byte Array (33 bytes)** | The identity key to search for certificates.                                                                                        |
| `limit`          | **VarInt**                | Maximum number of certificates to return, `-1` if not provided.                                                                     |
| `offset`         | **VarInt**                | Number of certificates to skip, `-1` if not provided.                                                                               |
| `seekPermission` | Int8                      | `1` for `true`, `0` for `false`, `-1` if not provided; current SDK validation defaults omitted discovery permission flags to false. |

**Return Values**

* **Error Code (1 byte):** `0` on success, non-zero error code otherwise.
* **Response Data (on success):**

This method returns a list of certificates with additional certifier information and decrypted fields.

| Field               | Type       | Description                                        |
| ------------------- | ---------- | -------------------------------------------------- |
| `totalCertificates` | **VarInt** | Total certificates matching the identity key.      |
| `certificates`      | **Array**  | Array of extended certificate structs (see below). |

Each certificate includes:

* **Certificate Binary Data:** Serialized certificate as in `proveCertificate` method.
* **Certifier Info Struct:** Contains the following fields:

  | Field         | Type             | Description                                |
  | ------------- | ---------------- | ------------------------------------------ |
  | `name`        | **UTF-8 String** | Certifier's name.                          |
  | `iconUrl`     | **UTF-8 String** | Certifier's icon URL.                      |
  | `description` | **UTF-8 String** | Certifier's description.                   |
  | `trust`       | **UInt8**        | User trust level of this certifier (1-10). |
* **Publicly Revealed Keyring:** Map of `fieldName` to keyring values.
* **Decrypted Fields:** Map of `fieldName` to decrypted `fieldValue` strings (UTF-8).

#### 22. `discoverByAttributes`

* **Call Code:** `22`

**Parameters**

| Field            | Type                    | Description                                                                                                                         |
| ---------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `attributes`     | **VarInt Length + Map** | Map of `fieldName` to `fieldValue` strings (UTF-8).                                                                                 |
| `limit`          | **VarInt**              | Maximum number of certificates to return, `-1` if not provided.                                                                     |
| `offset`         | **VarInt**              | Number of certificates to skip, `-1` if not provided.                                                                               |
| `seekPermission` | Int8                    | `1` for `true`, `0` for `false`, `-1` if not provided; current SDK validation defaults omitted discovery permission flags to false. |

**Return Values**

Same as `discoverByIdentityKey` method.

#### 23. `isAuthenticated`

* **Call Code:** `23`

**Parameters**

None.

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data (on success):**

| Field           | Type      | Description                          |
| --------------- | --------- | ------------------------------------ |
| `authenticated` | **UInt8** | `1` if authenticated, `0` otherwise. |

#### 24. `waitForAuthentication`

* **Call Code:** `24`

**Parameters**

None.

**Return Values**

* **Error Code (1 byte):** `0` once the user is authenticated.
* **Response Data:** None.

#### 25. `getHeight`

* **Call Code:** `25`

**Parameters**

None.

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data (on success):**

| Field    | Type       | Description           |
| -------- | ---------- | --------------------- |
| `height` | **VarInt** | Current block height. |

#### 26. `getHeaderForHeight`

* **Call Code:** `26`

**Parameters**

| Field    | Type       | Description                                        |
| -------- | ---------- | -------------------------------------------------- |
| `height` | **VarInt** | The block height for which to retrieve the header. |

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data (on success):**

| Field    | Type                      | Description                             |
| -------- | ------------------------- | --------------------------------------- |
| `header` | **Byte Array (80 bytes)** | The serialized block header (80 bytes). |

#### 27. `getNetwork`

* **Call Code:** `27`

**Parameters**

None.

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data (on success):**

| Field     | Type      | Description                           |
| --------- | --------- | ------------------------------------- |
| `network` | **UInt8** | `0` for 'mainnet', `1` for 'testnet'. |

#### 28. `getVersion`

* **Call Code:** `28`

**Parameters**

None.

**Return Values**

* **Error Code (1 byte):** `0` on success.
* **Response Data (on success):**

| Field     | Type             | Description                                         |
| --------- | ---------------- | --------------------------------------------------- |
| `version` | **UTF-8 String** | The wallet's version string (e.g., `vendor-1.0.0`). |

### General Notes

* **Absence of Optional Fields:** Optional fields are often indicated by a special value (e.g., `-1` as a `VarInt` for length fields). Implementations should handle these values appropriately to determine the presence or absence of data.
* **Encoding of Strings:** All strings are UTF-8 encoded and prefixed with their length as a `VarInt`. The `-1` value indicates absence, notably different from `0` which indicates an empty string (`""`).
* **Error Codes:** While only `0` (success) and `1` (generic error) are defined here, implementations may use additional error codes for error classification as needed.
* **Serialization of Nested Structures:** Arrays and maps are prefixed with their lengths as VarInts. Nested fields must follow the same serialization rules as their top-level counterparts.

This ABI specification provides a complete and detailed description of the binary protocol used for communication between wallets and applications. By adhering to this specification, developers can ensure compatibility and interoperability across different implementations, enabling a robust and secure ecosystem for wallet interactions within the BSV blockchain ecosystem.

Implementers should carefully follow the serialization and deserialization rules outlined for each method to ensure correct functionality.

## Glossary of Terms

**AbortAction**: A method in the Wallet Interface that allows the cancellation of a transaction that is in progress and has not yet been finalized or sent to the network.

**Action**: An Action is a Bitcoin transaction plus metadata. It is a foundational concept that aligns with "Action Oriented Programming" (see: <https://projectbabbage.com/docs/babbage-sdk/concepts/actions-aop>).

**Actions**: In the context of this specification, an **Action** is a Bitcoin transaction (as defined within the BSV blockchain context) that is enriched with additional metadata such as descriptions, labels, and other optional data. This concept follows the "Action Oriented Programming" paradigm, where each Action represents a business-level event or operation captured as a blockchain transaction. Actions are created using the `createAction` method, managed and categorized using labels, and can incorporate outputs tagged for easier retrieval.

**AES-256-GCM**: Advanced Encryption Standard (AES) cipher with a 256-bit key size using Galois/Counter Mode (GCM); used for symmetric encryption and decryption operations within the Wallet Interface (as per [BRC-2](/wallet/0002)). By convention, 32-byte initialization vectors are prepended to the beginning of the ciphertext.

**Application Binary Interface (ABI)**: A specification detailing the binary communication protocol between applications and wallets over a Wallet Wire, ensuring consistent method call structures, data serialization, and error handling as defined in the Wallet Interface.

**Background Evaluation Extended Format (BEEF)**: A compact data format specified in [BRC-62](/transactions/0062) for representing Bitcoin transactions optimized for Simplified Payment Verification (SPV) and efficient data transmission within the Wallet Interface.

**Baskets**: Conceptual containers within a wallet used to group and manage specific Unspent Transaction Outputs (UTXOs) as per [BRC-46](/wallet/0046), enabling organized tracking and handling across applications or protocols.

**Bitcoin Request for Comment (BRC)**: An informal proposal or standard within the BSV ecosystem that outlines protocols, methods, or guidelines for functionalities such as transactions, key derivation, network architecture, and wallet interfaces.

**BKDS (BSV Key Derivation Scheme)**: A key derivation scheme defined in [BRC-42](/key-derivation/0042) that allows wallets to derive multiple unique public-private key pairs from a shared master key using the secp256k1 elliptic curve and a counterparty.

**Blockchain Height**: The number of blocks in the longest valid chain of the blockchain, representing the latest block's height within the BSV network.

**BooleanDefaultFalse**: A data type representing an optional boolean parameter that defaults to `false` if not provided in method arguments.

**BooleanDefaultTrue**: A data type representing an optional boolean parameter that defaults to `true` if not provided in method arguments.

**BSV Blockchain**: A network emphasizing stability, scalability, and adherence to Satoshi Nakamoto's original vision for Bitcoin as a token system, a micropayment system, and a peer-to-peer electronic cash system.

**Call Code**: An unsigned integer used in the ABI specification to represent the method being invoked over the Wallet Wire.

**Certificate**: In the context of [BRC-52](/peer-to-peer/0052), a digital identity document that encapsulates a subject's identity information, certified by a trusted entity, with support for selective field encryption.

**Certificate Field Name**: The name of a specific attribute or piece of data within an identity certificate, used for identification and selective revelation.

**Certificate Revocation**: The process by which an identity certificate is invalidated, often implemented via spending a specific UTXO tied to a revocation outpoint; used to indicate the certificate is no longer valid.

**Compressed DER-formatted Public Key**: A public key formatted according to the Distinguished Encoding Rules (DER), compressed to represent a point on the secp256k1 elliptic curve using 33 bytes (66 hexadecimal characters). The first byte denotes whether Y is odd, and the remaining 32 bytes comprise the X coordinate on the curve.

**Counterparty**: An entity (e.g., sender, receiver, verifier) involved in a transaction or key derivation process, identified by their public key; used in BKDS and defined in [BRC-43](/key-derivation/0043).

**createAction**: A method that constructs a new transaction based on provided inputs, outputs, and options. It can return a completed transaction, or a `signableTransaction` if the transaction is not finalized.

**Custom Instructions**: Data attached to UTXOs that provide contextual information or necessary unlocking context within application logic, represented as a string in the Wallet Interface.

**Derivation Prefix/Suffix**: Values used during the internalization of payment outputs, as described within [BRC-29](/payments/0029) to generate different key pairs for each output across the same payment.

**ActionDescriptionString5to2000Characters**: A string data type used for action descriptions, input descriptions, output descriptions, and internalization descriptions within the Wallet Interface, constrained to a length between 5 and 2000 characters in current interoperable implementations.

**DescriptionString5to50Characters**: A string data type used for short descriptions such as `privilegedReason`, constrained to a length between 5 and 50 characters.

**Digital Signature**: A cryptographic value generated using a private key that verifies the authenticity and integrity of data, as outlined in [BRC-3](/wallet/0003) with ECDSA.

**ECDH (Elliptic Curve Diffie-Hellman)**: A key agreement protocol using elliptic curve cryptography that allows two parties to establish a shared secret over an insecure channel.

**ECDSA (Elliptic Curve Digital Signature Algorithm)**: A cryptographic algorithm used for creating digital signatures using elliptic curve cryptography, specifically over the secp256k1 curve.

**Encryption/Decryption**: The processes of scrambling data to prevent unauthorized access (encryption) and restoring it to its original form (decryption), as specified in [BRC-2](/wallet/0002) with AES-256-GCM and used within the Wallet Interface.

**Entity Icon URL**: A URL pointing to an icon representing a trusted entity or certifier in identity certificates, used for display and identification purposes.

**Entity Name**: The name of a trusted entity or certifier associated with an identity certificate, providing a human-readable identifier for the certifier.

**Error Handling**: The standardized method by which errors are communicated within the Wallet Interface, using structures containing status, code, description, and optional context.

**ErrorCodeString10To40Characters**: A data type representing a machine-readable error code string with a length between 10 and 40 characters, used in error responses.

**ErrorDescriptionString20To200Characters**: A data type representing a human-readable error description string with a length between 20 and 200 characters, providing details about an error.

**Fully Qualified Domain Name (FQDN)**: The complete domain name of a specific computer or host on the internet, used as the `OriginatorDomainNameString` to identify the originator of a request in the Wallet Interface.

**HMAC (Hash-based Message Authentication Code)**: A specific type of message authentication code involving a cryptographic hash function and a secret key, used for data integrity checks within the Wallet Interface.

**Identity Key**: The master public key of a wallet derived from the master private key using the secp256k1 elliptic curve; used to identify the wallet owner and derive child keys.

**Input (Transaction Input)**: A reference in a transaction to a previous UTXO that is being spent, containing details such as the outpoint and unlocking script.

**Key Derivation**: The process of generating child keys from a master key using a specified algorithm like BKDS, providing unique keys for different purposes or interactions.

**Key ID**: A unique identifier differentiating specific keys under the same Protocol ID, allowing for multiple derived keys with different purposes within the same context as defined in [BRC-43](/key-derivation/0043).

**Key Linkage**: Information that reveals the relationship between derived keys, used for transparency, auditability, and verification, particularly as per [BRC-69](/key-derivation/0069) and protected during transit by [BRC-72](/key-derivation/0072).

**Labels (Transaction Labels)**: Strings used to categorize transactions within a wallet for organizational and filtering purposes.

**Locking Script**: A script associated with a transaction output that specifies the conditions under which the output can later be spent (also known as an output script or `scriptPubKey`, though `scriptPubKey` should no longer be used).

**Lock Time**: A parameter in a Bitcoin transaction that specifies the earliest time or block height at which the transaction can be included in the blockchain.

**Master Private Key**: The primary private key of a wallet from which other keys are derived, forming the root of the wallet's key structure.

**Merkle Root**: The root hash of a Merkle tree, summarizing all transactions in a block; used in SPV to verify transaction inclusion without downloading the entire block.

**Originator Domain Name String**: The fully qualified domain name (FQDN) of the application that originated the request, used to authenticate and authorize requests in the Wallet Interface.

**Outpoint**: A reference to a specific output in a previous transaction, identified by the transaction ID (`txid`) and output index (`vout`), used when spending UTXOs.

**Output (Transaction Output)**: A component of a transaction that specifies the recipient of funds or data, including the amount (in satoshis) and a locking script.

**Output Tags**: Strings assigned to outputs within wallets to categorize or indicate attributes, facilitating searching, sorting, and filtering of UTXOs.

**Payment Internalization**: The process by which a wallet accepts and manages incoming transactions by parsing, tagging, and organizing outputs, as per [BRC-29](/payments/0029).

**Privileged Mode**: A mode of operation within the wallet where sensitive or high-security operations are performed using a secondary, more secure set of keys; requires additional authorization.

**Private Digital Signature**: A signature intended for a specified receiver, created using a private key derived via BKDS, ensuring that only the intended recipient can verify it using the corresponding public key.

**Proof-Type**: A one-byte unsigned integer (0-255) defined in [BRC-97](/wallet/0097) to specify the type of proof included in specific key linkage revelations. Proof Type 0 indicates no proof is provided, while Proof Types 1-255 are reserved for various zero-knowledge proof (ZKP) schemes, such as STARKs, Bulletproofs or SNARKs. This extensible format supports future advancements in ZKP technologies, enabling flexible and verifiable interactions while maintaining backward compatibility.

**Protocol ID**: An identifier used in [BRC-43](/key-derivation/0043) to define the context or usage of a derived key, combining a security level and a protocol string (e.g., `[1, "document signing"]`).

**PubKeyHex**: A hexadecimal string representing a compressed DER-formatted secp256k1 public key, 66 characters long (33 bytes).

**Satoshi**: The smallest unit of Bitcoin, equal to 0.00000001; used as the unit for transaction amounts within the Wallet Interface.

**Script Validation**: The process of evaluating and verifying the correctness of scripts (locking and unlocking scripts) within transactions, ensuring they adhere to Bitcoin's scripting rules.

**Security Level**: In [BRC-43](/key-derivation/0043), a classification that determines the required permissions and access controls for using a derived key (Level 0: open access, Level 1: requires cross-counterparty authorization, Level 2: requires individual permission for each counterparty).

**Shared Secret**: In ECDH key exchange, a secret value derived by both parties using their own private key and the other party’s public key, used as the basis for deriving child keys or symmetric encryption keys.

**Signable Transaction**: A Bitcoin transaction that has been created but is not yet fully signed or finalized. This transaction is in a preparatory state where it requires one or more digital signatures before it can be considered complete and potentially broadcast to the Bitcoin network. Returned from `createAction` as a partially constructed transaction in **BEEF** (Background Evaluation Extended Format), which includes a reference number for later signing or aborting. This appears when an input does not have its unlocking script yet. The transaction is partially constructed and returned with a reference. The caller can use this reference with `signAction` to supply the necessary unlocking scripts later. This mechanism decouples the initial transaction construction from the final signing steps.

**signAction**: A method that signs a previously created transaction (a **Signable Action**) using the unlocking scripts provided, allowing it to be finalized and potentially broadcast.

**Signature**: Data that proves the authenticity and integrity of a message or transaction, created using a private key and verifiable with the corresponding public key.

**Simplified Payment Verification (SPV)**: A method for verifying that a transaction is included in the blockchain without downloading the entire chain, by verifying the transaction's inclusion in a block (or its ancestors), checking the scripts that transfer coins, and validating that block via its Merkle root and proof-of-work.

**TXID (Transaction ID)**: A unique identifier for a transaction, calculated as a double SHA-256 hash of the transaction data.

**TagQueryMode**: A parameter determining how tags are matched when listing outputs within a wallet; can be `'any'` (matches if any tag matches) or `'all'` (matches only if all queried tags are present).

**Transaction**: An instruction sent to the Bitcoin network to transfer Bitcoin from one or more inputs to one or more outputs; forms the fundamental operation within the blockchain. Also referred to as **Actions** when used within applications.

**Unlocking Script**: A script that satisfies the conditions specified by the locking script of an output, allowing the output to be spent (also known as an input script or `scriptSig`, though `scriptSig` should no longer be used).

**Unspent Transaction Output (UTXO)**: An output from a prior transaction that has not yet been spent; represents an amount of Bitcoin controlled by a script that can be used as an input in a new transaction.

**Vendor Neutrality**: A design principle ensuring that an interface or protocol can be implemented by any vendor without proprietary constraints, promoting interoperability and standardization.

**VersionString7To30Characters**: A data type representing a version string of the wallet, constrained to be between 7 and 30 characters, in the format `[vendor]-[major].[minor].[patch]`.

**Wallet Interface**: The standardized set of methods and protocols defined in [BRC-100](/wallet/0100) for communication between wallets and applications in the BSV ecosystem, designed to be unified, vendor-neutral, and open.

**Wallet Wire**: The communication channel or protocol over which applications and wallets exchange messages using the defined ABI specification.

#### Conclusion

This specification provides a comprehensive and standardized framework for Wallet-to-Application interactions within the BSV ecosystem. By defining a clear and secure ABI, it fosters interoperability, scalability, and vendor-neutral implementation. The type constraints, structured error handling, and robust serialization methods enable developers to build reliable and secure applications across diverse environments.

Through its open and stable design, this specification ensures that wallets and applications can interoperate seamlessly, driving innovation while preserving stability. The reserved ranges for future call codes and adherence to backward compatibility principles also ensure long-term adaptability to new use cases and evolving technologies.

Developers and implementers are encouraged to align with this standard to enable a unified ecosystem, empowering applications and wallets to interact efficiently and securely, ultimately advancing the adoption and utility of the BSV blockchain.

At long last.


# PCW-1 : Peer Cash Wallet Protocol

This file contains the full specification from the [Substack article](https://substack.com/@cstominaga/note/p-171965159?r=5u2jjk) by Dr. Craig S Wright, including abstract, sections 1-17, and state machine diagram description.

The PCW-1 specification was designed and architected by Dr. Craig S. Wright and the protocol was engineered and implemented by Dr. Roy Murphy. The reference protocol implementation is located [here](https://github.com/murphsicles/PCW-1).

## Abstract

This work specifies a Bitcoin wallet workflow that conducts direct IP-to-IP negotiation between two identified parties and settles a payment as many independent on-chain transactions (“notes”). Identity keys are used only for ECDH and message authentication; they never appear on-chain. For each invoice and note index, a shared secret and an invoice fingerprint deterministically derive a unique recipient public key, ensuring unlinkability outside the two parties. The payer splits the total into bounded denominations agreed with the recipient and constructs one standard P2PKH transaction per note. Each transaction is funded with a disjoint input set and (if required) returns change to a per-note sender address derived deterministically so that change never overlaps across notes. Either party may submit any subset of fully signed transactions at any time; settlement is established by confirmation depth. The paper formalises notation, message frames, per-note key derivation, deterministic bounded splitting, disjoint coin-selection and change algorithms, ordering and pacing of broadcasts, selective-disclosure receipts, reissue rules prior to broadcast, and behaviours under reorgs. The result is a practical, auditable, and privacy-preserving method to enforce recipient constraints while keeping every note an independent on-chain payment.

## Bitcoin IP-to-IP Note Settlement: Purpose, Rationale, and Implementation

1. What is it.

A payment is settled as a set of small, standard on-chain transactions (“notes”), each paying a bounded amount to a unique address that only the recipient can spend. Bounds (per-note minimum and maximum) are negotiated up front, and the total is split into note amounts that sum exactly to the invoice total. Every note is valid on its own, funded by inputs that no other note in the same invoice uses, and optionally returns change to a unique, invoice-scoped sender address. Either side may submit any note to the network; confirmation depth defines settlement for that note.

Two long-lived identity keys authenticate the off-chain session and never appear on-chain. A per-invoice shared element derived from those identities, together with the hash of the canonical invoice JSON, scopes all derivations: per-note recipient addresses, per-note sender change addresses, the exact split of amounts, labels, and receipts. Because the scope is per invoice, derivations are never reused across invoices, and the same index i under a different invoice produces unrelated addresses.

2. Why it exists.

Policy-compliant intake for the payee. A recipient can publish firm bounds and a fee-rate floor once, then accept large totals without ever receiving an out-of-policy note. This simplifies operations and avoids exposing internal wallet structure. • Determinism and symmetry for the payer. The payer derives exactly the same note set the recipient expects, without shipping secrets or bespoke scripts. If both ends can parse the same canonical JSON and run the same hashes and curve operations already used in Bitcoin, they interoperate. • Audit without surveillance. A Merkle root commits to the entire set of notes. Later, either party can reveal proofs for any subset (for example, to an auditor) without disclosing the remainder. Off-chain logs are signed by identity keys, allowing reconstruction of intent without putting identities on-chain.

All of this uses only primitives that already exist in Bitcoin: secp256k1 keys, SHA-256 and RIPEMD-160, standard P2PKH outputs, and raw transaction serialisation. No new opcodes, no new script types, no new cryptographic gadgets.

3. What does it do (functional view)

Identity and scope. Alice (payer) and Bob (payee) authenticate using long-lived identity keypairs. They exchange two compact, signed messages: the policy (Bob’s bounds, anchor, fee floor, expiry) and the invoice (Alice’s total, unit, terms, and a reference to the policy). From these they compute (i) a shared element via ECDH and (ii) the invoice fingerprint, the SHA-256 hash of the canonical invoice JSON. The pair {Z, H\_I} defines the scope for everything that follows.

Bounded splitting. Within the published bounds, the total is decomposed into N note amounts. The number N is feasible by construction (between ceil(T ÷ v\_max) and floor(T ÷ v\_min)). The amounts are derived deterministically from {Z, H\_I} so both parties compute the same vector and then permute it to remove any index-to-size correlation. Off-chain, the split leaks only that each note lies within bounds.

Per-note addressing. For each index i, the payer derives the recipient’s public key for that note by tweaking the recipient’s on-chain anchor with a scalar computed from {Z, H\_I, i} and a role label. Only the recipient can compute the corresponding private key. In the same scope, the payer derives a unique sender change address for index i. Identity keys are never used on-chain; only anchors appear in settlement keys.

Disjoint funding and change. The payer assigns a strictly disjoint set of inputs to each note from a snapshot of her UTXO pool. No input appears in two notes. A standard size estimator and the negotiated fee-rate floor decide whether a note has one output (exact) or two (payee + change). When present, change always pays to the per-note sender change address. Change from one note never funds another note in the same invoice.

Transaction formation and broadcast. Each note is a standard P2PKH transaction, fully signed and valid in isolation. Either party may broadcast any subset; duplicate submission is benign. Broadcast may be all-at-once, paced, or in bursts; confirmation depth chosen by the recipient defines finality per note.

Receipts and selective disclosure. After notes exist, each side can compute a per-note leaf that commits to the index, txid, amount, and address payload, and then a Merkle root over the set of leaves. The root and a manifest of indices and txids provide a commitment that later supports selective proofs for any subset.

Failure handling. Deterministic behaviours cover insufficient inputs, pre-broadcast fee changes, external conflicts, reorgs, and expiry. Reissue preserves indices and addresses; older raw bytes are marked “superseded” off-chain and are not broadcast.

4. How it does it (implementation narrative)

Module 1 — Canonical JSON and signing. Provide byte-identical encodings of policy, invoice, logs, and receipts. Every signed artefact includes a detached signature made by the relevant identity key over the canonical bytes (signature fields omitted from the preimage). This guarantees both sides hash the same content when computing the invoice fingerprint and policy hash.

Module 2 — Scope and derivations. Given {Z, H\_I}, derive deterministically: (a) per-note recipient keys (label “recv”), (b) per-note sender change keys (label “snd”), (c) the split of amounts (label “split”), and (d) any pacing schedule (label “pace”). Output: reproducible addresses, labels, and amounts that both wallets compute locally without sharing per-note data.

Module 3 — Coin selection with reservation. Take a snapshot of the payer’s spendable UTXOs and allocate disjoint input sets to each note. Preference order: exact matches; then single-input near-over with valid change; then few-input combinations with minimal overshoot, always respecting dust and fee floors. If inputs are too coarse, perform one payer→payer fan-out and restart reservations. Reservations are tagged by the note identifier so rebuilding from logs yields the same table.

Module 4 — Transaction builder. For each index, combine reserved inputs with the payee output (and optional change), order inputs and outputs deterministically, compute the fee at the floor, and sign every input in the standard way. Output: raw transaction bytes and txid per note. Log the per-note metadata (index, note id, invoice hash, recipient address, amount, txid) with signatures.

Module 5 — Broadcast manager. Compute a nominal plan (all-at-once, paced, or bursts) using seeds derived from {Z, H\_I}. Either side may submit; duplicate submissions yield the same txid. Periodic rebroadcast continues until the recipient’s depth is reached or a note is cancelled or reissued. Hold-time limits trigger automatic reissue or cancel actions; all transitions are signed in the log.

Module 6 — Receipts and proofs. Compute the Merkle root over per-note leaves and store a manifest of indices and txids. Later, produce compact proofs for any subset by disclosing only those leaves and paths; a verifier recomputes the root without learning undisclosed notes.

Module 7 — Logging and audit. Every state transition—reservation, signing, broadcast, reissue, cancel, orphan—is recorded as canonical JSON with timestamps and identity signatures and chained by a “prev\_hash” field. Given these logs plus the public chain, an auditor can reconstruct the intended settlement set and verify that exactly one transaction per index was meant to settle.

5. The need and its consequences

Operational fit. Many recipients want steady, bounded inflows rather than sporadic large hits. By decomposing a purchase into bounded notes, intake risk and internal accounting become simpler, while the payer gains a clear, deterministic procedure that never leaks identity keys on-chain.

Robustness. Notes are independent and inputs are disjoint, so partial progress is meaningful: a subset can confirm while the rest are queued or reissued. Reorg handling is straightforward: rebroadcast the same bytes.

Privacy by construction. Identity keys stay off-chain. Per-invoice and per-note derivations require the off-chain scope to reproduce, so outsiders cannot link the recipient’s addresses across the set. Change addresses are unique per note, defeating shared-change clustering. Pacing reduces simple time-based clustering. Selective receipts allow proving exactly what is needed—no more.

Determinism and interoperability. Independent implementations that follow the rules produce the same addresses, the same split, the same reservations, and the same receipts. Disputes are resolvable by recomputation from signed logs.

Scope discipline. Binding all derivations to {Z, H\_I} prevents cross-invoice reuse. Index i is meaningful only within the invoice that defined it. This keeps the address space clean and prevents accidental collisions when many invoices are active.

6. Limitations and boundaries

The design uses standard transaction forms and well-understood cryptographic primitives. It does not compress notes into nonstandard constructs, introduce new script paths, or rely on external relays. Fees and confirmation policies remain subject to network conditions. The payer’s own inputs are clustered within each note by necessity; the privacy goal here is to avoid linking recipient notes to each other, not to conceal that a single payer funded each note.

7. What it means

This recentres Bitcoin settlement on two authenticated endpoints who deterministically derive everything needed for a payment and act independently to bring it to finality. The outcome is not merely “many small transactions”; it is a protocol for invoice-scoped, symmetric, auditable settlement:

Invoice-scoped: every artefact—addresses, amounts, labels, receipts—derives from the invoice fingerprint and shared element. • Symmetric: either party can complete settlement of any note at any time. • Auditable: small, signed JSON records and a single Merkle root suffice to reconstruct and prove the payment’s history.

An engineer following the formal sections can implement these modules with no new cryptography, no changes to standard transaction formats, and no special network behaviour. A reader evaluating the design can see what it is, why it exists, how it works, and what it achieves, while staying strictly within Bitcoin’s established toolset.

1. Objective and model

Objective. Specify a direct, invoice-scoped payment workflow in Bitcoin in which two principals — Alice (payer) and Bob (payee) — agree explicit bounds for the value of each note and settle a total as many independent on-chain transactions. Each note is a standard transaction paying a bounded amount to a unique recipient address computable by the payer yet spendable only by the payee. Identity keys have a single role: authenticate the off-chain session and yield shared material that scopes all deterministic derivations; identity keys never appear in locking scripts, never fund or receive outputs, and never enter the on-chain graph. “IP-to-IP” denotes a direct, mutually authenticated message channel adequate to exchange compact UTF-8 JSON documents and raw transaction serialisations; transport mechanics are outside scope and irrelevant to correctness. Either party may submit any fully formed note at any time; settlement finality is defined solely by the confirmation depth the payee requires for that note. The design enforces three global guarantees: note independence, strict non-overlap of inputs, and determinism sufficient for audit and replay.

Actors and keys. Alice maintains a long-lived identity pair Kₐ = (kₐ, Pₐ) and a sender anchor A = a·G used only to derive per-note change addresses. Bob maintains a long-lived identity pair Kᵦ = (kᵦ, Pᵦ) and a recipient anchor B = b·G used only to derive per-note receiving keys. Identity keys authenticate and scope; anchors settle on-chain. For each invoice the parties compute a shared secret Z := ECDH(kₐ, Pᵦ) = ECDH(kᵦ, Pₐ), and an invoice fingerprint Hᴵ := SHA-256(canonical-JSON(invoice)), which together bind all subsequent derivations to that invoice.

Scope and channel model. The off-chain state for a payment is a finite, authenticated transcript: policy → invoice → acknowledgements → (optional) per-note metadata → (optional) raw transactions → receipts. The only required capability of the channel is confidential, integrity-protected exchange of these small artefacts; routing, addressability, and link maintenance are orthogonal and out of scope.

Payment decomposition. Let T be the invoice total (in the smallest unit) and let the payee’s bounds be \[vₘᵢₙ, vₘₐₓ] with 0 < vₘᵢₙ ≤ vₘₐₓ. The payment is decomposed into N ≥ 2 notes with amounts a = (a₀, a₁, …, aₙ₋₁), such that for every index i: vₘᵢₙ ≤ aᵢ ≤ vₘₐₓ and Σ aᵢ = T. Feasibility requires ⌈T ÷ vₘₐₓ⌉ ≤ N ≤ ⌊T ÷ vₘᵢₙ⌋. The pair (N, a) and all per-note labels are deterministically derived from (Z, Hᴵ) and the accepted policy so that both parties — given the same inputs — arrive at the identical target set without exchanging per-note values in the clear.

Recipient addressing (model-level). For each index i ∈ {0, …, N−1} a unique recipient public key Pᴮ,ᵢ is derived so that: (1) Alice can compute Pᴮ,ᵢ and therefore the standard address Addrᴮ,ᵢ for the note; (2) only Bob can compute the corresponding private key kᴮ,ᵢ that spends from Addrᴮ,ᵢ; (3) no two notes of the invoice share a recipient key; and (4) observers lacking Z and B cannot link these addresses to identities or to one another.

Funding and change (model-level). Let U be Alice’s snapshot of available unspent outputs at construction time. A reservation mapping R assigns to each index i a finite, exclusive input set Sᵢ ⊆ U with pairwise disjointness Sᵢ ∩ Sⱼ = ∅ for all i ≠ j. Each note transaction Tᵢ is funded only by Sᵢ. Where change is required, it is paid to a per-note sender address Addrᴬ,ᵢ derived deterministically from (Z, Hᴵ) and the sender anchor A. Change from one note never overlaps with change from any other note in the same invoice and is never selected to fund a different note of that invoice; intra-invoice reuse is prohibited by construction.

Broadcast and finality. Because every Tᵢ is fully formed and independent, either party may broadcast any subset in any order or schedule (all-at-once, paced, or opportunistic). Settlement finality for a note is established when its transaction to Addrᴮ,ᵢ reaches the payee’s required confirmation depth d. Duplicate submission is benign. Conflicts cannot arise within the invoice because input sets are disjoint.

Determinism and auditability. Determinism is a first-class requirement: given the same policy, invoice fingerprint, identities, and input snapshot U, both parties compute the same (N, a), the same recipient and change address sets (Addrᴮ,ᵢ, Addrᴬ,ᵢ), and the same per-note labels. A complete audit is reconstructible from persisted canonical JSON logs and the chain: the invoice and policy, Hᴵ, the mapping i ↦ txidᵢ, and an optional Merkle root over note receipts. No external oracle is required to re-derive or verify the set.

Definitions (normative)

D1 — Policy. A signed statement by the payee declaring \[vₘᵢₙ, vₘₐₓ], any per-address cap (≤ vₘₐₓ), a fee-rate floor, and an expiry; hashed and referenced by the invoice.

D2 — Invoice. A signed statement by the payer declaring T, unit, terms, invoice number, and the hash of the accepted policy; its canonical hash Hᴵ scopes all derivations.

D3 — Note. A single standard on-chain transaction Tᵢ that pays aᵢ to Addrᴮ,ᵢ and, if necessary, returns change to Addrᴬ,ᵢ; valid, complete, and broadcastable in isolation.

D4 — Reservation. An exclusive assignment R(i) = Sᵢ of inputs to index i with Sᵢ ∩ Sⱼ = ∅ for i ≠ j.

D5 — Either-side broadcast. The right of both principals to announce any subset of {Tᵢ}; correctness and finality depend only on confirmation depth.

Invariants (safety and liveness)

I1 — Identity/settlement separation: identity keys authenticate and scope derivations; anchors alone appear in locking scripts. I2 — Per-invoice scoping: all per-note derivations and labels are functions of (Z, Hᴵ); nothing is reused across invoices. I3 — Note independence: each Tᵢ is valid without reference to any Tⱼ; no chained dependence within the invoice. I4 — Disjoint inputs: Sᵢ ∩ Sⱼ = ∅ for all i ≠ j; input reuse within an invoice is impossible by construction. I5 — Determinism: for fixed inputs (policy, invoice, Kₐ, Kᵦ, U) both parties derive the same (N, a), address sets, and labels. I6 — Non-overlapping change: for any i ≠ j, change outputs of Tᵢ and Tⱼ pay to distinct Addrᴬ,ᵢ and Addrᴬ,ⱼ and are never selected to fund another note of the same invoice. I7 — Finality by confirmation: a note is settled when its transaction has ≥ d confirmations to Addrᴮ,ᵢ; other notes are unaffected. I8 — Multiplicity: the cardinality of the note set satisfies |{Tᵢ}| = N ≥ 2; aggregation into a single multi-output transaction is out of model.

2. Primitives, notation, and encodings

Keys and curve (secp256k1). Private keys are scalars k in the range 1 ≤ k ≤ n−1 over the prime field 𝔽ₚ, with p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F (decimal p = 2²⁵⁶ − 2³² − 977). The base point (generator) G has order n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. Public keys are elliptic-curve points P = k·G on the curve y² = x³ + 7 (mod p). Private scalars are represented as fixed-length 32-byte big-endian values when serialised; public keys are represented in compressed SEC1 form (see serP).

Generator G. G is the unique base point defined by secp256k1 with affine coordinates (x\_G, y\_G) on 𝔽ₚ. All public keys and derivations are computed by scalar multiplication on this generator, using constant-time algorithms. No alternative generator is permitted; all parties MUST compute on the canonical G to guarantee interoperation and reproducibility.

Group order n. The order n is the prime 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141. All scalar arithmetic (addition, subtraction, multiplication) is carried out modulo n. Any derived scalar equal to 0 is invalid and MUST be skipped deterministically (e.g., by advancing the index) to maintain a one-to-one mapping between indices and usable keys.

ECDH shared element Z. ECDH(k, P′) is defined as the 32-byte big-endian x-coordinate of the point k·P′, where k is the caller’s private scalar and P′ is the counterparty’s public point. The raw ECDH output used in this specification is Z, a 32-byte array (left-padded with zeros if necessary). Only the x-coordinate is used; no further key-derivation primitive is introduced in this section.

Hash functions H and H160. H(x) denotes SHA-256 over the exact byte sequence x, returning 32 bytes. H160(x) denotes RIPEMD-160(SHA-256(x)), returning 20 bytes. All inputs to H and H160 are byte strings formed by the concatenation conventions defined below; strings such as literal labels (“recv”, “snd”, “split”) are 7-bit ASCII bytes in the concatenation, not hex text.

Generator-based serialisation serP(P). serP(P) denotes the compressed SEC1 encoding of the public point P: a single prefix byte 0x02 if y(P) is even or 0x03 if y(P) is odd, followed by the 32-byte big-endian x-coordinate. This yields 33 bytes. Uncompressed encodings (0x04 + x + y) are not used in this specification.

Base58Check(version ∥ payload ∥ checksum). Addresses are Base58Check encodings of a 1-byte version, a payload, and a 4-byte checksum. For pay-to-public-key-hash (P2PKH), version = 0x00, payload = H160(serP(P)), checksum = first four bytes of SHA-256(SHA-256(version ∥ payload)). The encoded address is the Base58 string of version ∥ payload ∥ checksum with no whitespace, no separators, and no leading “0x”.

Concatenation operator “∥”. The operator “∥” denotes byte-level concatenation: if x and y are byte strings, then x ∥ y is the byte string consisting of x immediately followed by y. When concatenating integers (other than LE32 below) they MUST first be encoded as fixed-length big-endian byte strings of their canonical size (e.g., 32 bytes for secp256k1 scalars). When concatenating literal labels, the labels are included as their raw ASCII bytes. No implicit conversions are permitted.

Little-endian index LE32(i). LE32(i) is the 4-byte little-endian encoding of the non-negative integer i modulo 2³². If i ≥ 2³², then LE32(i) = LE32(i mod 2³²). The index domain for this specification is 0 ≤ i ≤ 2³²−1; indices outside this range are invalid. LE32 is used only where explicitly stated; all other integers are big-endian.

Invoice fingerprint H\_I. The invoice fingerprint H\_I is a 32-byte value defined as H(canonical\_json(invoice)). The function canonical\_json(·) produces an unambiguous UTF-8 byte sequence for the invoice object according to the encoding rules below (key ordering, whitespace, numeric form, and normalisation are fixed). Any party recomputing H\_I over the same invoice MUST obtain the same 32-byte value.

Note identifier NoteID. For a given invoice fingerprint H\_I and note index i, the note identifier is NoteID = H(H\_I ∥ LE32(i)), a 32-byte value. NoteID uniquely labels a note within an invoice scope; it MUST be used in off-chain logs, reservation locks, and receipts to reference the note without revealing addresses or amounts. For fixed H\_I, distinct indices yield distinct NoteIDs up to the collision resistance of SHA-256.

Encoding rules for canonical JSON (normative).

Character encoding. All JSON text is encoded as UTF-8. No byte order mark (BOM) is permitted.

Unicode normalisation. All JSON string values MUST be normalised to NFC prior to byte-level serialisation. Field names are ASCII and need no normalisation.

Object key order. Within every JSON object, keys MUST be sorted in strict lexicographic order by Unicode code point of the key strings (e.g., “addr” < “amount” < “invoice\_hash” < “txid”). No deviations per locale are allowed.

Whitespace. No insignificant whitespace is permitted in canonical JSON. Specifically: no spaces or tabs before or after “:”; no spaces after “,”; no trailing commas; no leading or trailing spaces around values. Arrays and objects are compact (e.g., {"a":1,"b":2}).

Numbers. Integers are encoded in base-10 without leading zeros (except zero itself, which is “0”). No “+” sign. No fractional or scientific notation unless the field is explicitly defined as a float; in that case, use the shortest round-trip decimal form that parses identically (IEEE-754 round-trip), with a dot as decimal separator.

Booleans and null. Encode as the lowercase literals “true”, “false”, and “null”.

Byte strings. Fields that carry bytes (keys, hashes, signatures) are encoded as lowercase hexadecimal strings without “0x” prefix, unless a field is explicitly defined to contain Base58 (addresses) or base64url. For public keys, use hex of serP(P). For hashes (H, H160), use full-length lowercase hex.

Time. Timestamp fields use ISO-8601 basic or extended form with UTC “Z” (e.g., “2025-08-26T00:00:00Z”). Offsets other than “Z” are not permitted in canonical form.

Field presence. All required fields MUST appear exactly once. Optional fields MUST be omitted (not null) when absent.

Determinism. canonical\_json(x) is the byte sequence obtained after applying all the above rules; H\_I is computed over those exact bytes. Any semantically equivalent but differently formatted JSON MUST NOT be used for hashing.

Unambiguous definitions (summary).

H\_I := H(canonical\_json(invoice)) ∈ {0,1}²⁵⁶. This binds all derivations to a single, byte-exact invoice representation. Any alteration of any invoice field (including reordering or whitespace) changes H\_I.

NoteID := H(H\_I ∥ LE32(i)) ∈ {0,1}²⁵⁶. This labels note i within the scope of H\_I. NoteIDs are independent of addresses, amounts, and inputs; they exist to coordinate construction, logging, and receipts without exposing settlement details.

Implementation notes (conformance).

All scalar reductions are mod n; all field reductions are mod p. Any derived scalar equal to 0 MUST trigger deterministic skip logic to preserve a total, collision-free mapping from indices to usable keys.

All cryptographic hashes operate on the exact concatenated byte strings as defined; mixing hex text with raw bytes is an error. Literal labels used for domain separation are 7-bit ASCII and MUST be included as their byte values.

Base58Check outputs are case-sensitive and MUST match the standard Bitcoin alphabet; no whitespace or line breaks are permitted in encoded addresses.

When serialising or parsing serP(P), only 33-byte compressed encodings (0x02/0x03 + x) are valid in this specification; 65-byte uncompressed form MUST be rejected.

3.. Identities, policy, invoice, and scope

3.1 Roles and key material Alice (payer) holds a long-lived identity pair Kₐ = (kₐ, Pₐ) on secp256k1. Bob (payee) holds a long-lived identity pair Kᵦ = (kᵦ, Pᵦ) and a distinct settlement anchor b/B with B = b·G. Identity keys authenticate and sign off-chain artefacts; they do not appear in locking scripts. The anchor B is the sole on-chain base from which Bob’s per-note recipient keys are derived. Identity and anchor domains are strictly separated: kₐ, kᵦ are never used to authorise on-chain spends; b is never used to sign off-chain identity artefacts.

3.2 Shared secret and invoice scope For each invoice, both parties compute a shared element Z := ECDH(kₐ, Pᵦ) = ECDH(kᵦ, Pₐ) (32-byte x-coordinate, big-endian). Define the invoice fingerprint Hᴵ := H(canonical\_json(invoice)) (SHA-256 over the exact UTF-8 bytes). All derivations and labels for this invoice are functions of the pair {Z, Hᴵ}. This scoping is normative: any key, address, amount split, label, reservation lock, or receipt root that does not include {Z, Hᴵ} in its preimage is invalid for this specification. Cross-invoice reuse is forbidden by construction because Hᴵ is invoice-unique and Z is recomputed per counterparty pair.

3.3 Policy (payee → payer): JSON schema and signature Bob issues a signed policy describing bounds and operational parameters that the payer must satisfy when constructing notes. The policy is a single JSON object encoded canonically (see §2), then signed by Bob’s identity key kᵦ.

Canonical key order and field types (normative):

1. "pk\_anchor": string — hex(serP(B)), 66 chars (0x02/0x03 + 64 hex).
2. "vmin": integer — minimum per-note amount (smallest unit), vmin > 0.
3. "vmax": integer — maximum per-note amount (smallest unit), vmax ≥ vmin.
4. "per\_address\_cap": integer — cap per derived recipient address; MUST satisfy vmin ≤ per\_address\_cap ≤ vmax.
5. "feerate\_floor": integer — minimum fee-rate in units-per-byte (smallest unit per virtual byte), feerate\_floor ≥ 1.
6. "expiry": string — ISO-8601 UTC, e.g., "2025-08-26T00:00:00Z".
7. "sig\_key": string — hex(serP(Pᵦ)), Bob’s identity public key in compressed SEC1 hex.
8. "sig\_alg": string — "secp256k1-sha256".
9. "sig": string — hex(ECDSAₖᵦ(SHA-256(canonical\_json(policy\_without\_sig\_fields)))).

Constraints and verification (normative): • pk\_anchor MUST equal the compressed SEC1 encoding of B; the private b MUST be distinct from kᵦ. • vmin MUST be ≥ the current dust threshold; vmax MUST be ≥ vmin; per\_address\_cap MUST be within \[vmin, vmax]. • feerate\_floor MUST be a positive integer measured per byte; both parties compute size estimates identically. • expiry MUST be strictly in the future at the time of acceptance. • sig MUST verify under sig\_key over the canonical bytes of the policy with "sig", "sig\_key", "sig\_alg" omitted from the preimage. • H\_policy := H(canonical\_json(policy)) is the stable identifier referenced by the invoice.

Canonical skeleton (bytes hashed exactly as shown, no whitespace other than mandated): {"pk\_anchor":"…","vmin":…,"vmax":…,"per\_address\_cap":…,"feerate\_floor":…,"expiry":"…","sig\_key":"…","sig\_alg":"secp256k1-sha256","sig":"…"}

3.4 Invoice (payer → payee): JSON schema and signature Alice issues a signed invoice binding the total to the accepted policy and establishing the scope Hᴵ. The invoice is a single JSON object encoded canonically and signed by Alice’s identity key kₐ.

Canonical key order and field types (normative):

1. "invoice\_number": string — UTF-8 identifier under the payer’s namespace.
2. "terms": string — UTF-8 human-readable terms or reference thereto.
3. "unit": string — unit of account label (e.g., "sat", "USD" when quoting; settlement remains on-chain).
4. "total": integer — total amount in the smallest settlement unit.
5. "policy\_hash": string — hex(H\_policy) computed from the accepted policy in §3.3.
6. "expiry": string — ISO-8601 UTC, optional but recommended; if present, MUST be in the future.
7. "sig\_key": string — hex(serP(Pₐ)), Alice’s identity public key in compressed SEC1 hex.
8. "sig\_alg": string — "secp256k1-sha256".
9. "sig": string — hex(ECDSAₖₐ(SHA-256(canonical\_json(invoice\_without\_sig\_fields)))).

Constraints and verification (normative): • policy\_hash MUST equal H\_policy of the policy accepted for this invoice. • total MUST satisfy feasibility with the accepted bounds: ⌈total ÷ vmax⌉ ≤ N ≤ ⌊total ÷ vmin⌋ for some integer N ≥ 2 (checked later during split). • sig MUST verify under sig\_key over the canonical bytes of the invoice with "sig", "sig\_key", "sig\_alg" omitted from the preimage. • Hᴵ MUST be computed as H(canonical\_json(invoice)) after the signature is attached; the same Hᴵ MUST be used in all subsequent derivations for this invoice.

Canonical skeleton: {"invoice\_number":"…","terms":"…","unit":"…","total":…,"policy\_hash":"…","expiry":"…","sig\_key":"…","sig\_alg":"secp256k1-sha256","sig":"…"}

3.5 Authenticity and scoping procedure (end-to-end)

1. Bob constructs the policy object with fields 1–6, sets sig\_key = hex(serP(Pᵦ)), sig\_alg = "secp256k1-sha256", signs the canonical bytes of the object excluding the signature triplet, and attaches sig. He sends the exact UTF-8 bytes.
2. Alice verifies pk\_anchor structure, bounds, feerate\_floor, expiry, and the signature under Pᵦ; she computes H\_policy = H(canonical\_json(policy)).
3. Alice constructs the invoice object with fields 1–6, sets sig\_key = hex(serP(Pₐ)), sig\_alg = "secp256k1-sha256", signs the canonical bytes excluding the signature triplet, attaches sig, and sends the exact UTF-8 bytes.
4. Bob verifies policy\_hash matches his H\_policy, checks total and (optional) expiry, verifies Alice’s signature under Pₐ, and computes Hᴵ = H(canonical\_json(invoice)).
5. Both compute Z := ECDH(kₐ, Pᵦ) = ECDH(kᵦ, Pₐ). The tuple {Z, Hᴵ} is recorded as the sole cryptographic scope for this invoice. Any per-note recipient key, sender change key, amount split, label, reservation lock, or receipt root that does not include {Z, Hᴵ} in its preimage MUST be rejected.

3.6 Security properties (normative) • Domain separation by {Z, Hᴵ} prevents cross-invoice linkage and replay: the same index i under a different invoice produces unrelated recipient and change keys. • Identity/anchor separation prevents misuse of long-term identity material on-chain and constrains blast radius of compromise: kₐ or kᵦ compromise does not expose b; b compromise affects only settlement keys, not identity assertions. • Signatures bind human-readable terms to cryptographic scope: any alteration of policy or invoice (including key order or whitespace) changes the hash and invalidates the signature.

3.7 Rejection conditions (must fail) • Missing or malformed "pk\_anchor", "sig\_key", or signatures in either artefact. • vmin ≤ 0, vmax < vmin, per\_address\_cap outside \[vmin, vmax], feerate\_floor ≤ 0, or expired artefacts. • policy\_hash mismatch between invoice and policy. • Failure to compute Z (invalid public keys). • Any attempt to reuse an existing Hᴵ for a different set of invoice fields.

This section defines the precise identities, artefacts, and scoping required so that the remainder of the construction (per-note derivations, bounded splitting, disjoint funding, and receipts) operates deterministically and remains auditable without revealing identity keys on-chain.

4. Deterministic per-note recipient keys (sender can address; recipient alone can spend)

4.1 Inputs and domain separation Inputs: the recipient anchor B = b·G (public), the per-invoice shared element Z (32 bytes), and the invoice fingerprint Hᴵ (32 bytes). Domain separation: the literal ASCII label “recv” is included in the preimage; indices use LE32(i). All byte concatenations are with the operator “∥”. Identity keys never appear on-chain.

4.2 Scalar derivation per note For each note index i ≥ 0 derive a scalar tᵢ as a function of {Z, Hᴵ, i}: tᵢ := int( SHA-256( Z ∥ Hᴵ ∥ "recv" ∥ LE32(i) ) ) mod n. Reject-zero rule: if tᵢ = 0, re-derive with a counter appended until non-zero, leaving the index stable: tᵢ := int( SHA-256( Z ∥ Hᴵ ∥ "recv" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n, where ctr = 1,2,… until tᵢ ≠ 0. This “counter bump” preserves a one-to-one mapping from index i to a usable scalar without shifting indices and without introducing a new primitive.

4.3 Recipient public key and address (sender view) The sender computes the per-note recipient public key by a single public-key tweak anchored at B: Pᴮ,ᵢ := B + tᵢ·G. Encode the recipient address as a standard P2PKH address: Addrᴮ,ᵢ := Base58Check( 0x00 ∥ H160( serP( Pᴮ,ᵢ ) ) ). Properties: (a) Pᴮ,ᵢ ≠ B because tᵢ ≠ 0; (b) for fixed B, different i give independent points with overwhelming probability; (c) the sender never learns any private scalar corresponding to Pᴮ,ᵢ.

4.4 Recipient private key (recipient view) Only the recipient, who knows b, computes the spending scalar: kᴮ,ᵢ := ( b + tᵢ ) mod n, with corresponding public key Pᴮ,ᵢ = kᴮ,ᵢ·G by linearity. The recipient uses kᴮ,ᵢ to spend outputs paid to Addrᴮ,ᵢ. The base scalar b is never revealed and never used to sign off-chain artefacts.

4.5 Collision resistance and non-reuse Per-invoice scope: because tᵢ is a function of (Z, Hᴵ, "recv", i), two different invoices (different Hᴵ and/or Z) produce unrelated tᵢ values even at the same index. Per-note uniqueness: for a fixed invoice and anchor B, equality Pᴮ,ᵢ = Pᴮ,ⱼ with i ≠ j would require tᵢ ≡ tⱼ (mod n), which has negligible probability under SHA-256. Cross-role separation: the label “recv” fixes the derivation to recipient keys and prevents accidental overlap with other derivation namespaces (e.g., sender change which uses a different label).

4.6 One-wayness (proof sketch) Sender cannot recover kᴮ,ᵢ. The sender knows tᵢ and the public keys B and Pᴮ,ᵢ = B + tᵢ·G. Suppose the sender could compute kᴮ,ᵢ from these. Then b ≡ kᴮ,ᵢ − tᵢ (mod n) would be recoverable, yielding the discrete logarithm of B to the base G. This contradicts the hardness of the elliptic-curve discrete logarithm problem on secp256k1. Therefore, learning kᴮ,ᵢ without b is infeasible. Outsiders cannot link Pᴮ,ᵢ across notes or invoices. An observer sees addresses derived from Pᴮ,ᵢ but lacks Z and typically lacks Hᴵ. Without Z the observer cannot reproduce tᵢ, hence cannot predict or recognise the set {Pᴮ,ᵢ}. Because Pᴮ,ᵢ = B + tᵢ·G with tᵢ pseudorandom in \[1, n−1], the distribution of Pᴮ,ᵢ is computationally indistinguishable from uniform over the subgroup generated by G given B, and linkage reduces to breaking the preimage resistance of SHA-256 or the ECDLP.

4.7 Side-channel and constant-time requirements Scalar and point operations MUST be executed in constant time with respect to secret values (b and kᴮ,ᵢ). Implementations MUST avoid secret-dependent branches and table lookups during scalar multiplication and addition. The reduction “mod n” MUST be constant time. The counter-bump loop executes at most a negligible expected number of iterations (usually zero); its decision is data-independent with respect to b and only depends on the public hash tᵢ = 0 test, which is uniform at 1/n.

4.8 Derivation pseudocode (Unicode, canonical, no LaTeX)

Inputs • Z: 32-byte ECDH element (x-coordinate). • Hᴵ: 32-byte invoice fingerprint. • B: recipient anchor public key (compressed or internal point). • i: note index (0 ≤ i ≤ 2³²−1).

Functions • H(x) := SHA-256(x). • H160(x) := RIPEMD-160(SHA-256(x)). • serP(P) := SEC1 compressed encoding of point P (33 bytes). • LE32(u) := 4-byte little-endian of integer u. • Base58Check(v ∥ p) := Base58Check with version v and payload p (checksum = first 4 bytes of double-SHA-256).

Procedure (sender view)

1. tᵢ ← int( H( Z ∥ Hᴵ ∥ "recv" ∥ LE32(i) ) ) mod n.
2. if tᵢ = 0 then ctr ← 1; repeat tᵢ ← int( H( Z ∥ Hᴵ ∥ "recv" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n; ctr ← ctr + 1; until tᵢ ≠ 0.
3. Pᴮ,ᵢ ← point\_add( B, scalar\_mul( tᵢ, G ) ).
4. addrᴮ,ᵢ ← Base58Check( 0x00 ∥ H160( serP( Pᴮ,ᵢ ) ) ).
5. Output (Pᴮ,ᵢ, addrᴮ,ᵢ) for index i.

Procedure (recipient view)

1. Recompute tᵢ from {Z, Hᴵ, i} with the same counter-bump if necessary.
2. kᴮ,ᵢ ← ( b + tᵢ ) mod n.
3. Verify kᴮ,ᵢ·G equals Pᴮ,ᵢ (optional local check).
4. Use kᴮ,ᵢ to spend outputs paying to addrᴮ,ᵢ.

4.9 Conformance and rejection rules • If serP(B) is not a valid compressed SEC1 encoding or not on curve, reject the invoice before derivation. • If any tᵢ = 0 after counter-bump exhaustion (theoretically impossible for practical counters), reject index i and halt with an error state. • Implementations MUST record the counter value used for each i (if any) in off-chain logs to guarantee reproducibility. • Any per-note artefact (address, label, receipt) that cannot be recomputed from {Z, Hᴵ, i, B} MUST be considered invalid for this specification.

5. Deterministic bounded note-splitting (exact sum; indices independent of sizes)

5.1 Inputs and feasibility

Input parameters (all in the smallest settlement unit): total T ≥ 1; bounds \[v\_min, v\_max] with 1 ≤ v\_min ≤ v\_max; per-invoice scope {Z, H\_I}. Define the feasible note-count interval N\_min := ⌈T ÷ v\_max⌉ and N\_max := ⌊T ÷ v\_min⌋. Validity requires N\_min ≤ N\_max (otherwise the invoice is infeasible). A valid split consists of an integer N with N\_min ≤ N ≤ N\_max and an amount vector a\[0…N−1] such that for all i: v\_min ≤ a\[i] ≤ v\_max and Σ a\[i] = T.

5.2 Deterministic seeding

Define a seed S := H( Z ∥ H\_I ∥ "split" ) (32 bytes, SHA-256 of the exact bytes). All randomness below is deterministically derived from S. A counter-based PRNG is used: for j = 0,1,2,… define R\_j := H( S ∥ LE32(j) ) and let u\_j be the 64-bit unsigned integer formed from the first 8 bytes of R\_j (big-endian). The function next\_u64() returns u\_j and increments j. This yields a reproducible, stateless stream for both parties.

5.3 Choosing N (reproducible, interior-biased) If N\_min = N\_max, set N := N\_min. Otherwise set span := N\_max − N\_min, mid := ⌊(N\_min + N\_max)/2⌋, and Δ := ⌊span/4⌋. Draw u := next\_u64(). Map u to a symmetric jitter J in \[−Δ, +Δ] by J := (u mod (2Δ+1)) − Δ. Set N₀ := mid + J, then clamp to the feasible interval: N := min( max(N₀, N\_min), N\_max ). This rule is deterministic, prefers interior counts when unconstrained, and never violates feasibility.

5.4 Range-safe uniform integer draws To draw an integer r uniformly from \[0, R−1] using next\_u64() without modulo bias, use rejection sampling: let M := 2⁶⁴, lim := ⌊M/R⌋·R. Repeatedly draw u := next\_u64() until u < lim, then return r := u mod R. This is used below for bounded choices.

5.5 Prefix-clamped construction (exact sum, bounds preserved) Initialise rem := T. For i from 0 to N−2 do:

1. Compute the feasible interval for a\[i] given the remaining slots: slots := N−1−i low := max( v\_min, rem − v\_max·slots ) high := min( v\_max, rem − v\_min·slots ) (low ≤ high must hold by feasibility; see §5.7.)
2. Draw r ∈ \[0, (high−low)] uniformly using the range-safe method and set a\[i] := low + r.
3. Set rem := rem − a\[i]. After the loop set a\[N−1] := rem. By construction v\_min ≤ a\[N−1] ≤ v\_max (proof in §5.7). This produces v\_min ≤ a\[i] ≤ v\_max for all i and Σ a\[i] = T exactly.

5.6 Index/size de-correlation by permutation To ensure indices are independent of sizes, apply a deterministic Fisher–Yates shuffle to the completed vector a using a seed derived from S but disjoint from the draw sequence above. Define S\_perm := H( S ∥ "permute" ). Instantiate a second counter-based PRNG with S\_perm and perform Fisher–Yates on positions 0…N−1, using range-safe draws for each swap index. The permutation is thus fixed by {Z, H\_I} and independent of the prefix-clamping choices, so no structural correlation between index and size remains.

5.7 Correctness and termination Feasibility of each step. Assume at step i (0 ≤ i ≤ N−2) that rem satisfies v\_min·(N−i) ≤ rem ≤ v\_max·(N−i). Then: • Lower bound low = max( v\_min, rem − v\_max·(N−1−i) ) ensures that after choosing a\[i] ≥ low the remaining rem′ = rem − a\[i] can still be paid using at most (N−1−i) notes each of size ≤ v\_max, because rem′ ≤ rem − (rem − v\_max·(N−1−i)) = v\_max·(N−1−i). • Upper bound high = min( v\_max, rem − v\_min·(N−1−i) ) ensures that after choosing a\[i] ≤ high the remaining rem′ can still be paid using at least (N−1−i) notes each of size ≥ v\_min, because rem′ ≥ rem − (rem − v\_min·(N−1−i)) = v\_min·(N−1−i). Hence low ≤ a\[i] ≤ high implies v\_min·(N−1−i) ≤ rem′ ≤ v\_max·(N−1−i), maintaining the invariant. The base case at i = 0 holds by the choice of N (N\_min ≤ N ≤ N\_max). By induction the invariant holds for all i ≤ N−2. Termination occurs after exactly N steps. At i = N−1 we have rem′ = a\[N−1] and v\_min ≤ a\[N−1] ≤ v\_max by the invariant, and Σ a\[i] = T by construction.

5.8 Determinism and replay Every choice is a pure function of {Z, H\_I}, the accepted bounds, and the deterministic PRNG streams from S and S\_perm. Given identical inputs, independent implementations compute identical N, identical pre-permutation a, and an identical permutation. No per-note amounts need to be exchanged off-chain; both parties recompute the same vector.

5.9 Edge cases and rejection rules • Infeasible invoice: if N\_min > N\_max (e.g., T < v\_min or T > v\_max·N\_max under external constraints), reject the invoice before splitting. • Tight bounds: if v\_min = v\_max then N is forced to N\_min = N\_max and the vector is constant a\[i] = v\_min for all i. • Degenerate last step: if N = 1 the construction degenerates to a\[0] := T, which must equal v\_min = v\_max = T to be feasible; otherwise reject at §5.1. • Deterministic streams: the PRNG counters MUST NOT be shared between “split” and “permute”; S\_perm isolates the shuffle. Counters MUST be reset to zero for each new seed.

5.10 Pseudocode (Unicode; canonical; no bias)

Seed and PRNG seed\_split := H( Z ∥ H\_I ∥ "split" ) seed\_perm := H( seed\_split ∥ "permute" )

function next\_u64(seed, counter): R := H( seed ∥ LE32(counter) ) counter := counter + 1 return (first 8 bytes of R as uint64 big-endian), counter

function draw\_uniform(R, range): # range ≥ 1 M := 2⁶⁴ lim := (M ÷ range) × range loop: (u, ctr) := next\_u64(R.seed, R.ctr) if u < lim: return (u mod range), (R.seed, ctr) else: continue

Choosing N span := N\_max − N\_min if span = 0: N := N\_min else: mid := ⌊(N\_min + N\_max)/2⌋ Δ := ⌊span/4⌋ (r, R\_split) := draw\_uniform( R\_split, 2Δ + 1 ) J := r − Δ N := clamp( mid + J, N\_min, N\_max )

Constructing a rem := T for i in 0 … N−2: slots := N−1−i low := max( v\_min, rem − v\_max × slots ) high := min( v\_max, rem − v\_min × slots ) (r, R\_split) := draw\_uniform( R\_split, high − low + 1 ) a\[i] := low + r rem := rem − a\[i] a\[N−1] := rem

Permutation (Fisher–Yates using seed\_perm) for j in (N−1) down to 1: (r, R\_perm) := draw\_uniform( R\_perm, j+1 ) swap a\[j] ↔ a\[r]

5.11 Rationale for the permutation step Without permutation, prefix-clamping tends to bias early indices toward interior values of \[v\_min, v\_max] when T is near a boundary, creating a weak but systematic index→size correlation. A seeded Fisher–Yates shuffle removes positional information while preserving multiset equality and the exact sum. Using S\_perm derived from S ensures determinism tied to {Z, H\_I} and prevents stream re-use, so the shuffle cannot be predicted or recomputed by third parties lacking the invoice scope.

5.12 Security and leakage considerations The split leaks only that all amounts lie in \[v\_min, v\_max]; no per-note sizes are revealed off-chain because both wallets compute a independently. On-chain, an external observer learns the multiset of paid amounts if all notes are broadcast promptly; timing diversity in broadcast is handled elsewhere. The permutation ensures indices carry no information about sizes in off-chain logs or receipts. Deterministic seeding binds all outcomes to the invoice; cross-invoice linkage by split structure is prevented by H\_I.

6. Disjoint coin selection and strict non-overlap across notes

6.1 Snapshot and reservation model Input pool U is the payer’s spendable UTXOs at invoice start, filtered for script type (payer’s own keys), maturity, and policy (confirmations, timelocks). U is a set of outpoints ⟨txid, vout, value, scriptPubKey, keyref⟩. At construction time the wallet takes a read-only snapshot U₀ and builds a reservation table R mapping each note index i to an exclusive input set Sᵢ ⊂ U₀. Exclusivity is strict: Sᵢ ∩ Sⱼ = ∅ for all i ≠ j. Each outpoint carries a reservation state: free → reserved(i) → committed(i) or free (on cancel). Reservations are keyed by NoteID to ensure stable recovery from logs.

6.2 Deterministic ordering To maximise success and ensure reproducibility, notes are funded in a fixed order: sort indices by descending a\[i]; ties break by ascending i. The UTXO pool is iterated in a fixed order: sort by (value ascending, then txid lexicographic ascending, then vout ascending). No random tie-breakers appear in selection; given U₀ and a\[·], the same R is produced.

6.3 Fee and dust parameters Let feerate\_floor be the minimum units-per-byte. For a candidate with m inputs and n outputs (n ∈ {1,2}), estimate bytes as size ≈ 10 + 148·m + 34·n. Required fee = feerate\_floor × size (integer, round up). Dust threshold δ\_dust is the minimum output value permitted by local policy; any output < δ\_dust is invalid. Change outputs must be either ≥ δ\_dust or omitted (surplus folded into the fee only when explicitly permitted by policy).

6.4 Selection policy (bounded-knapsack with exact-match preference) Goal for note i: choose Sᵢ such that Σ value(Sᵢ) = a\[i] + fee + change, with change ∈ {0} ∪ \[δ\_dust, ∞). Preference order: (1) exact match with zero change; (2) single-input just-over target with valid change; (3) fewest inputs subject to minimal overshoot and valid change. Any candidate that would produce an output < δ\_dust or violate feerate\_floor is rejected.

6.5 Algorithm for building R (normative)

Inputs: U₀ (ordered), vector a\[0…N−1], feerate\_floor, δ\_dust. Outputs: reservation table R or failure.

Pre-pass: remove from U₀ any outpoint < δ\_dust or not controlled by the payer. Initialise R := ∅. Let Used := ∅.

For i in sort\_descending\_by a\[i] then ascending i:

1. Target initialisation target := a\[i] best := ⊥
2. Stage A — exact single-input For each u ∈ U₀ \ Used: fee₁ := feerate\_floor × (10 + 148·1 + 34·1) if value(u) = target + fee₁: best := {u}; goto Commit
3. Stage B — exact few-inputs (bounded subset) Search over combinations of up to K\_max inputs (K\_max default 4) from U₀ \ Used in ascending cardinality. For each candidate set C: m := |C|; fee\_m := feerate\_floor × (10 + 148·m + 34·1) if Σ value(C) = target + fee\_m: best := C; goto Commit (Prune by value sums exceeding target + fee\_m + δ\_dust unless Stage C.)
4. Stage C — single-input near-over For each u ∈ U₀ \ Used: fee₂ := feerate\_floor × (10 + 148·1 + 34·2) change := value(u) − target − fee₂ if change ≥ δ\_dust and change is minimal among examined: best := {u}
5. Stage D — fewest-inputs minimal-overshoot Increase m from 2 to M\_max (M\_max default 6). For each m, run a greedy bounded-knapsack over U₀ \ Used (largest-first or meet-in-the-middle for m≤4) to find C with Σ value(C) ≥ target + fee\_m₂ where fee\_m₂ := feerate\_floor × (10 + 148·m + 34·2). Among feasible C, minimise overshoot := Σ value(C) − (target + fee\_m₂) subject to overshoot = 0 or overshoot ≥ δ\_dust. Choose the C with smallest m then smallest overshoot. Set best := C if found.
6. Commit if best = ⊥: goto FailureForI m := |best| n := 1 if Σ value(best) = target + feerate\_floor×(10 + 148·m + 34·1) else 2 fee := feerate\_floor × (10 + 148·m + 34·n) sum\_in := Σ value(best) if n = 1: change := 0 else: change := sum\_in − target − fee if change < δ\_dust: // attempt to repair by adding one more input once pick the smallest u′ ∈ U₀ \ Used \ best if u′ exists: recompute m, n=2, fee, change; if change ≥ δ\_dust accept; else discard u′ and continue Stage D if no repair possible: goto FailureForI R\[i] := best mark all u ∈ best as reserved(i); Used := Used ∪ best continue with next i

FailureForI: Attempt optional fan-out (once per invoice). If fan-out succeeds and confirms per policy, refresh U₀ := U\_fanout ⊎ (U₀ \ Used) and restart from i = 0. If fan-out is disabled or fails, abort with “insufficient granularity”.

Return R upon success for all i.

6.6 Definition of “locked” UTXO A locked UTXO is an outpoint in state reserved(i) with metadata {NoteID, timestamp, size\_estimate, fee\_rate\_used}. While reserved, it MUST NOT be considered by selection for any j ≠ i within the same invoice. A reserved UTXO transitions to committed(i) once Tᵢ is fully signed. If a note is cancelled before broadcast, all Sᵢ return to free; if reissued, Sᵢ remain reserved until the new Tᵢ is committed.

6.7 Failure modes and outcomes • Insufficient value: Σ value(U₀) < Σ a\[i] + fees\_min → abort invoice (fan-out cannot create value). • Insufficient granularity: value(U₀) is concentrated in large outputs so that every candidate either violates δ\_dust for change or exceeds K\_max/M\_max → attempt fan-out. • Conflicting external spend: if a reserved outpoint is spent externally (wallet mutation), invalidate R and rebuild from a fresh U snapshot; log the conflict under the offending outpoint. • Policy violation: any candidate producing outputs below δ\_dust or underpaying fee is rejected; if no candidate remains, treat as insufficient granularity.

6.8 Optional preparatory fan-out (payer-only, outside the note set) Purpose: reshape coarse inputs into a set of smaller payer-owned outputs suitable for the target a\[·]. Rules: • Destination: strictly to the payer’s own addresses derived from the sender anchor under a distinct namespace label “fund” (not “snd”), e.g., Addr\_fund,j; never to the payee; never counted as a note. • Granularity: choose output sizes to cover the histogram of a\[·] and expected change values, typically near v\_max and mid-range values; all fan-out outputs ≥ max(δ\_dust, v\_min). • Count: minimise number of fan-out outputs while ensuring coverage; a practical target is ⌈Σ a\[i] ÷ v\_max⌉ plus a small buffer. • Fee and confirmation: construct with feerate ≥ feerate\_floor and, by default, require at least one confirmation before using the resulting outputs in R (deterministic policy); if unconfirmed chaining is allowed by local policy, mark chained notes as risk-accepted in logs. • Scope: fan-out transactions are labelled funding-only with a distinct manifest and are excluded from receipt accounting. • Idempotence: perform at most one fan-out attempt per invoice; repeated fan-outs can re-fragment and harm determinism.

6.9 Determinism and auditability Given U₀, a\[·], feerate\_floor, δ\_dust, K\_max, M\_max, and the fixed iteration orders, the algorithm produces a unique R. The wallet MUST persist U₀ snapshot metadata, the reservation table R, size/fee calculations, and any fan-out manifest to allow exact recomputation. Change outputs created by notes MUST NOT be admitted into U₀ for funding other notes within the same invoice; they become eligible only after the invoice is closed (completed or aborted).

6.10 Pseudocode (Unicode; canonical)

function build\_reservations(U₀, a\[0…N−1], feerate\_floor, δ\_dust): order\_notes := sort\_desc( (a\[i], −i) ) # by amount desc, then i asc order\_utxo := sort\_asc( (value, txid, vout) ) # deterministic pool order Used := ∅; R := {} fanout\_done := false repeat: for i in order\_notes: best := select\_inputs\_disjoint(order\_utxo \ Used, a\[i], feerate\_floor, δ\_dust) if best = ⊥: if not fanout\_done and policy\_allows\_fanout(): F := build\_fanout(order\_utxo \ Used, histogram(a), feerate\_floor, δ\_dust) if F.success and F.confirmed: U₀ := (U₀ \ F.inputs) ⊎ F.outputs Used := ∅; R := {}; fanout\_done := true; goto repeat return ⊥ R\[i] := best; Used := Used ∪ best return R

6.11 Guarantees • Strict non-overlap: by construction Sᵢ ∩ Sⱼ = ∅ for i ≠ j, and change from Tᵢ is excluded from funding Tⱼ. • Independence: each Tᵢ can be signed and broadcast without reference to any Tⱼ. • Compliance: every output is ≥ δ\_dust; every transaction meets or exceeds feerate\_floor. • Reproducibility: with the persisted U₀ and logs, the same R is reconstructed exactly.

7. Per-note change addresses and change calculation (no overlap; deterministic; auditable)

7.1 Scope and requirements Change outputs are per-note, invoice-scoped artefacts. For each index i, the payer derives exactly one sender-side change address Addrᴬ,ᵢ deterministically from {Z, Hᴵ} and a sender anchor A = a·G. Change produced by note i MUST pay to Addrᴬ,ᵢ. No change output from note i is eligible to fund any other note j ≠ i within the same invoice. Reissues before broadcast preserve the index i and Addrᴮ,ᵢ; Addrᴬ,ᵢ remains stable for i.

7.2 Sender change derivation (deterministic, per note) Inputs: sender anchor A = a·G (public), per-invoice scope {Z, Hᴵ}. Domain separation uses the ASCII label “snd”.

Define the per-note tweak scalar: sᵢ := int( SHA-256( Z ∥ Hᴵ ∥ "snd" ∥ LE32(i) ) ) mod n.

Reject-zero rule: if sᵢ = 0, deterministically bump a counter until non-zero: sᵢ := int( SHA-256( Z ∥ Hᴵ ∥ "snd" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n, ctr = 1,2,...

Define the per-note sender public key and address: Pᴬ,ᵢ := A + sᵢ·G. Addrᴬ,ᵢ := Base58Check( 0x00 ∥ H160( serP(Pᴬ,ᵢ) ) ).

Uniqueness and separation: for fixed A and invoice scope, indices map to distinct Pᴬ,ᵢ with overwhelming probability; “snd” prevents namespace collision with recipient derivations (“recv”).

7.3 Fee and change arithmetic (standard P2PKH) Parameters: • m = number of inputs for note i. • n ∈ {1, 2} = number of outputs (1 = pay-only, 2 = pay + change). • size\_bytes ≈ 10 + 148·m + 34·n (P2PKH approximation). • fee\_rate\_floor = minimum units-per-byte (smallest unit per byte). • fee := ceil( fee\_rate\_floor × size\_bytes ). • δ\_dust = dust threshold (policy parameter, smallest unit). • sum\_inputs := Σ value(inputⱼ) over the reserved set Sᵢ. • target := a\[i] (the note’s recipient amount).

Deterministic evaluation (no circularity): Step A — assume n = 1 (no change). Compute fee₁ := ceil( fee\_rate\_floor × (10 + 148·m + 34·1) ). • If sum\_inputs = target + fee₁ → construct a 1-output transaction: pay target to Addrᴮ,ᵢ; no change. • Else proceed to Step B.

Step B — assume n = 2 (pay + change). Compute fee₂ := ceil( fee\_rate\_floor × (10 + 148·m + 34·2) ). • change := sum\_inputs − target − fee₂. • If change ≥ δ\_dust → construct a 2-output transaction: pay target to Addrᴮ,ᵢ and change to Addrᴬ,ᵢ. • If change ∈ \[1, δ\_dust−1] → invalid (dust). Either add one more input (recompute m, fee₂, change) or reselect Sᵢ per §6; if no valid candidate exists, fail funding for i. • If change ≤ 0 → underfunded; add inputs or reselect Sᵢ.

Rounding rule: fees are rounded up to the nearest integer unit to avoid underpayment. All implementations MUST use identical size estimates and rounding to preserve determinism.

7.4 No-overlap and pool eligibility No intra-invoice reuse: change created by Tᵢ MUST NOT be inserted into the funding pool U for any j ≠ i while the invoice is open (building, broadcasting, or awaiting confirmations). Enforcement is by NoteID tagging: every change UTXO carries {invoice\_hash = Hᴵ, index = i} and a state “locked(change,i)” until closure.

7.5 Reissue semantics (pre-broadcast) If a different fee rate is desired before broadcast, reissue note i by constructing a new transaction with possibly different inputs but the same index i, the same recipient address Addrᴮ,ᵢ, and the same sender change address Addrᴬ,ᵢ. Update fee, change, and txid in logs; mark the prior, unbroadcast serialisation as superseded. Addrᴬ,ᵢ stability guarantees that all change for i remains isolated to that index across reissues.

7.6 Security and leakage Identity keys are not used on-chain. Sender change keys are invoice-scoped and per-note; without {Z, Hᴵ, A} an observer cannot regenerate Addrᴬ,ᵢ. Because change pays to unique addresses per index, clustering by shared change across notes of the same invoice is prevented by construction.

7.7 Conformance and rejection rules • Reject if serP(A) is invalid or not on curve. • Reject if sᵢ = 0 after counter-bump exhaustion (theoretical). • Reject any candidate where change ∈ (0, δ\_dust). • Reject any construction whose fee < fee\_rate\_floor × estimated size (post-rounding). • Persist {i, sum\_inputs, m, n, size\_bytes, fee, change, Addrᴮ,ᵢ, Addrᴬ,ᵢ, txid} for audit.

7.8 Pseudocode (Unicode; canonical)

Inputs • a\[i], Sᵢ (reserved inputs for i), fee\_rate\_floor, δ\_dust, A, Z, Hᴵ. • serP, H, H160, Base58Check as defined earlier.

Derive change address sᵢ := int( H( Z ∥ Hᴵ ∥ "snd" ∥ LE32(i) ) ) mod n if sᵢ = 0: ctr := 1 repeat: sᵢ := int( H( Z ∥ Hᴵ ∥ "snd" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n ctr := ctr + 1 until sᵢ ≠ 0 Pᴬ,ᵢ := point\_add( A, scalar\_mul( sᵢ, G ) ) Addrᴬ,ᵢ := Base58Check( 0x00 ∥ H160( serP(Pᴬ,ᵢ) ) )

Compute fee and change m := |Sᵢ| size₁ := 10 + 148×m + 34×1 fee₁ := ceil( fee\_rate\_floor × size₁ ) if Σ value(Sᵢ) = a\[i] + fee₁: outputs := \[ (Addrᴮ,ᵢ, a\[i]) ] # n = 1, no change else: size₂ := 10 + 148×m + 34×2 fee₂ := ceil( fee\_rate\_floor × size₂ ) change := Σ value(Sᵢ) − a\[i] − fee₂ if change ≥ δ\_dust: outputs := \[ (Addrᴮ,ᵢ, a\[i]), (Addrᴬ,ᵢ, change) ] # n = 2 else if 0 < change < δ\_dust: fail "dust-change" # add input or reselect Sᵢ else: fail "underfunded" # add input or reselect Sᵢ

Construct transaction Tᵢ := make\_tx( inputs = Sᵢ, outputs = outputs ) sign\_all\_inputs(Tᵢ) record\_log( i, Hᴵ, Sᵢ, m, outputs, fee, change, txid(Tᵢ), Addrᴬ,ᵢ, Addrᴮ,ᵢ )

Policy enforcement mark\_change\_utxo(Tᵢ, i, Hᴵ, state="locked(change,i)") forbid\_selection\_of\_locked\_change\_until\_invoice\_closed(Hᴵ)

7.9 Bytesize estimator and parameters (normative defaults) • Input (P2PKH): 148 bytes. Output (P2PKH): 34 bytes. Overhead: 10 bytes. • Implementations MAY use precise varint-aware sizing; if so, the same sizing method MUST be used deterministically by both parties. • δ\_dust is a fixed policy parameter for the implementation; it MUST be agreed implicitly by equal software or explicitly encoded in policy to avoid disagreements.

8. Transaction formation per note (independent, standard, fully signed)

8.1 Build order (normative)

For each index i:

1. Inputs (funding). Take the reserved input set Sᵢ from the reservation table (pairwise disjoint across i). Let m = |Sᵢ| and sum\_inputs = Σ value(Sᵢ).
2. Outputs (payee and optional change). • Primary output: (Addrᴮ,ᵢ, a\[i]). • Change output (if required by §7): (Addrᴬ,ᵢ, change\[i]). Output ordering is deterministic: primary first, change second if present.
3. Transaction fields. • nVersion = 1 (32-bit little-endian). • vin count = m (varint). • For each input j in Sᵢ in deterministic order (value ascending, then txid lexicographic ascending, then vout ascending): – prevout.txid (32-byte little-endian), prevout.vout (4-byte little-endian). – scriptSig = empty during preimage construction. – nSequence = 0xFFFFFFFF unless explicitly negotiated otherwise. • vout count = 1 or 2 (varint). • For each output in the order specified: – value (8-byte little-endian). – scriptPubKey = standard P2PKH: OP\_DUP OP\_HASH160 <20-byte H160(serP(P))> OP\_EQUALVERIFY OP\_CHECKSIG. • nLockTime = 0 unless explicitly negotiated otherwise.
4. Fee and dust validation. Verify the size estimate and fee satisfy the fee-rate floor; verify no output < δ\_dust (§7).
5. Signing scope. For each input j = 0…m−1, construct the legacy SIGHASH preimage for SIGHASH\_ALL: – Start from the transaction template with all scriptSig empty. – Replace the scriptSig of input j with the exact previous locking script (the P2PKH scriptPubKey of the UTXO referenced by Sᵢ\[j]). – Append the 4-byte SIGHASH type 0x00000001 (little-endian). – Double-hash with SHA-256 to obtain zⱼ. – Produce a deterministic ECDSA signature σⱼ over zⱼ using the private key that controls Sᵢ\[j], with low-s normalisation; append one-byte hash type 0x01 to σⱼ. – Set scriptSigⱼ := \<PUSH σⱼ> \<PUSH serP(Pⱼ)>, where Pⱼ is the corresponding public key.
6. Final serialisation and txid. Serialise the fully signed transaction using Bitcoin legacy (non-SegWit) encoding. Define txidᵢ := SHA-256(SHA-256(serialised\_bytes)), expressed as 32-byte hash (displayed big-endian; stored and relayed as little-endian in prevouts). The note transaction Tᵢ is now complete and valid in isolation.
7. Deterministic labelling. Compute NoteIDᵢ := H(Hᴵ ∥ LE32(i)). Associate NoteIDᵢ with Tᵢ in logs and manifests.

8.2 Independence and validity (normative)

Tᵢ references only inputs from Sᵢ and pays only to Addrᴮ,ᵢ and (if present) Addrᴬ,ᵢ. • Tᵢ contains no pointers to any Tⱼ, j ≠ i; no input or change overlap is permitted within the invoice. • Any subset of {Tᵢ} may be broadcast in any order without invalidating or starving the remainder.

8.3 NoteMeta schema (canonical JSON; log-only)

Minimal, required fields:

{"i": , "note\_id": "\<hex 32-byte NoteIDᵢ>", "invoice\_hash": "\<hex 32-byte Hᴵ>", "addr": "\<base58 Addrᴮ,ᵢ>", "amount": \<int a\[i]>, "txid": "\<hex 32-byte txidᵢ>"}

Recommended extensions for audit (all integers in smallest unit; strings lowercase hex unless noted):

{"size\_bytes": , "fee": , "feerate\_used": , "change\_addr": "\<base58 Addrᴬ,ᵢ or empty>", "change\_amount": \<int or 0>, "inputs": \[ {"txid":"","vout":,"value":,"scriptPubKey":""} ], "outputs": \[ {"addr":"","value":}, {"addr":"","value":}? ], "sig\_alg": "secp256k1-sha256", "created\_at": "", "status": "unsigned|signed|broadcast|confirmed|reissued|cancelled"}

All NoteMeta objects MUST be encoded canonically (UTF-8, sorted keys, no extraneous whitespace) when hashed or signed. The tuple (i, NoteIDᵢ, txidᵢ) is the stable handle for the note within an invoice.

8.4 Deterministic ordering and tie-breaking (normative)

Inputs inside Tᵢ are ordered by (value ascending, txid lexicographic ascending, vout ascending). • Outputs are ordered: primary primary first, change second if present. • scriptSig contains exactly one DER-encoded ECDSA signature with appended 0x01 hash-type byte and one compressed public key; no additional pushes are permitted.

8.5 Conformance and rejection rules

Reject Tᵢ if any output value < 0 or > 2⁶³−1, or if Σ outputs + fee ≠ Σ inputs. • Reject if any output < δ\_dust. • Reject if fee < fee-rate floor × size\_bytes (post-rounding). • Reject if any scriptSig is empty or fails standard verification under SIGHASH\_ALL. • Reject if NoteIDᵢ mismatches {Hᴵ, i}, or if addr ≠ Addrᴮ,ᵢ derived from {Z, Hᴵ, i, B}. • On reissue prior to broadcast, the new transaction replaces txidᵢ in logs for the same i and NoteIDᵢ; Addrᴮ,ᵢ and Addrᴬ,ᵢ remain unchanged.

9. Ordering, pacing, and broadcast authority (either side may settle)

9.1 Authority and symmetry Either party may broadcast any subset of the note transactions at any time. The payer (Alice) and the recipient (Bob) hold identical authority to submit a fully signed note TiTᵢ to the network. Duplicate broadcast of identical bytes is benign: identical transactions share the same txid and are deduplicated by nodes. Settlement for a note is defined solely by confirmation depth dd selected by the recipient in policy; once TiTᵢ achieves ≥ dd confirmations to Addrᴮ,ᵢ, that note is settled irrespective of the status of other notes in the invoice.

9.2 Broadcast strategies (permissible) • All-at-once — submit all TiTᵢ immediately. • Paced — submit TiTᵢ according to a deterministic schedule over a window \[t0,t1]\[t₀, t₁] with minimum spacing. • Grouped bursts — partition notes into batches of size β; submit batches with inter-batch gaps.

A strategy is advisory; either party may accelerate or decelerate within the agreed bounds.

9.3 Policy fields (broadcast) — payee → payer (canonical JSON) Augment the policy (§3.3) with:

"broadcast": { "authority": "either", // fixed literal "strategy\_default": "paced|all\_at\_once|bursts", "min\_spacing\_ms": \<int ≥ 0>, // minimum inter-note gap "max\_spacing\_ms": \<int ≥ min\_spacing>, // upper bound for paced jitter "burst\_size": \<int ≥ 1>, // used when strategy\_default = "bursts" "burst\_gap\_ms": \<int ≥ 0>, // inter-burst gap "window\_start": "|"" ", "window\_end": "|"" ", "rebroadcast\_interval\_s": \<int ≥ 60>, // periodic re-announce until confirmed "hold\_time\_max\_s": \<int ≥ 0>, // max age for unbroadcast notes before action "confirm\_depth": \<int ≥ 0> // d, settlement depth }

Normative constraints: • If "window\_start" and "window\_end" are both present, require window\_start < window\_end. • If "strategy\_default" = "bursts", "burst\_size" ≥ 2; otherwise "burst\_size" is ignored. • "confirm\_depth" defines finality dd for all notes unless overridden in the invoice.

9.4 Invoice overrides — payer → payee (canonical JSON) An invoice MAY propose narrower pacing within policy bounds:

"broadcast\_overrides": { "strategy": "paced|all\_at\_once|bursts", // optional; must be allowed by policy "min\_spacing\_ms": , // ≤ policy.max\_spacing\_ms "max\_spacing\_ms": , // ≥ min\_spacing\_ms and ≤ policy.max\_spacing\_ms "burst\_size": , // if strategy = "bursts" and ≤ policy.burst\_size "burst\_gap\_ms": , // ≤ policy.burst\_gap\_ms or equal "window\_start": "|"" ", "window\_end": "|"" ", "confirm\_depth": // ≥ policy.confirm\_depth }

Validation: every override must satisfy policy bounds; otherwise the invoice is invalid.

9.5 Deterministic pacing schedule (sender/recipient can compute identically) Seed for schedule: S\_pace := H( Z ∥ Hᴵ ∥ "pace" ). Derive a reproducible jitter stream from S\_pace (as in §5.2). Let order of notes for scheduling be the stable index order 0…N−1.

All-at-once: for all i, schedule\_time\[i] := max(now, window\_start). Paced: for i = 0…N−1, draw δᵢ uniformly from \[min\_spacing\_ms, max\_spacing\_ms] via deterministic PRNG; set schedule\_time\[0] := max(now, window\_start); schedule\_time\[i] := schedule\_time\[i−1] + δᵢ ms; if schedule\_time\[i] > window\_end (when set), cap at window\_end. Bursts: partition indices into batches of size β (β = burst\_size). For batch k, set batch\_time\[k] := (k = 0 ? max(now, window\_start) : batch\_time\[k−1] + burst\_gap\_ms). All notes in batch k share batch\_time\[k]; within a batch, apply a tiny deterministic intra-batch offset εᵢ ∈ \[0, min\_spacing\_ms] to break ties.

Either party may use the same schedule; divergence does not affect correctness. The schedule is an off-chain convenience that does not appear on-chain.

9.6 Hold-time and lifecycle timers Define τ\_hold\_max := hold\_time\_max\_s (policy). A note ii moves through states:

constructed → signed → queued → broadcast → seen → confirmed | orphaned ↘ reissued → signed → queued … ↘ cancelled

Timer rules (normative): • If queued and now − created\_at(i) ≥ τ\_hold\_max, choose exactly one: (a) reissue ii with new inputs Sᵢ′ (same i, same Addrᴮ,ᵢ, same Addrᴬ,ᵢ), reset created\_at; or (b) cancel ii explicitly (see §9.8). • If broadcast but not seen by the counterparty within 2 × rebroadcast\_interval\_s, rebroadcast. • If seen but unconfirmed for more than κ × rebroadcast\_interval\_s (κ ≥ 3), continue periodic rebroadcast until confirmed or cancelled.

9.7 Reissue procedure (pre-broadcast; deterministic labels preserved) Preconditions: note ii is not confirmed; the previous candidate Tᵢ has not been announced or is to be superseded; reasons include fee adjustment or input conflict.

Steps:

1. Release prior reservation Sᵢ to free (unless already conflicted on-chain).
2. Select new Sᵢ′ disjoint from every Sⱼ, j ≠ i (see §6).
3. Recompute fee and change at the negotiated floor (§7); derive the same Addrᴮ,ᵢ and Addrᴬ,ᵢ.
4. Build and sign new transaction Tᵢ′.
5. Update NoteMeta\[i] with fields: "supersedes": "", "version": \<prev\_version + 1>, "txid": "", "status": "signed".
6. Update the old raw bytes as voided\_offchain = true and "status": "reissued".
7. Persist an append-only audit record of the transition.

Only the latest version for index i is eligible for broadcast. Earlier versions MUST NOT be re-broadcast once superseded.

9.8 Cancellation procedure (explicit, off-chain) If a note will not be used:

1. Set NoteMeta\[i].status := "cancelled".
2. Clear reservation Sᵢ to free; do not derive a new candidate unless re-enabled.
3. Record a signed cancellation entry by the party effecting the cancel: {"i": i, "note\_id": "\<NoteIDᵢ>", "invoice\_hash": "\<Hᴵ>", "action": "cancel", "by": "\<sig\_key>", "at": "", "sig": ""}.

9.9 Duplicate broadcast semantics • Identical bytes: if Alice and Bob both submit the same Tᵢ, the txid is identical; network deduplicates; state becomes "seen" then "confirmed". • Divergent bytes (only possible after reissue): the policy forbids broadcasting superseded versions. If an older Tᵢ is propagated accidentally, whichever transaction confirms first defines settlement for i; the other is a double-spend loser and MUST be marked "obsolete" in logs. Implementations SHOULD guard by refusing to broadcast any version where NoteMeta\[i].version < current\_version.

9.10 Logging fields (canonical JSON; per note) Extend NoteMeta (§8.3) with broadcast management:

{ "i": , "note\_id": "", "invoice\_hash": "", "txid": "", "version": \<int ≥ 1>, "status": "queued|broadcast|seen|confirmed|reissued|cancelled|obsolete|orphaned", "scheduled\_at": "", "broadcast\_at": "|""", "last\_rebroadcast\_at": "|""", "confirm\_depth": , // effective d "supersedes": "|""", "superseded\_by": "|""" }

All entries are UTF-8 canonical JSON (sorted keys). Signatures over log events MAY be added using identity keys to bind audit records.

9.11 Conformance and rejection rules • If "broadcast\_overrides" conflicts with policy bounds, reject the invoice. • If a party attempts to broadcast a superseded version (version < current\_version), reject locally and log an error; do not transmit. • If a queued note exceeds τ\_hold\_max without reissue or cancel, mark "stale" and require explicit operator action before any broadcast. • Confirmation acceptance: mark a note "confirmed" only after ≥ d confirmations for its output to Addrᴮ,ᵢ; d is taken from invoice override if present, else from policy.confirm\_depth.

9.12 Determinism and auditability The schedule derived from S\_pace ensures both parties can compute the same nominal broadcast plan without coordination. Regardless of who submits, finality is defined by on-chain confirmations. The “supersedes” chain, version counter, and timestamped log records provide an append-only audit trail proving that, for each index i, exactly one transaction version was intended to settle, and any earlier candidates were explicitly voided off-chain before settlement.

10. Receipts and selective disclosure

10.1 Purpose Receipts bind a complete set of notes for an invoice into a single commitment that can later be opened selectively. The commitment is a Merkle root M computed over per-note leaves Lᵢ. To acknowledge any subset, the prover discloses those leaves and their Merkle paths; all undisclosed leaves remain hidden. Domain separation by the invoice fingerprint Hᴵ prevents cross-invoice linkage.

10.2 Exact leaf structure (bytes, canonical) Each leaf is the SHA-256 of a byte-exact concatenation of fixed-width fields. The recipient address is represented as its binary payload (version byte and 20-byte public-key hash), not as Base58 text.

Definitions

label := ASCII bytes of the literal string "leaf". • i := note index (0 ≤ i ≤ 2³²−1). • LE32(i) := 4-byte little-endian encoding of i. • txidᵢ := 32-byte transaction identifier in big-endian display order (exact bytes of the hex string). • amountᵢ := 8-byte little-endian unsigned integer (smallest unit). • Pᴮ,ᵢ := recipient public key for note i. • h160ᵢ := RIPEMD-160(SHA-256(serP(Pᴮ,ᵢ))) (20 bytes). • ver := 0x00 (1 byte; P2PKH version). • addr\_payloadᵢ := ver ∥ h160ᵢ (21 bytes). (Checksum and Base58 encoding are excluded.)

Leaf preimage and hash preimageᵢ := label ∥ LE32(i) ∥ txidᵢ ∥ amountᵢ ∥ addr\_payloadᵢ Lᵢ := SHA-256(preimageᵢ) (32 bytes)

Notes • All concatenations are byte-level. • All numeric encodings and endianness are as stated; no alternative formats are permitted. • If the implementation wishes to include additional fields (e.g., change\_amount), they MUST be placed after addr\_payloadᵢ and MUST also be fixed-width and identically encoded by both parties; otherwise omit them entirely.

10.3 Merkle tree construction (binary, deterministic) • Leaves: the sequence \[L₀, L₁, …, L\_{N−1}] in ascending index order. • Internal node hash: H(x ∥ y) := SHA-256(x ∥ y) with x,y each 32 bytes. • Layering: pair adjacent elements left-to-right; if a layer has odd cardinality, duplicate the final element (Bitcoin-style padding) so every parent has two children. Repeat until a single 32-byte value remains; that value is the Merkle root M. • Empty set: N ≥ 1 is required; for N = 1, M = L₀.

10.4 Stored commitment and manifest (canonical JSON) Persist a compact manifest for the invoice. Canonical JSON rules from §2 apply (UTF-8, NFC, sorted keys, no extraneous whitespace).

Minimal manifest { "invoice\_hash": "\<hex 32-byte H\_I>", "merkle\_root": "\<hex 32-byte M>", "count": , "entries": \[ {"i": , "txid": "\<hex 32-byte>"}, … ] }

Recommendations • Do not store amounts or addresses in the manifest; keep them private to the payer and payee. • Optionally include "created\_at" (ISO-8601 UTC) and detached signatures by identity keys to authenticate the record.

10.5 Selective disclosure proof (single leaf) A proof for index i consists of: • The disclosed leaf data (i, txidᵢ, amountᵢ, addr\_payloadᵢ). • The computed leaf hash Lᵢ. • A Merkle path πᵢ = \[(pos₀, s₀), (pos₁, s₁), …, (pos\_{h−1}, s\_{h−1})], where each sⱼ is a 32-byte sibling hash and posⱼ ∈ {"L","R"} indicates whether L (or the running hash) is on the left or right at that layer. • The root M being claimed.

Verification procedure

1. Recompute preimageᵢ := "leaf" ∥ LE32(i) ∥ txidᵢ ∥ amountᵢ ∥ addr\_payloadᵢ.
2. Compute L ← SHA-256(preimageᵢ).
3. For j = 0…h−1: • If posⱼ = "L": L ← SHA-256( L ∥ sⱼ ). • If posⱼ = "R": L ← SHA-256( sⱼ ∥ L ).
4. Accept if and only if L equals the claimed merkle\_root M (as 32-byte value). Otherwise reject.

10.6 Selective disclosure proof (multiple leaves) For a subset S ⊆ {0,…,N−1}, provide independent single-leaf proofs (as above) for each i ∈ S, or provide a multi-proof that minimises duplicate siblings by sharing path segments. A multi-proof may be represented as: { "invoice\_hash": "", "merkle\_root": "", "leaves": \[ {"i": , "txid": "", "amount": , "addr\_payload": "\<hex 21-byte>"}, … ], "sibling\_hashes": \["\<hex 32-byte>", …], "structure": "" // e.g., a bitstring specifying merge order } Verification recomputes the same frontier using the structure bitstring; if unfamiliar with multi-proofs, verify each leaf independently.

10.7 Privacy properties • For any disclosed leaf i, the verifier learns (i, txidᵢ, amountᵢ, addr\_payloadᵢ). • For any undisclosed leaf j ∉ S, no information beyond the length N and the set of indices S is revealed; sibling hashes are computationally indistinguishable from random without preimages. • The manifest stores only (i, txidᵢ) pairs, avoiding publication of amounts and addresses. • Domain separation by H\_I ensures that identical (i, txidᵢ, amountᵢ, addr\_payloadᵢ) under a different invoice yields a different leaf and root.

10.8 Examples of partial proofs (formats)

Example A — single-leaf proof (index 7) { "invoice\_hash": "…h\_i…", "merkle\_root": "…m…", "leaf": { "i": 7, "txid": "…32-byte-hex…", "amount": 1420, "addr\_payload": "00…20-byte-h160…" }, "path": \[ {"pos": "L", "hash": "…32-byte-hex…"}, {"pos": "R", "hash": "…32-byte-hex…"}, {"pos": "R", "hash": "…32-byte-hex…"} ] }

Example B — two-leaf independent proofs (indices 3 and 11) \[ { "invoice\_hash": "…h\_i…", "merkle\_root": "…m…", "leaf": {"i": 3, "txid": "…", "amount": 600, "addr\_payload": "00…"}, "path": \[ … ] }, { "invoice\_hash": "…h\_i…", "merkle\_root": "…m…", "leaf": {"i": 11, "txid": "…", "amount": 785, "addr\_payload": "00…"}, "path": \[ … ] } ]

Example C — redacted manifest with selective disclosure Manifest (public): { "invoice\_hash": "…h\_i…", "merkle\_root": "…m…", "count": 72, "entries": \[ {"i": 0, "txid": "…"}, {"i": 1, "txid": "…"}, … ] } Disclosure: provide Example A for i = 7 only. Verifier checks that the txid in the proof matches the manifest entry for i = 7 and that the recomputed root equals merkle\_root.

10.9 Edge conditions and rejection rules • Reject any proof where the byte encodings (LE32(i), amountᵢ, addr\_payloadᵢ) do not match the normative widths and endianness. • Reject if txidᵢ is not a 32-byte value (invalid hex length or characters). • Reject if any path element has an invalid "pos" or a non-32-byte sibling. • Reject if the recomputed root ≠ merkle\_root M. • Reject if invoice\_hash in the proof ≠ H\_I for the invoice under consideration.

10.10 Construction and logging (normative) • Both parties compute the same M from the same ordered leaf list. • Store merkle\_root M, count N, and entries \[(i, txidᵢ)] in the manifest. • Optionally sign the manifest with identity keys to authenticate receipt creation: {"sig\_key": "\<hex serP(Pₐ or Pᵦ)>", "sig\_alg": "secp256k1-sha256", "sig": ""} • For each disclosed proof later, store the presented leaf and path alongside the manifest for audit.

10.11 Deterministic recomputation Given H\_I, the derivation rules, and full knowledge of the note set, either party can recompute every preimage and thus M exactly. Given only the manifest and a subset proof, a third party verifies membership of the disclosed leaves without learning the undisclosed ones.

10.12 Interoperability notes • The use of addr\_payload (ver ∥ h160) avoids ambiguity and normalisation issues inherent in Base58 strings and checksums, while remaining consistent with P2PKH addressing. • If an implementation wishes to future-proof for alternative script types, include an extra 1-byte "script\_tag" before addr\_payloadᵢ to denote the payload format; for this specification, script\_tag = 0x00 (P2PKH) and MUST be present if the extension is adopted by both parties.

11. Failure handling and exact behaviours

11.1 Overview (normative) Failures are handled deterministically at two levels: per-note and per-invoice. Every transition is recorded in canonical JSON with timestamps and signatures by the acting party’s identity key. All timers are measured in seconds since the Unix epoch; all timestamps are ISO-8601 UTC.

— Global timers and flags — • τ\_hold\_max := policy.broadcast.hold\_time\_max\_s (maximum queued time before action). • τ\_rebroadcast := policy.broadcast.rebroadcast\_interval\_s (periodic re-announce). • d\_confirm := effective confirmation depth (invoice override or policy default). • expires\_at\_policy := policy.expiry. • expires\_at\_invoice := invoice.expiry (if present). • fanout\_allowed := implementation/policy toggle; at most one fan-out per invoice. • supersedes flag := indicates a newer transaction version exists for the same index i. • voided\_offchain := indicates older raw bytes must not be broadcast.

11.2 Per-note state machine (textual diagram)

States: S0 Constructed → S1 Signed → S2 Queued → S3 Broadcast → S4 Seen → S5 Confirmed ↘ R Reissued (loops back to S1) ↘ C Cancelled (terminal) ↘ O Orphaned (from S5; loops to S3 on rebroadcast) ↘ X Obsolete (terminal; older superseded bytes) ↘ F Conflict (terminal; external spend of reserved inputs)

Transitions (events → new state): • construct(i) → S0 • sign(i) → S1 • enqueue(i) → S2 • t ≥ scheduled\_at(i) ∧ authority\_either → broadcast(i) → S3 • mempool\_accept(i) → S4 • conf\_depth(i) ≥ d\_confirm → S5 • t − created\_at(i) ≥ τ\_hold\_max ∧ S2 → reissue(i) → R → S1 • explicit\_cancel(i) at S0/S1/S2/S3/S4 → C • superseded(i) (new Tᵢ′ built) → old version → X; new version → S1 • external\_conflict(Sᵢ) (reserved input spent elsewhere) → F • reorg\_orphan(i) at S5 → O → rebroadcast(i) → S3

Determinism: given identical logs and timers, the same sequence of transitions occurs.

11.3 Invoice-level state machine (textual diagram)

I0 Open → I1 Fan-out-Pending (optional) → I2 Building → I3 Ready → I4 Broadcasting → I5 Closing ↘ IF Insufficient-UTXO (terminal failure) ↘ IE Expired (terminal failure) ↘ IS Stopped (operator abort) ↘ IC Completed (terminal success: all i ∈ \[0…N−1] in S5)

Triggers: • accept(policy, invoice) → I0 • fanout\_start (if required) → I1 → fanout\_confirmed → I2 • reservations\_built(R) → I3 • any\_broadcast → I4 • all notes S5 → IC • t ≥ expires\_at\_invoice or t ≥ expires\_at\_policy → IE • Σ value(U₀) insufficient or granularity impossible after one fan-out → IF

11.4 Deterministic behaviours by failure class (normative)

A) Insufficient UTXOs Condition: Σ value(U₀) < Σ a\[i] + fees\_min, or granularity failure after bounded-knapsack (§6). Action sequence:

1. If fanout\_allowed = true and no prior fan-out: perform exactly one preparatory fan-out (payer→payer) per §6.8; wait per policy (confirm or risk-accepted) deterministically.
2. Rebuild U₀ from fan-out result; rebuild R.
3. If still insufficient, set invoice.state := IF and emit failure record; no notes are issued or all outstanding notes are cancelled (see 11.6). Audit: record {"event":"insufficient\_utxo","at":…,"fanout\_attempted":true|false}.

B) Fee change before broadcast Condition: either party requires a new fee rate > fee\_rate\_floor while a note i is S0/S1/S2 and no broadcast has occurred for i. Action sequence:

1. Reissue i: select new Sᵢ′ (disjoint), recompute fee at requested rate (must be ≥ floor), preserve Addrᴮ,ᵢ and Addrᴬ,ᵢ.
2. Set NoteMeta\[i].supersedes := txid\_old; version := version + 1.
3. Mark old bytes voided\_offchain := true and status := "reissued".
4. New candidate enters S1 (Signed). Audit: append {"i":i,"event":"reissue","supersedes":"\<txid\_old>","version":v}.

C) Conflicting spend detected Condition: any u ∈ Sᵢ appears spent on-chain or reserved contradictorily by external wallet mutation while note i ∈ {S0,S1,S2}. Action sequence:

1. Abort invoice build immediately: invoice.state := I2 (rebuild) or I5 (closing) depending on operator policy.
2. Invalidate R; take a fresh snapshot U′; rebuild reservations per §6 or fail IF.
3. For the conflicted note i, mark status := "conflict" and record the offending outpoint. Audit: record {"i":i,"event":"conflict\_external","outpoint":{"txid":…,"vout":…},"at":…}. Determinism: any detected conflict forces a full reservation rebuild; partial salvage is not permitted.

D) Reorg Condition: a confirmed note i (S5) is orphaned by reorganisation (conf\_depth falls below d\_confirm). Action sequence:

1. Transition i → O (Orphaned).
2. Rebroadcast the same raw bytes of Tᵢ (no reconstruction, no re-sign).
3. Return to S3 (Broadcast) → S4 (Seen) → S5 (Confirmed) as usual. Audit: record {"i":i,"event":"orphaned","rebroadcast":true,"txid":"","at":…}. Constraint: reissue is not permitted for orphaned-but-valid transactions unless they later double-spend fail; first action is always rebroadcast.

E) Expiry Condition: t ≥ expires\_at\_invoice or t ≥ expires\_at\_policy before all notes reach S5. Action sequence:

1. For every i ∈ {S0,S1,S2}: set status := "cancelled"; release reservations.
2. For i ∈ {S3,S4}: continue passive monitoring until either confirmed (counted) or timeout set by operator; no new broadcasts after expiry.
3. Set invoice.state := IE and emit summary record with counts by status. Audit: record {"event":"invoice\_expired","at":…,"counts":{"cancelled":…,"broadcast":…,"confirmed":…}}.

11.5 Timers and automated actions (per note)

Scheduler tick (every τ\_rebroadcast seconds): • If status = "broadcast" ∨ "seen" and conf\_depth < d\_confirm → rebroadcast raw bytes. • If status = "queued" and t − created\_at >= τ\_hold\_max → reissue(i) (preferred) or cancel(i) per policy. • If status = "signed" and scheduled\_at reached → broadcast(i).

All actions are idempotent: rebroadcasting identical bytes preserves txid; reissuing increments version and voids older bytes.

11.6 Precise reissue and cancel records (canonical JSON)

Reissue record (append-only): { "invoice\_hash":"", "i":, "note\_id":"\<hex NoteIDᵢ>", "event":"reissue", "version":, // new version "supersedes":"", "txid\_new":"", "addr\_recv":"\<base58 Addrᴮ,ᵢ>", "addr\_change":"\<base58 Addrᴬ,ᵢ>", "fee":, "feerate\_used":, "at":"", "by":"\<hex serP(Pₐ or Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

Cancel record (append-only): { "invoice\_hash":"", "i":, "note\_id":"\<hex NoteIDᵢ>", "event":"cancel", "reason":"hold\_timeout|operator|expiry|insufficient\_utxo", "version":, "at":"", "by":"\<hex serP(Pₐ or Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

Conflict record (append-only): { "invoice\_hash":"", "i":, "note\_id":"\<hex NoteIDᵢ>", "event":"conflict\_external", "outpoint":{"txid":"","vout":}, "at":"", "by":"\<hex serP(Pₐ or Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

Orphaned record (append-only): { "invoice\_hash":"", "i":, "note\_id":"\<hex NoteIDᵢ>", "event":"orphaned", "txid":"", "rebroadcast":true, "at":"", "by":"\<hex serP(Pₐ or Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

11.7 Rejection conditions (must fail immediately) • Attempt to broadcast a superseded version (version < current\_version). • Attempt to broadcast after invoice expiry (except passive rebroadcasts of already-broadcast notes until final state is reached). • Attempt to reuse change from one note to fund another note in the same invoice. • Attempt to continue with reservations after detecting external conflict; a full rebuild is mandatory.

11.8 Determinism and auditability guarantees • Given identical inputs (policy, invoice, {Z,Hᴵ}, U₀) and identical timer parameters, two independent implementations will produce identical reservation tables, note schedules, and failure-handling transitions. • The append-only reissue/cancel logs, combined with NoteMeta and the receipt manifest (§10), are sufficient to reconstruct the exact intended settlement set and to demonstrate that exactly one transaction per index i was designated to settle, with earlier byte serialisations voided before settlement.

12. Privacy and linkage analysis (bounded disclosures; role separation)

12.1 Adversary model (explicit) • A₁ — Passive chain analyst: observes full blocks and mempools, parses scripts, amounts, timings, and addresses; cannot break SHA-256, RIPEMD-160, or the secp256k1 discrete-log problem. • A₂ — Passive network observer: sees when and from which IP a transaction is first announced; cannot tamper with payloads. • A₃ — Inquisitive counterparty: one party attempts to learn more than the protocol reveals; has its own keys and transcript but lacks the other party’s secret scalars. • A₄ — Data-broker adversary: correlates public off-chain artefacts (e.g., leaked policies, screenshots, manifests) with on-chain activity. Assumptions: preimage resistance of SHA-256, collision resistance where invoked, and hardness of ECDLP on secp256k1.

12.2 Role separation and cryptographic domains Identity keys never appear in locking scripts; only settlement keys derived from anchors do. All recipient keys are derived in the “recv” domain: Pᴮ,ᵢ = B + tᵢ·G, tᵢ := int(SHA-256(Z ∥ Hᴵ ∥ "recv" ∥ LE32(i))) mod n, tᵢ ≠ 0. All sender-change keys are derived in the “snd” domain: Pᴬ,ᵢ = A + sᵢ·G, sᵢ := int(SHA-256(Z ∥ Hᴵ ∥ "snd" ∥ LE32(i))) mod n, sᵢ ≠ 0. Domain labels (“recv”, “snd”) and the invoice fingerprint Hᴵ ensure that the same index i across invoices yields unrelated points; identity/settlement separation prevents anchor leakage via off-chain signatures.

12.3 What an outsider can and cannot infer

1. Linking recipient addresses across notes (same invoice). Cannot: Without Z and Hᴵ, an outsider cannot recompute tᵢ and so cannot test membership of any address in the set {B + tᵢ·G}. Observing two spends reveals serP(Pᴮ,ᵢ) and serP(Pᴮ,ⱼ), but the relation Pᴮ,ᵢ − Pᴮ,ⱼ = (tᵢ − tⱼ)·G holds for all random points; without tᵢ, tⱼ it gives no anchor linkage. Limit: If the anchor B itself is leaked off-chain and the adversary also learns Z or Hᴵ (both should remain secret), they could regenerate addresses; by design, neither Z nor Hᴵ is published.
2. Linking across invoices (same payee). Cannot: Hᴵ changes per invoice; tᵢ depends on Hᴵ. Even with B known, without Z the adversary cannot bind addresses to a particular invoice scope. Limit: Long-term address-reuse does not occur; accidental reuse would require tᵢ collision or (b + tᵢ) mod n repeating—negligible under SHA-256 and modular arithmetic.
3. Change-based clustering within an invoice. Prevented: Change pays to per-note Addrᴬ,ᵢ under “snd”; selection forbids using change from one note to fund another of the same invoice. The common “shared-change” heuristic therefore does not tie notes i and j together. Limit: Each individual note still clusters its own inputs (multi-input heuristic) to the payer, which is acceptable: the payer’s identity is not a privacy target here; recipient unlinkability is.
4. Amount and bounds leakage. Possible: When many notes are broadcast close in time, an observer may infer that outputs lie in \[v\_min, v\_max] and approximate N by histogramming. Limit: Exact partition a\[·] remains hidden off-chain; permutation decouples indices from sizes; paced broadcast blurs simultaneity.
5. Timing linkage. Possible: If all notes are broadcast at once from a single IP, a₁/a₂ can cluster them temporally and by origin peer. Mitigation below: either-side broadcast; paced or bursty schedules; diverse first-announcers.

(6) Off-chain artefact correlation. Possible: If a policy or manifest leaks publicly (A₄), an adversary learns B and (i, txid) pairs. Limit: txid alone does not reveal amounts or change; B does not reveal kᴮ,ᵢ or tᵢ; receipts commit to data but selective proofs reveal only chosen leaves.

12.4 Recommended mitigations (no new primitives)

M₁ — Either-side broadcast. Let the recipient submit a random subset first. This breaks any single-origin heuristic; first-seen IP attribution no longer correlates the whole set to the payer.

M₂ — Paced or burst scheduling with deterministic jitter. Use the S\_pace-derived schedule (§9) to spread announcements over a window. Choose “bursts” with β ≪ N and non-uniform burst gaps to defeat simple time-window clustering.

M₃ — Wider published bounds than actually used. Bob may publish \[v\_min\_pub, v\_max\_pub] with v\_min ≤ v\_min\_pub and v\_max\_pub ≤ v\_max; enforce the true internal bounds off-chain. Observers only learn the loose public interval.

M₄ — Interleave multiple invoices. When operationally feasible, the payer constructs two or more invoices concurrently and interleaves broadcasts. This raises ambiguity without changing any primitive.

M₅ — Strict no-reuse within invoice. Enforce “no intra-invoice change reuse” and “no cross-note input reuse” (already normative) to preclude standard clustering hooks.

M₆ — Avoid deterministic time-of-day fingerprints. Do not always start at the top of the hour; salt schedule start with H(Z ∥ Hᴵ ∥ "pace") to vary timing patterns.

M₇ — Minimal public metadata. Manifests published externally should contain only (i, txid) and the Merkle root M; keep amounts, addresses, and H160 payloads private. Sign manifests with identity keys to prove authorship without revealing anchors.

12.5 Specific inferences and their limits (tabular)

Inference: “These outputs belong to the same payer.” Basis: multi-input heuristic per note. Limit: Does not link different notes together; does not link recipient addresses across notes.

Inference: “These outputs belong to the same payee.” Basis: proximity, equal denominations in \[v\_min, v\_max]. Limit: Without Z and Hᴵ, cannot prove common derivation from B; pacing and interleaving degrade confidence.

Inference: “This set is one invoice of size T.” Basis: near-simultaneous group of bounded outputs summing to ≈ T. Limit: Pacing plus concurrent invoices makes partitioning NP-hard in practice; receipts reveal only when the payee consents.

Inference: “These two payee public keys share an anchor B.” Basis: Pᴮ,ᵢ − Pᴮ,ⱼ is some scalar times G. Limit: Trivial in any cyclic group; provides no test for a shared anchor without tᵢ, tⱼ; computing b from B is ECDLP-hard.

12.6 Residual risks and operator guidance

R₁ — Broadcast-pattern fingerprinting. Residual risk: sophisticated a₂ correlates bursts over long windows. Guidance: vary β and gaps per invoice; occasionally switch who broadcasts first.

R₂ — Anchor disclosure. Residual risk: if B is widely published and Z or Hᴵ leaks, address sets become derivable. Guidance: treat Z and Hᴵ as confidential; never publish invoice JSON or transcripts; rotate anchors periodically (operational) without changing identity keys.

R₃ — Wallet hygiene. Residual risk: accidental reuse if software ignores reject-zero logic or mis-scopes derivations. Guidance: enforce reject-zero; unit tests that re-derive all Pᴮ,ᵢ and Pᴬ,ᵢ from logs; fail closed on any mismatch.

Summary. Outsiders cannot link recipient addresses across notes or invoices because identity keys never appear on-chain and per-note keys require {Z, H\_I} with domain separation. Amount bounds leak a coarse interval; pacing, interleaving, and wider published bounds blunt inference. Change addresses are per-note under “snd”, eliminating shared-change clustering. All recommendations require no new primitives—only deterministic use of the ones already defined.

13. Security considerations (keys, transcripts, and logs)

13.1 Key domains and separation (normative) • Identity (off-chain): Kₐ = (kₐ, Pₐ), Kᵦ = (kᵦ, Pᵦ). Purpose: authenticate messages; sign policy, invoice, and logs. Identity keys MUST NEVER appear in locking scripts or be used to spend on-chain funds. • Anchors (on-chain bases): A = a·G (sender change domain), B = b·G (recipient receive domain). Purpose: derive settlement keys per invoice and per note. Anchor private scalars (a, b) MUST NEVER sign off-chain identity artefacts. • Scope tuple: {Z, Hᴵ}. Z := ECDH(kₐ, Pᵦ) = ECDH(kᵦ, Pₐ) (32-byte x-coordinate). Hᴵ := H(canonical\_json(invoice)). Hᴵ MUST be unique per invoice and MUST NOT be reused. Z SHOULD be recomputed on demand and SHOULD NOT be stored at rest unless encrypted under an operator-controlled mechanism (implementation policy).

13.2 Key-handling checklist (operational) ✓ Generate identity and anchor keys on distinct cryptographic modules or profiles. ✓ Store kₐ, kᵦ, a, b in separate sealed locations; never co-reside identity and anchor secrets for the same principal on the same soft-keystore without OS isolation. ✓ Export only compressed public keys (serP) to peers; never export private material. ✓ Enforce constant-time scalar use; disable debug traces of secret scalars. ✓ Back up identity keys (kₐ, kᵦ) with out-of-band recovery; back up anchors (a, b) with the same or stronger controls; record public anchors (A, B) separately for verification. ✓ Rotate anchors periodically (operational policy) and immediately on suspected compromise; old anchors remain valid only for historical settlement and verification. ✓ Bind every signed artefact to the exact canonical JSON bytes; do not sign ad hoc serialisations. ✓ Treat transcripts and logs as confidential; encrypt at rest per environment policy; access is least-privilege. ✓ Never reuse Hᴵ; never derive keys or amounts without {Z, Hᴵ} and the correct domain label (“recv”, “snd”). ✓ Refuse to proceed if any signature verification or canonicalisation check fails.

13.3 Compromise impact summary • kₐ or kᵦ compromised → attacker can forge off-chain messages/logs for that identity; on-chain funds unaffected; rotate identity key; rebind future policies/invoices; mark prior logs with rotation event. • a (sender anchor) compromised → attacker can spend sender change if they control UTXOs addressed to Pᴬ,ᵢ; recipient funds unaffected; rotate A, quarantine outstanding change, rebuild change policy. • b (recipient anchor) compromised → attacker may spend future notes to Addrᴮ,ᵢ; rotate B immediately; cease using the compromised anchor; existing receipts remain valid.

13.4 Log and transcript principles • Canonical JSON only: UTF-8, NFC, sorted keys, no extraneous whitespace (see §2). • Detached signatures: every signed record includes {"sig\_key","sig\_alg","sig"} where the signature is computed over the canonical bytes of the record with the signature triplet omitted from the preimage. • Append-only: logs are monotone sequences with sequence numbers and a hash-chain (“prev\_hash”) for tamper evidence. • Append-only: logs are monotone sequences with sequence numbers and a hash-chain (“prev\_hash”) for tamper evidence. • Append-only: logs are monotone sequences with sequence numbers and a hash-chain (“prev\_hash”) for tamper evidence. • Append-only: logs are monotone sequences with sequence numbers and a hash-chain (“prev\_hash”) for tamper evidence.

13.5 Signature placement (normative) • Policy (Bob) — signed by kᵦ. • Invoice (Alice) — signed by kₐ. • Reservation table R and per-note NoteMeta updates — signed by the party performing the action (payer when constructing/funding; either when broadcasting). • Receipt manifest (Merkle root M) — at least signed by the recipient; co-signature by the payer is RECOMMENDED for bilateral acknowledgement. • Reissue / cancel / conflict / orphan records — signed by the actor identity key initiating the state change.

13.6 Core log schemas (canonical JSON; required fields; sorted keys)

A) Key registry (local, not exchanged) { "anchors": { "payer": {"pub":"\<hex serP(A)>"}, "payee": {"pub":"\<hex serP(B)>"} }, "identities": { "payer": {"pub":"\<hex serP(Pₐ)>"}, "payee": {"pub":"\<hex serP(Pᵦ)>"} }, "created\_at":"", "sig\_alg":"secp256k1-sha256", "sig\_key":"\<hex serP(Pₐ or Pᵦ)>", "sig":"" }

B) Policy record (Bob → Alice) — as §3.3 plus envelope { "policy": { …canonical policy object per §3.3… }, "hash":"", "record\_type":"policy\_record", "prev\_hash":"\<hex|"">", "seq":, "created\_at":"", "sig\_key":"\<hex serP(Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

C) Invoice record (Alice → Bob) — as §3.4 plus envelope { "invoice": { …canonical invoice object per §3.4… }, "invoice\_hash":"\<hex Hᴵ>", "record\_type":"invoice\_record", "prev\_hash":"", "seq":, "created\_at":"", "sig\_key":"\<hex serP(Pₐ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

D) Reservation table (payer, deterministic R) — summary plus per-note entries { "invoice\_hash":"\<hex Hᴵ>", "record\_type":"reservations", "entries":\[ { "i":, "note\_id":"\<hex NoteIDᵢ>", "inputs":\[{"txid":"","vout":,"value":}], "sum\_inputs":, "feerate\_used":, "created\_at":"" }, … ], "prev\_hash":"", "seq":, "sig\_key":"\<hex serP(Pₐ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

E) NoteMeta (per note; extends §8.3 with lifecycle) { "i":, "note\_id":"", "invoice\_hash":"\<hex Hᴵ>", "addr":"\<base58 Addrᴮ,ᵢ>", "amount":, "txid":"\<hex|"">", "change\_addr":"\<base58 Addrᴬ,ᵢ|"">", "change\_amount":, "size\_bytes":, "fee":, "feerate\_used":, "inputs": \[ {"txid":"","vout":,"value":,"scriptPubKey":""} ], "outputs": \[ {"addr":"","value":}, {"addr":"","value":}? ], "sig\_alg": "secp256k1-sha256", "created\_at": "", "updated\_at": "", "prev\_hash":"", "seq":, "sig\_key":"\<hex serP(Pₐ or Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

F) Receipt manifest (Merkle root M) — see §10; repeated here with envelope { "invoice\_hash":"\<hex Hᴵ>", "merkle\_root":"", "count":, "entries":\[{"i":,"txid":""}…], "record\_type":"receipt\_manifest", "prev\_hash":"", "seq":, "created\_at":"", "sig\_key":"\<hex serP(Pᵦ)>", "sig\_alg":"secp256k1-sha256", "sig":"" }

G) Event records (reissue / cancel / conflict / orphan) — see §11.6; all include {invoice\_hash, i, note\_id, prev\_hash, seq, created\_at, sig\_key, sig\_alg, sig}.

13.7 Transcript chaining (tamper evidence) Each persisted record Rₖ includes "prev\_hash" = H(canonical\_bytes(Rₖ₋₁ without its signature triplet)) and "seq" = seqₖ₋₁ + 1. A daily (or per-invoice) journal root J := MerkleRoot( H(canonical\_bytes(Rₖ)) for all k in window ) MAY be published or anchored externally to provide a timestamped, tamper-evident commitment to the log without revealing contents.

13.8 Retention and export • Retain logs and manifests for the statutory/accounting horizon; redact only where policy permits. • Export is the ordered canonical JSON stream with signatures; verification replays the hash-chain and signatures, recomputes R, NoteMeta, and M, and checks that exactly one final transaction per i reached "confirmed".

13.9 Conformance (must-pass checks) • Identity/anchor separation enforced at API boundaries (no cross-domain signing). • Hᴵ uniqueness per invoice; refusal to load or write logs if an Hᴵ collision is detected. • All signatures verify over canonical bytes; any failure halts processing. • Hash-chain continuity: each "prev\_hash" matches the prior record; gaps or forks are rejected unless explicitly marked as a rotated journal with a signed boundary record.

This section specifies the mandatory operational controls for key isolation and the precise, signed, append-only logging required to reconstruct, verify, and audit every invoice, note, and receipt without exposing identity keys on-chain.

14. Minimal message frames (UTF-8 JSON; canonical where hashed)

14.1 Policy (Bob → Alice)

Wire frame (compact):

{"pk\_anchor":"", "vmin":, "vmax":, "feerate\_floor":, "expiry":"", "sig":""}

Canonical field order (for signature preimage): expiry, feerate\_floor, pk\_anchor, vmax, vmin (“sig” is excluded from the preimage)

Signature input (bytes to sign): canonical\_json({pk\_anchor, vmin, vmax, feerate\_floor, expiry}) → SHA-256 → ECDSA sign with K\_B.

Verification rules (normative): • pk\_anchor is 33-byte SEC1 compressed hex (0x02/0x03 + 32-byte x), on-curve. • vmin ≥ 1, vmax ≥ vmin, feerate\_floor ≥ 1; expiry is future UTC. • Verify sig with P\_B over SHA-256(canonical\_json(policy\_without\_sig)). • H\_policy := SHA-256(canonical\_json(policy\_with\_sig)) is the policy hash used by the invoice. • Canonical JSON per §2 (UTF-8, NFC strings, sorted keys, no extra whitespace).

14.2 Invoice (Alice → Bob)

Wire frame (compact):

{"invoice\_number":"", "terms":"", "unit":"", "total":, "policy\_hash":"\<hex 32-byte H\_policy>", "expiry":"", "sig":""}

Canonical field order (for signature preimage): expiry, invoice\_number, policy\_hash, terms, total, unit (“sig” is excluded from the preimage)

Signature input (bytes to sign): canonical\_json({invoice\_number, terms, unit, total, policy\_hash, expiry}) → SHA-256 → ECDSA sign with K\_A.

Verification rules (normative): • total ≥ 1; policy\_hash equals H\_policy from the accepted policy (14.1). • Verify sig with P\_A over SHA-256(canonical\_json(invoice\_without\_sig)). • H\_I := SHA-256(canonical\_json(invoice\_with\_sig)) is the invoice fingerprint for all derivations. • If expiry is present, it MUST be future UTC at acceptance.

14.3 NoteMeta (optional to exchange; log/audit)

Wire frame (compact):

{"i":"", "addr":"", "amount":"", "invoice\_hash":"\<hex 32-byte H\_I>", "txid":"\<hex 32-byte>"}

Canonical field order (when hashing/recording): addr, amount, i, invoice\_hash, txid

Verification rules (normative): • i ∈ \[0, 2³²−1]; amount ≥ 1. • addr decodes to version 0x00 and a 20-byte payload. • txid is 32-byte hex (big-endian textual form). • invoice\_hash equals H\_I for this invoice. • addr must equal Addr\_B,i derived from {Z, H\_I, i, B} (sender/recipient can recompute). • Optional signatures over NoteMeta (if used) are made with the actor’s identity key over SHA-256(canonical\_json(NoteMeta\_without\_sig)).

14.4 Canonical JSON recap (applies wherever “canonical\_json” is referenced)

UTF-8 encoding; no BOM; all strings NFC-normalised. • Keys sorted lexicographically by Unicode code point. • No insignificant whitespace (compact objects/arrays). • Integers in base-10 without leading zeros (except “0”). • Hex fields are lowercase, no “0x”. • Timestamps are ISO-8601 with “Z”.

14.5 Cross-checks (receiver MUST perform)

Policy:

1. Parse and validate fields; verify ECDSA(sig, SHA-256(canonical\_json(policy\_without\_sig)), P\_B).
2. Compute H\_policy = SHA-256(canonical\_json(policy\_with\_sig)).

Invoice:

1. Verify policy\_hash = H\_policy.
2. Verify ECDSA(sig, SHA-256(canonical\_json(invoice\_without\_sig)), P\_A).
3. Compute H\_I = SHA-256(canonical\_json(invoice\_with\_sig)) and cache {Z, H\_I} for derivations.

NoteMeta (if exchanged):

1. Verify invoice\_hash = H\_I.
2. Recompute Addr\_B,i; check equality.
3. Optionally verify txid exists or matches a provided raw transaction.

All frames are minimal by design; anything not explicitly listed is out of scope and MUST be omitted.

15. Reference pseudocode

Per-note recipient key (sender view)

Z := ECDH(k\_A, K\_B) # 32-byte x-coordinate H\_I := SHA256(canonical\_json(invoice)) # invoice fingerprint

t\_i := int( SHA256( Z ∥ H\_I ∥ "recv" ∥ LE32(i) ) ) mod n if t\_i = 0: ctr := 1 repeat: t\_i := int( SHA256( Z ∥ H\_I ∥ "recv" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n ctr := ctr + 1 until t\_i ≠ 0

P\_Bi := point\_add( B, scalar\_mul(t\_i, G) ) addrB\_i := Base58Check( 0x00 ∥ RIPEMD160( SHA256( serP(P\_Bi) ) ) )

Per-note sender change (payer view)

s\_i := int( SHA256( Z ∥ H\_I ∥ "snd" ∥ LE32(i) ) ) mod n if s\_i = 0: ctr := 1 repeat: s\_i := int( SHA256( Z ∥ H\_I ∥ "snd" ∥ LE32(i) ∥ LE32(ctr) ) ) mod n ctr := ctr + 1 until s\_i ≠ 0

P\_Ai := point\_add( A, scalar\_mul(s\_i, G) ) addrA\_i := Base58Check( 0x00 ∥ RIPEMD160( SHA256( serP(P\_Ai) ) ) )

Bounded split (exact sum)

S := SHA256( Z ∥ H\_I ∥ "split" ) S\_perm := SHA256( S ∥ "permute" )

function next\_u64(seed, ctr): R := SHA256( seed ∥ LE32(ctr) ) ctr := ctr + 1 return (first 8 bytes of R as uint64), ctr

function draw\_uniform(seed, ctr, range): # unbiased draw in \[0, range−1] M := 2^64 lim := (M ÷ range) × range loop: (u, ctr) := next\_u64(seed, ctr) if u < lim: return (u mod range), ctr

Nmin := ceil(T ÷ v\_max) Nmax := floor(T ÷ v\_min) N := choose\_count(S, Nmin, Nmax) # deterministic, interior-biased

rem := T ctr := 0 for i in 0 … N−2: slots := N−1−i low := max( v\_min, rem − v\_max × slots ) high := min( v\_max, rem − v\_min × slots ) (r, ctr) := draw\_uniform(S, ctr, high − low + 1) a\[i] := low + r rem := rem − a\[i] a\[N−1] := rem

**Fisher–Yates permutation to decorrelate indices from sizes**

ctrp := 0 for j in (N−1) down to 1: (r, ctrp) := draw\_uniform(S\_perm, ctrp, j + 1) swap a\[j] ↔ a\[r]

Coin selection with reservation

U := snapshot\_available\_utxos() payer-owned, spendable, filtered R := {} reservation table: i ↦ S\_i

for i in note\_indices\_in\_fixed\_order(): e.g., by descending a\[i], then i target := a\[i] (S\_i, fee, change) := select\_inputs\_disjoint(U, target, feerate\_floor, dust) R\[i] := S\_i U := U \ S\_i remove reserved inputs (strict non-overlap)

Change arithmetic and size estimate

**m inputs, n outputs**

(n ∈ {1,2}); P2PKH sizes size\_bytes(m, n) := 10 + 148×m + 34×n fee(m, n) := ceil( feerate\_floor × size\_bytes(m, n) )

sum\_in := Σ value(input ∈ S\_i) target := a\[i]

**Try no-change first**

fee1 := fee(m=|S\_i|, n=1) if sum\_in = target + fee1: outputs := \[ (addrB\_i, target) ] # exact match, n = 1 else: fee2 := fee(m=|S\_i|, n=2) change := sum\_in − target − fee2 if change ≥ dust: outputs := \[ (addrB\_i, target), (addrA\_i, change) ] # n = 2 elif 0 < change < dust: reseat\_inputs() # add/select different inputs; recompute else: # change ≤ 0 reseat\_inputs() # underfunded; add/select different inputs

16. Worked-through flow (illustrative only)

Step 0 — Preconditions • Alice (payer): identity K\_A = (k\_A, P\_A); sender anchor A = a·G. • Bob (payee): identity K\_B = (k\_B, P\_B); recipient anchor B = b·G. • All amounts are in the smallest settlement unit. This slice uses N = 7 notes for readability; amounts remain symbolic (a₀…a₆) and are illustrative only.

Step 1 — Policy exchange (Bob → Alice) Bob sends a canonical Policy JSON; Alice verifies signature and computes H\_policy. Example (placeholders only):

{"pk\_anchor":"\<hex serP(B)>", "vmin":\<v\_min>, "vmax":\<v\_max>, "feerate\_floor":, "expiry":"", "sig":""}

Alice verifies pk\_anchor structure, bounds, feerate\_floor, expiry, and sig(K\_B).

Step 2 — Invoice issuance (Alice → Bob) Alice issues a canonical Invoice JSON referencing H\_policy; Bob verifies and computes H\_I:

{"invoice\_number":"", "terms":"", "unit":"", "total":, "policy\_hash":"", "expiry":"", "sig":""}

Both parties compute the per-invoice scope: • Z := ECDH(k\_A, K\_B) = ECDH(k\_B, K\_A) (32-byte x-coordinate). • H\_I := SHA-256(canonical\_json(invoice)). All subsequent derivations are functions of {Z, H\_I}.

Step 3 — Bounded split (deterministic; exact sum) Seed S := SHA-256( Z ∥ H\_I ∥ "split" ). Compute N\_min := ⌈T ÷ v\_max⌉, N\_max := ⌊T ÷ v\_min⌋; choose N ∈ \[N\_min, N\_max] deterministically (interior-biased). For this slice, N = 7. Compute a vector a = (a₀, a₁, …, a₆) with v\_min ≤ aᵢ ≤ v\_max and Σ aᵢ = T using prefix clamping; then apply a seeded Fisher–Yates permutation with S\_perm := H(S ∥ "permute"). Both parties obtain identical (N, a) without exchanging per-note amounts.

Step 4 — Reservation build (disjoint funding) Alice snapshots her UTXO pool U₀ and constructs a reservation table R with pairwise disjoint input sets Sᵢ. Deterministic ordering: fund larger aᵢ first; UTXOs iterated in fixed (value↑, txid↑, vout↑) order. For illustration: • S₀ = {u\_3, u\_9} • S₁ = {u\_1} • S₂ = {u\_5, u\_7, u\_12} • S₃ = {u\_8} • S₄ = {u\_2, u\_10} • S₅ = {u\_4} • S₆ = {u\_6, u\_11} with Sᵢ ∩ Sⱼ = ∅ for all i ≠ j. Exact members and values are determined by the bounded-knapsack policy (§6). Size/fee are computed at feerate\_floor; candidates producing dust change are rejected or reseated.

Step 5 — Per-note addresses (recipient and change) For each index i = 0…6:

Recipient (sender view): • tᵢ := int( SHA-256( Z ∥ H\_I ∥ "recv" ∥ LE32(i) ) ) mod n; if tᵢ = 0, bump ctr deterministically until non-zero. • P\_B,i := B + tᵢ·G; Addr\_B,i := Base58Check( 0x00 ∥ H160( serP(P\_B,i) ) ).

Sender change (payer view): • sᵢ := int( SHA-256( Z ∥ H\_I ∥ "snd" ∥ LE32(i) ) ) mod n; if sᵢ = 0, bump ctr deterministically until non-zero. • P\_A,i := A + sᵢ·G; Addr\_A,i := Base58Check( 0x00 ∥ H160( serP(P\_A,i) ) ).

All Addr\_B,i are unique per note; all Addr\_A,i are unique per note; identity keys never appear on-chain.

Step 6 — Transaction construction (one per note) For each i:

Inputs: Sᵢ (reserved). Let m = |Sᵢ|, sum\_in = Σ value(Sᵢ). Outputs: primary (Addr\_B,i, aᵢ); optional change (Addr\_A,i, changeᵢ).

Fee/change arithmetic (deterministic at floor): • size1 ≈ 10 + 148·m + 34; fee1 := ceil(floor × size1). If sum\_in = aᵢ + fee1 → 1-output note (no change). • Else size2 ≈ 10 + 148·m + 68; fee2 := ceil(floor × size2). changeᵢ := sum\_in − aᵢ − fee2; require changeᵢ = 0 or changeᵢ ≥ dust. If 0 < changeᵢ < dust or changeᵢ ≤ 0 → reseat inputs.

Build legacy P2PKH transaction Tᵢ with deterministic input order; sign every input under SIGHASH\_ALL. Compute txidᵢ = dSHA256(serialisation). Record NoteMetaᵢ := {"i": i, "note\_id": H(H\_I ∥ LE32(i)), "invoice\_hash": H\_I, "addr": Addr\_B,i, "amount": aᵢ, "txid": txidᵢ, …}.

Result: seven independent, fully valid transactions {T₀…T₆}, each paying aᵢ to Addr\_B,i and (if present) change to Addr\_A,i; no input overlaps; no change overlaps within the invoice.

Step 7 — Either-side broadcast and pacing Broadcast authority is “either”. Seed S\_pace := H( Z ∥ H\_I ∥ "pace" ); compute a paced schedule within policy bounds (min/max spacing or bursts). Either party may submit any subset in any order; duplicate submission of identical bytes is benign. Confirmation depth d from policy defines settlement for each note independently.

Step 8 — Receipts and commitment (Merkle root) For i = 0…6, construct leaves: • preimageᵢ := "leaf" ∥ LE32(i) ∥ txidᵢ (32 bytes) ∥ amountᵢ (8-byte LE) ∥ addr\_payloadᵢ (0x00 ∥ 20-byte H160). • Lᵢ := SHA-256(preimageᵢ). Compute M := MerkleRoot(\[L₀, L₁, …, L₆]) with Bitcoin-style odd-leaf duplication. Persist a manifest:

{"invoice\_hash":"", "merkle\_root":"", "count":7, "entries":\[ {"i":0,"txid":""}, {"i":1,"txid":""}, {"i":2,"txid":""}, {"i":3,"txid":""}, {"i":4,"txid":""}, {"i":5,"txid":""}, {"i":6,"txid":""} ]}

Selective proof example (index 3 only; placeholders):

{"invoice\_hash":"", "merkle\_root":"", "leaf":{"i":3, "txid":"", "amount":\<a\_3>, "addr\_payload":"00<20-byte-h160-hex>"}, "path":\[ {"pos":"L","hash":""}, {"pos":"R","hash":""}, {"pos":"R","hash":""} ]}

A verifier recomputes L₃ and folds the path to M; no information about i ≠ 3 is revealed.

Step 9 — Reissue before broadcast (fee update example) Suppose, prior to any broadcast, a higher effective fee is desired for i = 5. Procedure:

1. Release reservation S₅; select a new disjoint S₅′; recompute fee at the new rate (≥ floor) (preserve Addr\_B,5, Addr\_A,5).
2. Build and sign T₅′; compute txid₅′.
3. Update NoteMeta₅:

{"i":5, "note\_id":"", "invoice\_hash":"", "version":2, "supersedes":"", "txid":"", "status":"signed"}

Only the latest version is eligible for broadcast; older txid₅ is not propagated.

Step 10 — Settlement and closure As notes are announced (by either party) and reach ≥ d confirmations to their respective Addr\_B,i, statuses become “confirmed”. Orphaned confirmations (reorg) trigger rebroadcast of the same raw bytes until reconfirmed. When all seven notes are confirmed (or when the invoice expires, cancelling any remaining queued notes), the invoice is closed. The audit trail consists of: the signed Policy and Invoice, H\_I, the deterministic split a\[·], the reservation table R, per-note NoteMeta with txidᵢ, the Merkle root M and manifest, and any reissue/cancel records. Every note in the slice has a disjoint input set Sᵢ, a unique recipient address Addr\_B,i, and—where applicable—a unique sender change address Addr\_A,i.

17. Testing and verification

17.1 Scope Verify correctness, determinism, and auditability for a complete invoice lifecycle. All tests operate on canonical UTF-8 JSON, secp256k1 over compressed keys, and the invoice scope {Z, Hᴵ}.

17.2 Required fixtures (deterministic) • Keys – Identity: Kₐ = (kₐ, Pₐ), Kᵦ = (kᵦ, Pᵦ). – Anchors: A = a·G, B = b·G (a ≠ kₐ, b ≠ kᵦ). • Policy (canonical JSON) with fields per §3.3 and valid ECDSA by kᵦ. • Invoice (canonical JSON) referencing H\_policy, valid ECDSA by kₐ; compute Hᴵ := H(canonical\_json(invoice)). • UTXO snapshots U₀ covering edge cases: – exact-match inputs; – “just-over” inputs that yield valid ≥ dust change; – coarse inputs forcing fan-out; – conflicting external spend (simulated) on a reserved outpoint on a reserved outpoint. • Parameters: v\_min, v\_max, fee\_rate\_floor, dust, expiry, d (confirm depth). • Golden PRNG seeds are implicit: derived from {Z, Hᴵ} per spec; no external RNG.

17.3 Property tests (must hold)

P1 — Sums and bounds (split). For hundreds of randomly chosen feasible triples (T, v\_min, v\_max): • Compute N\_min := ⌈T/v\_max⌉, N\_max := ⌊T/v\_min⌋; run the split (§5). • Assert: N\_min ≤ N ≤ N\_max; ∑ᵢ a\[i] = T; ∀i: v\_min ≤ a\[i] ≤ v\_max. • Re-run: identical a (before permutation) and identical permuted a.

P2 — Address agreement (recipient). For i = 0…N−1: • Sender computes Pᴮ,ᵢ, Addrᴮ,ᵢ from {Z, Hᴵ, B, i}. • Recipient computes kᴮ,ᵢ := (b + tᵢ) mod n; check kᴮ,ᵢ·G = Pᴮ,ᵢ. • Assert identical Addrᴮ,ᵢ at both ends.

P3 — Disjoint reservations. Build R from U₀ and a\[·] (§6). • Assert Sᵢ ∩ Sⱼ = ∅ for all i ≠ j; • Assert every input used appears in exactly one Sᵢ; • Assert deterministic reproducibility: rebuild(R) equals prior R byte-for-byte.

P4 — Non-overlapping change. For all notes that produce change: • Derive Addrᴬ,ᵢ (§7); assert Addrᴬ,ᵢ ≠ Addrᴬ,ⱼ for i ≠ j; • Assert no change UTXO from note i is admitted to the funding pool while invoice open. • Assert transactions meet dust and fee-floor rules; otherwise reseat inputs.

P5 — Reissue invariants. For a pre-broadcast fee update on index i: • Build Tᵢ, then reissue Tᵢ′ at higher fee. • Assert: i unchanged; NoteIDᵢ unchanged; Addrᴮ,ᵢ unchanged; Addrᴬ,ᵢ unchanged; txidᵢ ≠ txidᵢ′; prior bytes flagged superseded and never broadcast.

P6 — Receipts. Construct leaves Lᵢ and root M (§10). • For random S ⊂ {0…N−1}, produce single-leaf proofs and verify to M. • If multi-proof implemented, verify shared-sibling structure; otherwise independent proofs suffice. • Manifest { (i, txidᵢ) } + M must match recomputation.

P7 — Rebuild from logs. Given only signed logs (policy, invoice, reservations, NoteMeta, receipt manifest): • Verify signatures, canonical JSON, and hash-chain “prev\_hash”. • Recompute H\_policy, Hᴵ, Z. • Recompute a\[·], Addrᴮ,ᵢ, Addrᴬ,ᵢ; rebuild R from the logged U₀ snapshot and parameters; • Reconstruct or fetch raw Tᵢ, recompute txid\_i. • Check lifecycle: supersedes chain, statuses, timers consistent with §9–§11. All asserts must pass without external oracles.

17.4 Negative / failure tests (must reject)

N1 — Infeasible invoice. Choose T, v\_min, v\_max with ⌈T/v\_max⌉ > ⌊T/v\_min⌋ → split must fail early.

N2 — Canonicalisation break. Permute JSON key order or add whitespace → signatures fail; H\_policy/Hᴵ differ; reject.

N3 — Scope misuse. Attempt derivation without {Z, Hᴵ} or with wrong domain label (“recv” vs “snd”) → derived addresses mismatch; reject.

N4 — Zero-scalar forcing. Artificially search i such that tᵢ = 0 or sᵢ = 0; verify deterministic counter-bump produces non-zero and stable i mapping; log counter used.

N5 — Reservation overlap. Mutate R to place same outpoint in two Sᵢ → builder must detect and fail.

N6 — Dust/fee violations. Construct candidates with change ∈ (0, dust) or fee < fee\_floor × size → must reseat or fail.

N7 — Conflicting external spend. Mark a reserved input as spent elsewhere → builder must abort and rebuild R (§11).

N8 — Expiry and stale notes. Advance clock beyond invoice/policy expiry → queued notes become “cancelled”; no new broadcasts permitted.

17.5 Rebuild procedure from logs (normative)

Inputs: append-only, signed, canonical JSON records per §13 (policy\_record, invoice\_record, reservations, NoteMeta\*, receipt\_manifest, event records).

Procedure:

1. Verify hash-chain: for each record R\_k, check prev\_hash = H(bytes(R\_{k−1} \ sigtriplet)).
2. Verify signatures by the stated sig\_key over canonical bytes (without sigtriplet).
3. Extract policy → compute H\_policy; extract invoice → verify policy\_hash and compute H\_I.
4. Recompute Z from K\_A, K\_B.
5. Recompute split a\[·] from {Z, H\_I, v\_min, v\_max} (and permutation).
6. Rebuild R deterministically from logged U₀ snapshot and parameters; compare to logged reservations.
7. For each i: a. Derive Addr\_B,i and Addr\_A,i; compare to NoteMeta (if present). b. Reconstruct tx template from NoteMeta.inputs/outputs or fetch raw tx; recompute txid\_i. c. Check lifecycle: supersedes chain, statuses, timers consistent with §9–§11.
8. Recompute leaves L\_i and Merkle root M; match receipt\_manifest.merkle\_root.
9. Emit a verification report listing all assertions; fail on first discrepancy.

17.6 Golden vectors and cross-impl determinism Publish at least two golden invoices (small and large N) with: policy, invoice, H\_policy, H\_I, Z (hex), a\[·], R (outpoints), (Addr\_B,i, Addr\_A,i), txidᵢ, M, and selective proofs. Independent implementations must reproduce these byte-for-byte.

17.7 Fuzzing and coverage (recommended) • Fuzz (T, v\_min, v\_max) under feasibility; run P1–P6. • Fuzz U₀ distributions (heavy-tail, uniform, clustered). • Randomly trigger reissue, conflict, expiry. • Target ≥ 95 % branch coverage across split, reservation, fee/change, and receipt code paths.

17.8 CI gating (pass/fail) A build is releasable only if: P1–P7 pass; N1–N8 reject as specified; golden vectors match; rebuild-from-logs produces identical outputs including R, addresses, txids, and M.


# P Labels: Allowing Future Wallet Action Label Permission Schemes

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The [BRC-65](/wallet/0065) and [BRC-100](/wallet/0100) label system enables applications to organize and query actions. However, labels are also used in permission checks (e.g. for `listActions`), which limits extensibility for module-defined labeling and querying schemes. This proposal reserves a label prefix to allow wallets to delegate label permissioning and label-specific behavior to installed modules, while preserving the core principle that labels remain organizational metadata.

## Motivation

Labels are intentionally non-behavioral and used for categorization and filtering. But in practice, permissions for creating actions with labels and querying actions by labels can be enforced by wallets. Modules that define their own labeling schemes need a way to:

* Distinguish module-specific labels from ordinary labels.
* Apply module-specific permission rules for label creation and querying.
* Optionally transform query results when a module label is used.

To enable this, we reserve a label namespace analogous to the reserved protocol and basket namespaces in [BRC-98](/wallet/0098) and [BRC-99](/wallet/0099).

## Specification

### Reserved Label Prefix

To accommodate future label permission schemes, wallets must reject any operation requests involving labels beginning with `p` (a lowercase "p" followed by a space), unless the wallet explicitly supports the corresponding module ID.

### Future Module Identifiers

Module-defined label schemes must follow this format:

* Module IDs cannot contain spaces.
* Labels must start with `p` , followed by the module ID, a space, and the rest of the label.

#### Example Format

A label like `p invoicing invoice 2026-02-02` could represent a module-specific label, where:

* `p` designates a module-defined label scheme.
* `invoicing` identifies the module.
* `invoice 2026-02-02` is the module-defined label payload.

#### Parsing and Rules

Wallets must differentiate between standard and module-defined labels by recognizing the `p` prefix followed by a distinct, space-free module ID:

* The label must begin with exactly `p` .
* The module ID must be at least 1 character and contain no spaces.
* A single space must immediately follow the module ID to separate it from the module-defined label payload.
* The module-defined label payload must be at least 1 character.
* All labels must still comply with the general label constraints in [BRC-100](/wallet/0100) (e.g. length limits).

### Module Resolution and Permissions

There is no global registry. A wallet determines which modules it supports based on its installed or integrated modules and their declared module IDs.

When a label follows the `p <moduleId> <label>` structure:

* If the wallet does not support the module ID, it must reject the operation.
* If the wallet supports the module ID, it must defer to the module for permission checks involving that label.

Modules may implement custom permission rules for label usage that override or bypass the wallet's default label permission checks.

### Module Influence on Queries

When a module-defined label is supplied to `listActions` or any other label-based query:

* The module may apply additional permission checks on the query inputs.
* The module may transform or filter the resulting action set before it is returned.

This behavior is optional per module and does not change the rule that labels remain organizational metadata; it only affects permissioning and query handling.

## Reserved Structure

To maintain clarity and prevent conflicts:

* Labels beginning with `p` must be reserved for module-defined schemes.
* Wallets must reject operations involving such labels unless they explicitly support the module ID.
* A space must immediately follow the module ID to separate it from other elements.

## Extensibility Beyond Current Paradigms

This specification allows modules to define new label permission schemes beyond current wallet permission systems, while preserving the fundamental categorization-only role of labels. It enables future label semantics to evolve without disrupting existing applications or introducing parsing ambiguities.

## Conclusion

By reserving labels starting with `p` and delegating permissioning to installed modules, this specification ensures forward compatibility and extensibility for action labeling and querying within the BSV wallet ecosystem.


# Balance Baskets: Backwards-Compatible Basket Balance Queries

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines a backwards-compatible extension to [BRC-100](/wallet/0100) that allows applications to query a basket's balance using a `balance` prefix in `listOutputs`. The wallet returns no outputs and re-purposes `totalOutputs` to report the sum of satoshis contained in all outputs in the specified basket.

## Motivation

Applications often need a quick way to retrieve the total value of a basket without enumerating all outputs. Extending `listOutputs` with a special basket prefix preserves API compatibility while enabling efficient balance queries.

## Specification

### Balance Basket Prefix

A balance query is requested by using a basket name that begins with `balance` (a lowercase "balance" followed by a space). The remainder of the basket string is treated as the target basket name.

Example:

* `listOutputs({ basket: "balance savings" })`

### Required Behavior

When `listOutputs` is called with a basket name beginning with `balance` :

* The wallet must return **no outputs** in the results array.
* The wallet must re-purpose `totalOutputs` to contain the sum (in satoshis) of **all** outputs currently in the target basket that match any provided tag filters.
* The wallet must still enforce all normal permission checks for access to the target basket.
* The wallet must apply any basket resolution, ownership, internalization, and tag-filtering rules as if the caller had requested outputs from the target basket directly.
* No regard is given to the `limit` or `offset` parameters, since this is a sum of total value.

This behavior is backwards compatible because existing callers will ignore the empty outputs array and read `totalOutputs` as the count of outputs; with this extension, `totalOutputs` is intentionally redefined **only** when the `balance` prefix is used.

### BRC-99 Module Baskets

For module-defined baskets in [BRC-99](/wallet/0099):

* `balance p <module> <basket>` is **never** allowed and must be rejected.
* `p <module> balance <basket>` **may** be implemented at the module's discretion.

If a module chooses to implement balance semantics, it may interpret `balance <basket>` according to its own rules, including permissioning and aggregation behavior. If a module does not implement this, the wallet must reject the request as unsupported.

### Validation Rules

* The `balance` prefix must be lowercase and followed by a single space.
* The target basket name that follows must conform to the basket naming rules in [BRC-100](/wallet/0100) and [BRC-99](/wallet/0099) where applicable.
* If the target basket does not exist or is not accessible, the wallet must return the appropriate error according to [BRC-100](/wallet/0100).

## Conclusion

By reserving a `balance` prefix within basket queries, this specification enables efficient balance retrieval while remaining compatible with existing `listOutputs` behavior and the broader wallet permission model.


# Time Labels: Backwards-Compatible Action Timestamp Filters for List Actions

Brayden Langley (<brayden@projectbabbage.com>)

## Abstract

This specification defines a backwards-compatible extension to [BRC-100](/wallet/0100) that allows applications to filter `listActions` results by wallet-tracked action timestamps using reserved query labels. The response format remains unchanged.

## Motivation

Wallet applications often need time-windowed action data for analytics and user insights, such as:

* "What app did I spend the most on this week?"
* "How did my balance change over time?"
* "Show spending for today in the user's local timezone."

Wallet-toolbox implementations already track action timestamps. This specification standardizes how applications request timestamp filtering without changing the `listActions` API surface.

## Specification

### Reserved Query Label Namespace

This specification reserves only the following exact `listActions` query-control label forms:

* `action time from <unixMillis>`
* `action time to <unixMillis>`

Other labels (including labels that begin with `time` or `action time` ) remain valid ordinary labels unless they exactly match one of the reserved query-control forms above.

### Timestamp Filter Labels

Timestamp filtering is requested by including one or both reserved labels in `listActions`:

* `action time from <unixMillis>`
* `action time to <unixMillis>`

Where `<unixMillis>` is an unsigned decimal integer representing Unix time in milliseconds since 1-Jan-1970 UTC.

Examples:

* `listActions({ labels: ["expenses", "action time from 1738368000000", "action time to 1740787200000"] })`
* `listActions({ labels: ["action time from 1738368000000"] })`

### Request vs Response Label Forms

This specification defines two distinct label roles:

* **Request control labels**: `action time from <unixMillis>` and `action time to <unixMillis>` (input to `listActions` filtering).
* **Response timestamp label**: `action time <unixMillis>` (output annotation in returned action labels).

The wallet must not interpret `action time <unixMillis>` as a timestamp-filter control label in requests.

### Required Behavior

When `listActions` includes at least one timestamp filter label:

* The wallet must partition `args.labels` into:
  1. Timestamp control labels (`action time from <unixMillis>`, `action time to <unixMillis>`).
  2. Remaining ordinary labels (all other labels, such as `awesomeActions`).
* Timestamp control labels must not be matched as ordinary action labels.
* The wallet must apply timestamp filtering against its internally tracked action timestamp for each action.
* `action time from` is an inclusive lower bound (`timestamp >= from`).
* `action time to` is an exclusive upper bound (`timestamp < to`).
* The wallet must apply filtering in this order:
  1. Apply timestamp filtering first.
  2. Apply ordinary label filtering rules from [BRC-100](/wallet/0100) to the remaining (non-time-filter) labels.
* The wallet must still enforce normal permission checks; timestamp filters must not bypass or weaken permissions.
* The wallet must return the normal `listActions` response shape. No new response fields are introduced.
* `totalActions`, `limit`, and `offset` must be computed against the fully filtered result set.

If no non-time-filter labels remain after parsing, the wallet must treat the request as "all permitted actions within the specified time range."

Example:

* `labels: ["action time from <unixMillis>", "action time to <unixMillis>", "awesomeActions"]` must return actions with the `awesomeActions` label that also fall within the specified time range.

### Returned Timestamp Labels

When `listActions` uses timestamp filter labels and `includeLabels` is `true`, each returned action must include exactly one timestamp label in its `labels` array:

* `action time <unixMillis>`

Where `<unixMillis>` is the same canonical wallet-tracked timestamp value used for filtering that action.

Additional rules:

* The wallet must include this label only when `includeLabels` is `true`.
* If the action already contains the same `action time <unixMillis>` label, the wallet must avoid duplicating it.
* The `action time <unixMillis>` label is response-derived query metadata and must not be persisted as a normal action label.

### Timestamp Source

Wallets must use their canonical per-action wallet-tracked timestamp (the same timestamp source used by the wallet for action chronology and indexing). If an action has no timestamp in that source, the action must be excluded whenever timestamp filtering is requested.

When both a creation timestamp and an update timestamp are available for an action record, wallets must use the creation timestamp for `action time from` / `action time to` filtering and for the derived `action time <unixMillis>` response label. Update timestamps must not affect time-bucket membership for this specification.

### Validation Rules

* The request control prefixes `action time from` and `action time to` must be lowercase and followed by a single space.
* The response timestamp label prefix must be exactly `action time` (lowercase, single trailing space before the value).
* Each request may include at most one `action time from` label and at most one `action time to` label.
* Timestamp values must be valid unsigned base-10 integers representing Unix milliseconds.
* If both bounds are present and `from >= to`, the wallet must return an error.
* If a label uses `action time from` or `action time to` but has an invalid timestamp value, the wallet must return an error.
* If a request label begins with `action time` but does not match either reserved request control form, the wallet must treat it as an ordinary label (not as a timestamp filter control label).
* All labels must still comply with general label constraints in [BRC-100](/wallet/0100) and module label rules in [BRC-111](/wallet/0111).

### Timezone Guidance

Timezone-specific views (day/week/month in a user locale) are achieved by computing local window boundaries in the application, converting those boundaries to Unix milliseconds UTC, and supplying them via `action time from` and `action time to`.

## Conclusion

By reserving `action time from` and `action time to` labels for `listActions`, this specification enables interoperable timestamp-range querying while preserving backwards compatibility and existing wallet permission models.


# Wallet Permissions and Counterparty Trust

* Stephen Thomson (<stephen@projectbabbage.com>)
* Brayden Langley (<brayden@projectbabbage.com>)
* Jackie Lu (<jackie@projectbabbage.com>)

## Abstract

This document fully specifies how BRC-100 wallets handle application permission requests.

Its goals are to:

* Define, without ambiguity, how permissions are declared, requested, and evaluated
* Specify full permission lifecycle behavior: grant, deny, renew, revoke, and persistence
* Distinguish **group permissions** from **counterparty trust (PACT)** and define how both are enforced together
* Provide an authoritative, implementation-agnostic reference for compliant wallet behavior

This document is **normative**: it defines complete expected behavior for compliant wallets and applications, independent of UI or implementation language.

***

## Motivation

Wallets act as security boundaries between users and applications.\
Applications must not be able to:

* Spend funds
* Access private data
* Interact with protocols
* Access certificates

...without **explicit user authorization**.

The permissions system ensures:

* Least-privilege access
* Clear user consent
* Deterministic enforcement
* Predictable developer behavior

## Normative Language

The key words **MUST**, **MUST NOT**, **SHOULD**, and **MAY** in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.

***

## Specification

### 3.0 Terminology

The following terms are used consistently throughout this document:

* **Wallet**: Software responsible for enforcing permissions and managing user assets and data.
* **Application (Originator)**: Software acting as an originator that requests permissions from the wallet. Identified by its domain (e.g. `example.com`).
* **User**: The human owner of the wallet who grants or denies permissions.
* **Counterparty**: An external entity (identified by a compressed public key) interacting with the user through an application via Level 2 protocols.
* **Permission Grant**: A persisted authorization allowing an originator to perform a specific action within defined scope.
* **Permission Token**: An on-chain PushDrop output stored in an admin basket that represents a persisted permission grant. Tokens are encrypted so only the wallet owner can read them. See Section 7.
* **Security Level**: An integer (0, 1, or 2) that classifies protocol access. **Level 0** is open (no permission required). **Level 1** protocols are application-scoped. **Level 2** protocols are counterparty-specific (a distinct external entity is involved). The [BRC-43](https://brc.dev/43) specification defines how these are applied and used within cryptographic operations.
* **Manifest**: A JSON file served at `https://{domain}/manifest.json` providing application metadata. See Section 3.2.
* **Trusted Certifier**: An entity the user has chosen to trust, identified by a compressed public key and assigned a trust score (1-10). Used for registry resolution and identity verification.

For the purposes of this document, **group permissions** refer to permissions granted by a user to an application originator via the wallet's standard permission system. These are distinct from **PACT** (counterparty trust), which governs trust relationships with external entities (see Section 5).

### 3.1 Originator

An **originator** represents the identity of an application requesting permissions, derived from the application's domain.

Permissions are always scoped to an originator. Granting permission to one originator does **not** imply permission for another.

The wallet resolves originator identity from the request context (typically HTTP headers such as `Origin` or `Originator`) when evaluating permission scope.

**Security Note:** The user's wallet, web browser and computer system are expected to coordinate and confirm the authenticity of originators, thereby preventing request forgery. The steps taken to confirm originator authenticity vary depending on wallet deployment context, but this specification assumes wallets will prevent forged requests via IPC, local kernel/substrate-specific authentication, or other means.

***

### 3.2 Manifest

Each application SHOULD serve a **manifest** file at `https://{domain}/manifest.json`.

The manifest follows the [W3C Web App Manifest](https://www.w3.org/TR/appmanifest/) standard for application metadata (name, icons, display preferences), and extends it with a `metanet` namespace for wallet permission declarations. This keeps the manifest interoperable with standard web tooling while adding Metanet-specific capabilities.

The manifest serves three purposes:

1. **Application metadata** - Standard W3C fields (`name`, `short_name`, `description`, `icons`) for display in permission prompts and the wallet dashboard.
2. **Group permission declarations** - Under `metanet.groupPermissions`, applications declare the permissions they need. When grouped permission seeking is enabled, the wallet fetches these declarations and presents them to the user as a single grouped prompt on first interaction, rather than prompting one-by-one as each capability is used.
3. **Counterparty permission declarations** - Under `metanet.counterpartyPermissions`, applications declare the Level 2 protocols they require for peer interaction in that app. When a new (untrusted) counterparty is encountered, the wallet uses this declaration to request trust for those protocols before allowing that peer interaction to proceed.

#### Manifest Structure

```json
{
  "name": "My Application",
  "short_name": "MyApp",
  "description": "A web application with Metanet permissions.",
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    }
  ],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Permissions needed by this application",
      "spendingAuthorization": {
        "amount": 10000,
        "description": "Monthly spending limit for transactions"
      },
      "protocolPermissions": [
        {
          "protocolID": [2, "myprotocol"],
          "counterparty": "<counterpartyPublicKey>",
          "description": "Peer messaging"
        }
      ],
      "basketAccess": [
        {
          "basket": "my-tokens",
          "description": "Token storage"
        }
      ],
      "certificateAccess": [
        {
          "type": "<certTypeBase64>",
          "verifierPublicKey": "02...",
          "fields": ["name", "email"],
          "description": "Identity verification"
        }
      ]
    },
    "counterpartyPermissions": {
      "description": "Peer trust required by this application",
      "protocols": [
        {
          "protocolName": "myprotocol",
          "description": "Peer-to-peer messaging"
        }
      ]
    }
  }
}
```

All fields under `metanet` are optional. New manifests SHOULD include `schemaVersion`. An application that declares no `metanet.groupPermissions` will simply be prompted individually at runtime for each capability it uses. An application that declares no `metanet.counterpartyPermissions` will never trigger a PACT prompt - Level 2 protocol requests will fall through to individual or grouped permission prompts instead.

#### Schema Version

The `metanet.schemaVersion` field identifies the version of the Metanet permission schema. New manifests SHOULD provide it, and the current version is `1`.

For interoperability with current wallet-toolbox behavior:

* Wallets SHOULD tolerate manifests that omit `schemaVersion`.
* Wallets SHOULD continue to support the legacy `babbage` namespace as described in the Backwards Compatibility section.
* Wallets SHOULD handle unknown or unsupported schema versions gracefully (for example by ignoring the unsupported fields or warning the user).

#### Group Permissions Declaration

Grouped permissions let an application declare multiple permissions required up front so the wallet can request them in one consolidated prompt.\
Without grouped declarations, permissions are requested individually at runtime when each protected operation is first attempted.

The `metanet.groupPermissions` object follows the `GroupedPermissions` interface ([BRC-73](https://brc.dev/73)):

| Field                   | Type                      | Description                                                                                                                               |
| ----------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `description`           | string?                   | Human-readable description of the permission group                                                                                        |
| `spendingAuthorization` | `{ amount, description }` | Monthly spending limit in satoshis                                                                                                        |
| `protocolPermissions`   | array                     | Protocol access requests (Level 1 or 2). Level 2 entries MUST include a specific `counterparty`; Level 1 entries MAY omit `counterparty`. |
| `basketAccess`          | array                     | Basket access requests, each with a `basket` name                                                                                         |
| `certificateAccess`     | array                     | Certificate access requests with `type`, `verifierPublicKey`, `fields`                                                                    |

When the wallet encounters a permission-requiring operation, it MUST:

1. Check whether a permission token already exists for this operation. If so, allow the operation. Otherwise, proceed with step 2.
2. Fetch the originator's manifest.
3. Check whether the current operation is covered by the manifest's `groupPermissions`. If it is not, trigger a one-off prompt for the operation. If it is, proceed with step 4.
4. Filter out permissions already granted (by looking up existing on-chain tokens). Remove them from the list before showing the user a prompt.
5. Present all remaining permissions as a single grouped prompt for the user's approval.
6. After the user responds, check whether the specific triggering operation is now satisfied. If yes, allow it to proceed with the operation. If no, trigger an individual one-off prompt.

#### Counterparty Permissions Declaration

Counterparty permissions let an app declare all the Level 2 protocols it needs when interacting with an untrusted counterparty. The wallet can then request trust for that person in one bundled prompt, instead of showing repeated one-off Level 2 prompts as each operation is attempted.

The `metanet.counterpartyPermissions` object declares PACT requirements - the Level 2 protocol set the application requires to interact with a peer in that app:

| Field         | Type    | Description                                                                                                     |
| ------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `description` | string? | Human-readable description                                                                                      |
| `protocols`   | array   | Array of `{ protocolName, description }` - required peer-interaction protocols, interpreted as **Level 2 only** |

The wallet MUST interpret `counterpartyPermissions` entries as Level 2 protocol declarations.

For interoperability with current wallet-toolbox behavior, a declaration MAY identify the protocol either by:

* a non-empty `protocolName`, or
* a `protocolID` equal to `[2, "<name>"]`.

Entries that do not resolve to a Level 2 protocol declaration MUST be ignored. These declarations define required Level 2 protocols, while the concrete counterparty is supplied at request time. Effective permission scope remains per-originator + per-counterparty.

Including `counterpartyPermissions` tells the wallet: "when this app encounters a new (untrusted) counterparty, request trust for this declared protocol set before allowing peer interaction to proceed." Without it, each Level 2 protocol permission is requested individually as it's used.

#### Manifest Examples

**Example 1: Simple app with spending only**

An application that only needs to spend satoshis (e.g. a tipping service):

```json
{
  "name": "Tip Jar",
  "short_name": "TipJar",
  "description": "Send tips to content creators.",
  "start_url": ".",
  "display": "standalone",
  "icons": [{ "src": "icon.png", "sizes": "192x192", "type": "image/png" }],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Spending permission for sending tips",
      "spendingAuthorization": {
        "amount": 50000,
        "description": "Monthly tip budget (50,000 satoshis)"
      }
    }
  }
}
```

**Example 2: Data storage app (baskets, no counterparties)**

An application that stores encrypted notes in a basket using a Level 1 protocol:

```json
{
  "name": "Secure Notes",
  "short_name": "Notes",
  "description": "Encrypted personal notes on the blockchain.",
  "start_url": ".",
  "display": "standalone",
  "icons": [{ "src": "icon.png", "sizes": "192x192", "type": "image/png" }],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Storage and encryption permissions",
      "protocolPermissions": [
        {
          "protocolID": [1, "secure-notes"],
          "description": "Encrypt and decrypt your notes"
        }
      ],
      "basketAccess": [
        {
          "basket": "encrypted-notes",
          "description": "Store your encrypted notes"
        }
      ]
    }
  }
}
```

**Example 3: Peer-to-peer messaging (with counterparty permissions)**

An application where users exchange messages with specific counterparties. This declares `counterpartyPermissions` so the wallet will prompt the user to establish trust (PACT) when a new counterparty is encountered:

```json
{
  "name": "Peer Messenger",
  "short_name": "PeerMsg",
  "description": "End-to-end encrypted messaging.",
  "start_url": ".",
  "display": "standalone",
  "icons": [{ "src": "icon.png", "sizes": "192x192", "type": "image/png" }],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Messaging permissions",
      "protocolPermissions": [],
      "basketAccess": [
        {
          "basket": "message-inbox",
          "description": "Store received messages"
        }
      ],
      "spendingAuthorization": {
        "amount": 5000,
        "description": "Transaction fees for sending messages"
      }
    },
    "counterpartyPermissions": {
      "description": "Trust required to communicate with a peer",
      "protocols": [
        {
          "protocolName": "peer-messaging",
          "description": "Allow this person to send you encrypted messages"
        },
        {
          "protocolName": "peer-presence",
          "description": "Share your online status with this person"
        }
      ]
    }
  }
}
```

**Example 4: Identity verification app (certificates)**

An application that verifies user identity by requesting specific certificate fields:

```json
{
  "name": "KYC Portal",
  "short_name": "KYC",
  "description": "Identity verification for regulated services.",
  "start_url": ".",
  "display": "standalone",
  "icons": [{ "src": "icon.png", "sizes": "192x192", "type": "image/png" }],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Identity verification permissions",
      "certificateAccess": [
        {
          "type": "AGbsvkGHSi78y1FR6JL0Ig==",
          "verifierPublicKey": "0294c479f762f3571c4c36f6a75f04995ddcf200777b704131ca71dab5b0e19bfb",
          "fields": ["firstName", "lastName", "dateOfBirth"],
          "description": "Verify your legal name and date of birth"
        },
        {
          "type": "AGbsvkGHSi78y1FR6JL0Ig==",
          "verifierPublicKey": "0294c479f762f3571c4c36f6a75f04995ddcf200777b704131ca71dab5b0e19bfb",
          "fields": ["country", "address"],
          "description": "Verify your address for compliance"
        }
      ]
    }
  }
}
```

**Example 5: Full-featured application (all permission types + PACT)**

```json
{
  "name": "Decentralized Marketplace",
  "short_name": "DeMarket",
  "description": "Buy and sell goods with on-chain escrow.",
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#1a1a2e",
  "background_color": "#ffffff",
  "icons": [
    { "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "icon-512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "metanet": {
    "schemaVersion": 1,
    "groupPermissions": {
      "description": "Marketplace permissions",
      "spendingAuthorization": {
        "amount": 1000000,
        "description": "Monthly purchase and escrow budget"
      },
      "protocolPermissions": [
        {
          "protocolID": [1, "marketplace-listings"],
          "description": "Create and manage your product listings"
        },
        {
          "protocolID": [2, "escrow-negotiation"],
          "counterparty": "02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
          "description": "Negotiate escrow terms with buyers/sellers"
        },
        {
          "protocolID": [2, "trade-messaging"],
          "counterparty": "02bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
          "description": "Exchange messages during a trade"
        }
      ],
      "basketAccess": [
        {
          "basket": "marketplace-listings",
          "description": "Your active product listings"
        },
        {
          "basket": "escrow-contracts",
          "description": "Active escrow agreements"
        },
        {
          "basket": "trade-receipts",
          "description": "Completed trade receipts"
        }
      ],
      "certificateAccess": [
        {
          "type": "AGbsvkGHSi78y1FR6JL0Ig==",
          "verifierPublicKey": "0294c479f762f3571c4c36f6a75f04995ddcf200777b704131ca71dab5b0e19bfb",
          "fields": ["displayName"],
          "description": "Display your verified name to trade partners"
        }
      ]
    },
    "counterpartyPermissions": {
      "description": "Trust required to trade with a peer",
      "protocols": [
        {
          "protocolName": "escrow-negotiation",
          "description": "Negotiate escrow terms with this trader"
        },
        {
          "protocolName": "trade-messaging",
          "description": "Exchange messages with this trader"
        }
      ]
    }
  }
}
```

**Example 6: Minimal manifest (no Metanet permissions)**

An application that does not declare any permissions upfront. The wallet will prompt individually as each capability is used:

```json
{
  "name": "Simple App",
  "short_name": "Simple",
  "description": "A basic application.",
  "start_url": ".",
  "display": "standalone",
  "icons": [{ "src": "favicon.ico", "sizes": "64x64", "type": "image/x-icon" }]
}
```

***

### 3.3 Permission Categories

The system defines four primary permission types:

| # | Type                    | Protocol Acronym                                | Scope Key                            | Counterparty-Aware |
| - | ----------------------- | ----------------------------------------------- | ------------------------------------ | ------------------ |
| 1 | Protocol Permissions    | DPACP (Domain Protocol Access Control Protocol) | `protocolID` (security level + name) | Yes (Level 2)      |
| 2 | Spending Permissions    | DSAP (Domain Spending Authorization Protocol)   | Satoshi amount                       | No                 |
| 3 | Basket Permissions      | DBAP (Domain Basket Access Protocol)            | Basket name                          | No                 |
| 4 | Certificate Permissions | DCAP (Domain Certificate Access Protocol)       | Certificate type + verifier + fields | No                 |

Each permission type has distinct scope, grant options, and prompting behavior. All four types support a **renewal** flag indicating the permission is being re-requested after a previous grant has expired (when finite expiry is used) or been revoked.

### 3.4 Deterministic Permission Evaluation

To remove ambiguity across implementations:

* Wallets MUST make allow/deny decisions from canonical permission state: valid, unspent, unexpired on-chain permission tokens plus explicit one-time ephemeral grants for the current request.
* In-memory caches are performance optimizations only and MUST NOT be treated as authoritative permission state.
* If cache state and on-chain token state disagree, the wallet MUST use on-chain token state.
* Revocation, renewal, or token spend events that change permission state MUST invalidate affected cache entries immediately.
* Internal function names, callback names, and class names in this document are illustrative unless explicitly defined as protocol surface.

***

## 4. Permission Types

### 4.1 Protocol Permissions

Protocol permissions control an application's ability to interact with named protocols for cryptographic and data operations.

#### Scope

A protocol permission is scoped by two fields:

* **`protocolID`**: A tuple of `[securityLevel, protocolName]`
  * `securityLevel` - integer, `1` or `2`
  * `protocolName` - string identifier for the protocol
* **`counterparty`**: `'self'`, `'anyone'`, or a compressed public key identifying the other party. Empty string is invalid.
  * For **Level 2**, `counterparty` is REQUIRED and permission scope is per-originator + per-counterparty.
  * For **Level 1**, any `counterparty` is allowed, and it SHOULD be omitted in manifest declarations.

#### Security Levels

* **Level 0**: Open usage - no permission check is required.
* **Level 1**: Any counterparty is allowed. No counterparty prompt is required. Once granted for an originator, the permission applies across all counterparties for that originator. The wallet SHOULD present the scope as application-only (e.g. "only with this app").
* **Level 2**: Only this specific external counterparty is permitted. The wallet MUST identify the counterparty to the user by displaying the counterparty's compressed public key, and a PACT relationship may be created (see Section 5). The wallet SHOULD present the scope as application-and-counterparty (e.g. "only with this app and counterparty").

#### Grant Options

Protocol permission grants are binary (grant or deny). There are no amount limits or ephemeral flags. Wallets SHOULD issue protocol grants as non-expiring (`expiry = 0`). If an implementation supports finite expiry values, it SHOULD require explicit user or administrator intent, because routine expiry creates repeated renewal prompts and degraded UX.

***

### 4.2 Spending Permissions

Spending permissions control an application's ability to spend funds (satoshis) on behalf of the user. These are **high risk** and require explicit, informed user consent.

#### Scope

A spending permission is scoped by:

* **`satoshis`**: The amount being requested for the current transaction.
* **`lineItems`** (optional): An itemized breakdown, where each item has `{ satoshis, description }`.

#### Grant Options

Spending permissions support three grant outcomes:

* **One-time grant**: Approves only the current spend request and does not create a standing authorization.
* **Standing authorization**: Approves spending up to an authorized monthly limit for the originator.
* **Denial**: Rejects the request.

#### Prompt Presentation

The spending prompt SHOULD display an itemized breakdown when `lineItems` are provided. Each line item shows a description and satoshi amount. The wallet SHOULD include the network fee as a separate line item and display a total. This allows the user to understand exactly what they are paying for before approving.

Wallets MUST treat application-provided description text as untrusted. Wallets MUST compute and display authoritative satoshi amounts from structured numeric fields (such as `spending.satoshis`, `lineItems[].satoshis`, and `spendingAuthorization.amount`) rather than relying on free-form description text.

#### Monthly Tracking

## Spending authorization tokens do **not** have a time-based expiry (expiry is always `0`). Instead, spending is tracked on a **calendar month** basis. The wallet calculates how much the originator has spent in the current month and compares it against the authorized limit.

### 4.3 Basket Permissions

Basket permissions control an application's ability to interact with named data baskets - structured, wallet-managed collections of spendable outputs.

#### Scope

A basket permission is scoped by:

* **`basket`**: The basket identifier string.

Basket permissions are granted per basket name. Granting access to one basket does **not** imply access to any other.

Basket operations commonly include insertion, listing, and removal. Wallets MAY apply distinct prompt policy per operation type.

#### Grant Options

Basket permission grants are binary (grant or deny). There are no amount limits or ephemeral flags.

***

### 4.4 Certificate Permissions

Certificate permissions control an application's ability to interact with identity certificates - structured attestations issued by certifiers.

#### Scope

A certificate permission is scoped by:

* **`type`** (`certType`): The certificate type identifier string.
* **`verifierPublicKey`** (`verifier`): The compressed public key of the entity that will verify/receive the certificate data.
* **`fields`**: A record of specific certificate fields being requested. This enables **selective disclosure** - the application requests only the fields it needs, not the entire certificate.

#### Operation Scope

Certificate operations use two permission mechanisms:

* **Disclosure operations** use certificate-access checks (DCAP) with `certType`, verifier, and field-level scope.
* **Acquisition, listing, and relinquishment** use protocol-level checks (DPACP), where certificate type is represented in protocol scope.

#### Field Matching

When looking up an existing DCAP token for a disclosure request, field matching uses **subset semantics**: the requested fields MUST be a subset of the token's granted fields. A token granting `["name", "email"]` satisfies a request for `["name"]`, but a token granting `["name"]` does NOT satisfy a request for `["name", "email"]`.

Both `certType` and `verifierPublicKey` MUST match exactly (strict equality). There is no partial or fuzzy matching.

Note: The grouped manifest inclusion check (Section 6.3) uses **exact set equality** for fields - both the manifest entry and the request must have the same field set. This is stricter than the token lookup's subset semantics.

#### Grant Options

Certificate permission grants are binary (grant or deny), but the wallet presents field-level granularity - the user sees which specific fields are being requested.

***

## 5. Counterparty Trust (PACT)

### 5.1 Definition

PACT (**Protocol Agnostic Counterparty Trust**) is a trust mechanism that governs whether the wallet user trusts a specific external counterparty for Level 2 protocol interactions within a specific application.

When an application involves peer-to-peer interactions - encrypted messaging, collaborative editing, trading, or any protocol where the user's cryptographic keys are used in relation to another person - the wallet needs to answer a question that group permissions alone cannot: **"Do I trust this particular person, through this particular app?"**

PACT provides that answer. It is a separate trust layer from group permissions, and it is established per originator + counterparty pair. A PACT granted for a counterparty through one application does **not** carry over to another application.

Applications declare their PACT requirements in the manifest's `metanet.counterpartyPermissions` section. This tells the wallet which Level 2 protocols are part of the peer interaction, so they can all be presented in a single trust prompt when a new counterparty is encountered - rather than prompting one protocol at a time.

***

### 5.2 Purpose

PACT addresses a fundamental gap in the permission model: group permissions answer "do I trust this app?", but they do not answer "do I trust this person through this app?" For applications with peer interactions, both questions must be answered.

PACT exists to:

* **Gate counterparty-specific interactions** - ensure a counterparty is explicitly trusted before Level 2 protocol permissions are granted for them
* **Separate app trust from peer trust** - a user may trust the app but not every counterparty the app introduces
* **Bundle peer protocol grants** - present all Level 2 protocols declared in `metanet.counterpartyPermissions` for a given counterparty in a single prompt, rather than prompting one-by-one as each protocol is used
* **Scope trust narrowly** - PACT is scoped to the originator + counterparty pair, preventing one app's trust decisions from affecting another app

***

### 5.3 Scope: Level 2 Only

PACT applies **exclusively** to Level 2 protocols. Level 1 protocols do not involve an external counterparty and therefore do not require PACT. Wallets SHOULD present this to the user as: "This person (counterparty) wants to interact with you through this app (originator) using these protocols."

***

### 5.4 Separation from Group Permissions

Key distinctions:

| Aspect               | Group Permissions               | PACT                                        |
| -------------------- | ------------------------------- | ------------------------------------------- |
| Governs              | Application -> Wallet           | Counterparty -> User (via application)      |
| Scoped to            | Originator domain               | Originator domain + Counterparty public key |
| Applies to           | All four permission types       | Level 2 protocols only                      |
| Prompted when        | App attempts a wallet operation | App involves an untrusted counterparty      |
| Declared in manifest | `metanet.groupPermissions`      | `metanet.counterpartyPermissions`           |

An operation MAY require both:

* A granted group permission (e.g. protocol access for the originator)
* An established PACT (trust for the specific counterparty)

***

### 5.5 Manifest-Driven Routing

When a permission request arrives, the wallet checks the originator's manifest and **routes the request based on its type**. The reference flow in Section 6.2 uses a deterministic strategy order (PACT -> peer-grouped -> grouped -> individual):

* If the request is a **Level 2 protocol** with a specific counterparty, and the manifest declares `counterpartyPermissions` covering that protocol -> the wallet checks for an established PACT and, if missing, triggers a **PACT prompt**.
* If the request is covered by the manifest's `groupPermissions` (any of the four permission types) -> the wallet filters out already-granted permissions and triggers a **grouped permission prompt**.
* If the request is a Level 2 protocol that appears in `groupPermissions` and shares a counterparty with other Level 2 protocols -> the wallet MAY present a **peer-grouped prompt** (all Level 2 protocols for that peer at once).
* If the request is not covered by any manifest declaration, or if the manifest is unavailable -> the wallet triggers an **individual permission prompt** for the specific permission type.

The routing is determined by the nature of each incoming request and what the manifest declares.

***

### 5.6 PACT Lifecycle

Compliant wallets MUST implement the following PACT lifecycle behavior:

PACT is not an in-memory-only permission. A granted PACT is represented by on-chain Level 2 protocol permission tokens scoped to the originator + counterparty pair.

#### Establishing a PACT

A PACT request flow triggers only when **all** of the following conditions are met:

1. Grouped permission prompting is enabled
2. Counterparty trust prompting is enabled by the wallet
3. The request is for a **protocol** permission (not basket, certificate, or spending)
4. The request is **not** privileged
5. The protocol is **Level 2**
6. The counterparty is a specific 66-character hex public key (not `'self'` or `'anyone'`)
7. No PACT is already established for this originator + counterparty

If all conditions pass, the wallet:

1. Fetches the manifest's `counterpartyPermissions` for the originator.
2. Computes the relevant declared Level 2 protocol set for this originator + counterparty context.
3. Checks which of those protocols already have valid tokens.
4. Presents only missing protocols to the user in a PACT prompt.
5. On grant, creates on-chain protocol permission tokens for each approved protocol.
6. Ensures subsequent checks observe the newly granted state consistently.

#### Checking if a PACT Exists

For PACT evaluation, define the **relevant protocol set** as the Level 2 protocols from `counterpartyPermissions` that apply to the current originator + counterparty context. PACT is established only when every protocol in this set has a valid, unexpired token.

The wallet checks in this order:

1. If counterparty is `'self'` or `'anyone'` -> always `true`.
2. If the manifest has no `counterpartyPermissions` -> `true` (no PACT needed). This means applications that do not declare `counterpartyPermissions` will never trigger a PACT prompt - Level 2 protocol requests will fall through to individual or grouped permission prompts instead.
3. Evaluate the full relevant protocol set against canonical permission state (Section 3.4). If all protocols in the set are valid -> `true`.
4. Otherwise -> `false`.

#### Concurrent PACT Requests

If multiple requests simultaneously require establishing a PACT for the same originator + counterparty pair, wallets MUST apply one consistent permission decision to all affected requests.

***

### 5.7 Whitelisted Counterparties

Wallets MAY define a policy-managed set of trusted infrastructure counterparties that can bypass prompts for specific Level 2 protocols.

If a wallet supports this mechanism:

* It MUST scope each entry to explicit counterparty public key + protocol combinations.
* It MUST NOT treat `'self'` or `'anyone'` as whitelist targets.
* It SHOULD use this only for pre-vetted infrastructure entities.

***

### 5.8 Trust Settings

Wallets MAY expose user trust settings for registry and certifier selection. The trust data model and validation rules are defined by BRC-68 and are out of scope for this specification.

***

## 6. Permission Request Flow

This section defines the normative decision model wallets MUST apply when evaluating application permission requests. It specifies required outcomes and precedence constraints, without prescribing internal implementation details.

### 6.1 Permission Triggering

Permissions are checked **at runtime** when an application calls a wallet API method. Wallets MUST enforce permission checks before executing protected operations.

Applications MAY additionally declare their permissions upfront in the manifest's `metanet.groupPermissions` (see Section 3.2). When present, the wallet uses these declarations to **batch** permission prompts into a single grouped request on first interaction, reducing the number of individual prompts.

### 6.2 High-Level Flow

For each wallet API call from an originator, compliant wallets MUST enforce the following:

* **Originator handling**: The wallet MUST normalize the originator domain before permission evaluation.
* **Admin originator**: Requests from the admin originator are allowed.
* **Reserved namespaces**: Non-admin originators MUST be denied access to admin-reserved protocol, basket, or label names (`admin` prefix or `p` prefix).
* **Policy gating**: If wallet policy disables permission seeking for an operation category, the wallet MAY allow that operation without prompting.
* **Whitelist handling**: If a request matches a policy-approved counterparty+protocol whitelist entry, the wallet MAY allow without prompting.
* **Canonical evaluation**: Otherwise, permission decisions MUST be based on canonical permission state (Section 3.4).
* **Token validity**: A valid unexpired token allows the request. An expired token triggers renewal handling.
* **Missing permission**: If no valid permission exists, the wallet MUST obtain an explicit user decision through applicable permission flows (PACT, grouped/peer-grouped, or individual).
* **Decision outcome**: A grant may create or renew an on-chain token (unless ephemeral). A denial MUST fail the operation with `ERR_PERMISSION_DENIED`.

### 6.3 Grouped Permission Requests

When an application declares `metanet.groupPermissions` in its manifest, the wallet uses this to present a **grouped permission request** - a single prompt containing all the permissions the application needs.

#### Trigger Conditions

The grouped prompt fires only when **all** of the following conditions are met:

1. **Grouped permission prompting is enabled** in the wallet policy.
2. **The manifest is available** and contains a `metanet.groupPermissions` object.
3. **The current request is included** in the manifest's group permissions (see inclusion rules below).
4. **At least one declared permission is not yet granted.** The wallet checks each manifest-declared permission against existing on-chain tokens. If every permission is already granted, the grouped prompt is skipped.

If any condition fails, the wallet MUST use a non-grouped permission path for the triggering request.

#### Inclusion Rules

The wallet determines whether a request is "included" in the manifest's group permissions based on the request type:

* **Protocol**: The request's `protocolID` must exactly match a manifest entry. For **Level 2**, manifest `counterparty` is REQUIRED and MUST exactly match the request counterparty. For **Level 1**, matching is by `protocolID` (counterparty is application-scoped). Empty-string counterparty values are invalid. **Privileged** protocol requests are never included in grouped flows.
* **Basket**: The request's basket name must exactly match a manifest entry.
* **Certificate**: The request's `certType`, `verifierPublicKey`, and `fields` must all match a manifest entry. Field matching uses **exact set equality** (both sets must have the same elements). **Privileged** certificate requests are never included.
* **Spending**: Any spending request matches if the manifest declares a `spendingAuthorization` (regardless of amount).

#### Grouped Request Contents

A grouped request bundles:

* Protocol permissions (zero or more)
* Basket access (zero or more)
* Certificate access (zero or more)
* Spending authorization (zero or one)

The user can selectively approve individual items within the group. The triggering operation MUST be allowed only if its specific required permission is approved.

#### Serialization

Wallets MUST ensure grouped prompting is deterministic under concurrency. A request arriving during an active grouped flow for the same originator MUST observe that flow's final decision before any additional prompt is shown.

### 6.4 Peer-Grouped Requests

Peer-grouped requests are a specialized form of grouped request for Level 2 protocol permissions with a specific counterparty.

#### Trigger Conditions

A peer-grouped flow fires when **all** of:

1. Grouped permission prompting is enabled.
2. The current request is for a **protocol** permission.
3. The protocol is **Level 2**.
4. The manifest contains matching Level 2 protocol entries for the same counterparty.
5. At least one matching permission is not yet granted.

#### Characteristics

A peer-grouped prompt contains **only** Level 2 protocol permissions for a **single** counterparty - no basket, certificate, or spending items. This represents a focused trust decision: "grant this counterparty access to these protocols through this app."

If peer-grouped prompting does not apply, the wallet uses another applicable permission path.

### 6.5 Permission-Seeking Policy

Wallets MAY expose policy controls that enable or disable prompting for specific operation categories. These controls are implementation-defined, but they MUST NOT broaden permission scope, bypass explicit denial handling, or weaken user-driven consent guarantees defined in this specification.

***

## 7. Permission Tokens (On-Chain Persistence)

Wallets MAY persist granted permissions as **on-chain PushDrop outputs** stored in admin baskets within the user's wallet. This provides a tamper-evident, user-owned record of all granted permissions.

### 7.1 Token Storage

Each permission type has a dedicated admin basket:

| Type        | Acronym | Admin Basket Name              |
| ----------- | ------- | ------------------------------ |
| Protocol    | DPACP   | `admin protocol-permission`    |
| Basket      | DBAP    | `admin basket-access`          |
| Certificate | DCAP    | `admin certificate-access`     |
| Spending    | DSAP    | `admin spending-authorization` |

Tokens are encoded as encrypted PushDrop outputs. All fields within the token are encrypted, so an observer inspecting the blockchain cannot determine which protocols are authorized, the expiry times, or the originator domains.

### 7.2 Token Fields

Each token type stores encrypted fields for permission scope and validity:

**DPACP (Protocol):** 6 fields

| # | Field         | Description                         |
| - | ------------- | ----------------------------------- |
| 0 | originator    | Normalized domain                   |
| 1 | expiry        | UNIX epoch seconds (0 = never)      |
| 2 | privileged    | `'true'` or `'false'`               |
| 3 | securityLevel | `1` or `2`                          |
| 4 | protocolName  | Protocol identifier string          |
| 5 | counterparty  | `'self'`, `'anyone'`, or public key |

DPACP tokens MUST NOT be created for Level 0 protocols, because Level 0 is open usage and does not require permission.

**DBAP (Basket):** 3 fields

| # | Field      | Description                    |
| - | ---------- | ------------------------------ |
| 0 | originator | Normalized domain              |
| 1 | expiry     | UNIX epoch seconds (0 = never) |
| 2 | basketName | Basket identifier string       |

**DCAP (Certificate):** 6 fields

| # | Field      | Description                                  |
| - | ---------- | -------------------------------------------- |
| 0 | originator | Normalized domain                            |
| 1 | expiry     | UNIX epoch seconds (0 = never)               |
| 2 | privileged | `'true'` or `'false'`                        |
| 3 | certType   | Certificate type identifier                  |
| 4 | fields     | JSON-encoded array of authorized field names |
| 5 | verifier   | Verifier public key                          |

**DSAP (Spending):** 2 fields

| # | Field            | Description           |
| - | ---------------- | --------------------- |
| 0 | originator       | Normalized domain     |
| 1 | authorizedAmount | Monthly satoshi limit |

Note: DSAP tokens do not have an expiry field. Spending is tracked by calendar month.

### 7.3 Expiry

* Tokens with `expiry === 0` never expire.
* Tokens with `expiry > 0` are compared against the current UNIX epoch time in seconds. If `expiry < now`, the token is expired.
* When an expired token is found, the wallet triggers a **renewal** flow rather than a new permission request.
* Wallets SHOULD set `expiry = 0` for user-granted permissions, because users can revoke permissions at any time and non-zero expiry creates avoidable renewal friction.
* If a finite expiry is supported, it SHOULD be an explicit opt-in policy choice rather than the default.
* DSAP (spending) tokens always have `expiry = 0` - spending limits are enforced on a calendar month basis instead.

### 7.4 Granting

When the user approves a permission request:

1. The wallet applies the approved permission grant scope.
2. Unless this is an ephemeral request, the wallet creates or renews the corresponding on-chain permission token.
3. Subsequent permission checks MUST reflect the new state.

Grant variants:

* **Individual grant** - applies to one permission request.
* **Grouped grant** - applies to the approved subset from a grouped permission request.
* **PACT grant** - creates protocol tokens for each approved Level 2 protocol in the PACT prompt.

**Ephemeral grants**: When `ephemeral: true`, no on-chain token is created. The permission is a one-time in-memory authorization. The caller's request is allowed, but no persistent record is kept. Used primarily for one-off spending approvals.

### 7.5 Denial

When the user denies a permission:

* The triggering operation is rejected with error code `ERR_PERMISSION_DENIED`.
* The wallet MUST enforce denial deterministically - it MUST NOT silently fall back or degrade.

Applications SHOULD treat denied permissions as expected states, not errors.

### 7.6 Revocation

Granted permissions MUST be revokable by the user at any time through the wallet's user interface. Revocation MUST **spend** the on-chain token output with no replacement - the transaction consumes the token UTXO and produces no new permission output, leaving nothing in the respective admin basket.

The wallet supports:

* **Individual revocation** - spends and invalidates a single token.
* **Batch revocation** - revokes multiple tokens.
* **Full originator revocation** - revokes all tokens for an application, optionally filtered by type.

Revoked permissions MUST be enforced immediately. The user will be prompted again the next time the application tries to trigger the operation.

### 7.7 Renewal

When the wallet finds an expired token during a permission check, it triggers a renewal flow:

1. The wallet treats the request as a renewal of previously granted scope.
2. On grant, the wallet invalidates the expired token and creates a replacement token with updated validity.
3. On denial, the operation is rejected.

### 7.8 Caching

Wallets MAY use caching as an optimization, but cache contents are never authoritative. Any allow/deny decision MUST be equivalent to canonical permission-state evaluation (Section 3.4).

### 7.9 Request Deduplication

Wallets SHOULD avoid duplicate prompts for concurrent requests that require the same permission scope. Deduplication strategy is implementation-defined, but MUST preserve correct scope and decision consistency.

***

## 8. Manifest Guidance

Applications SHOULD:

* Serve a valid W3C-compliant `manifest.json` at their domain root
* Include a human-readable `name` and `short_name` field
* Include an `icons` array with at least one icon for visual identification in wallet UI
* Declare `metanet.groupPermissions` listing all permissions the application requires - this enables a single upfront prompt instead of repeated individual prompts
* Declare `metanet.counterpartyPermissions` if the application involves Level 2 peer interactions

Applications MAY:

* Include a `metanet.trust` object (BRC-68) if the application operator is also a trust entity

Applications MUST NOT:

* Declare Level 1 protocols in `counterpartyPermissions` (they will be silently dropped)
* Include misleading or inconsistent numeric claims in permission descriptions (for example, description text that states an amount different from the actual requested satoshi amount or authorization limit)

Wallets SHOULD:

* Display the manifest name in all permission prompts and dashboard views
* Fall back gracefully to the domain name if the manifest is unavailable or lacks a `name` field

***

## 9. Security Considerations

### 9.1 Security Scan

Wallets SHOULD scan manifest-sourced permission descriptions for malicious, deceptive, or internally inconsistent wording. At minimum, wallets SHOULD detect and flag description text that conflicts with structured permission data (for example, satoshi amounts in description text that differ from requested or authorized numeric values). When suspicious wording is detected, wallets SHOULD warn users clearly before approval.

### 9.2 General

* Permissions MUST never be implicitly granted.
* All permission decisions MUST be user-driven.
* Revoked permissions MUST be enforced immediately.
* Permission data MUST be isolated per originator.
* Manifest fetches MUST be restricted to HTTPS (except localhost for development).
* Manifest retrieval MUST be constrained to the origin's `manifest.json` resource and MUST reject unrelated paths.
* Whitelisted counterparties SHOULD be limited to infrastructure entities pre-vetted by the wallet vendor. The whitelist MUST NOT be user-extensible without explicit configuration.
* Trusted certifier public keys MUST conform to compressed public key format (`/^(02|03)[a-fA-F0-9]{64}$/`).
* On-chain permission token fields MUST be encrypted so that only the wallet owner can read them.
* Protocol and basket names starting with `admin` or `p` are reserved and MUST NOT be accessible to non-admin originators.
* The basket name `default` is reserved for internal wallet operations.
* PACT counterparties MUST be validated as 66-character hexadecimal strings (compressed public key format).

***

## Implementations (Informative)

> This section captures implementation-specific details. These are informative, not normative.

**Wallet-Toolbox (`@bsv/wallet-toolbox-client`):**

* `WalletPermissionsManager` wraps the BRC-100 `WalletInterface`, intercepting every method call. It is the enforcement layer.
* Permission tokens are created, renewed, and revoked using standard BRC-100 `createAction` / `signAction` flows.
* Transaction descriptions for permission operations are optionally encrypted using `[2, 'admin metadata encryption']` when `encryptWalletMetadata` is enabled.
* The manager supports **P-modules** via the `PermissionsModule` interface (see below).

**P-Modules (`PermissionsModule` - BRC-98/99/111):**

P-modules are an extensibility mechanism that allows wallet consumers to add custom permission handling for P-prefix baskets, protocols, and labels.

*Interface:* A module MUST implement two methods:

* **`onRequest({ method, args, originator })`** -> `{ args }` - transforms the request before it reaches the underlying wallet. May enforce permissions, throw errors, or modify arguments.
* **`onResponse(result, { method, originator })`** -> `result` - transforms the wallet response before returning to the caller.

*Registration:* Modules are registered via the `permissionModules` config field - a `Record<string, PermissionsModule>` keyed by scheme ID (e.g. `{ "btms": new BtmsModule() }`). If a P-prefix name references an unregistered scheme ID, the wallet throws immediately.

*Detection and routing:* Any basket name, protocol name, or label starting with `"p "` triggers the P-module system. The scheme ID is extracted as the second token (e.g. `"p btms token123"` -> scheme ID `"btms"`).

*P-label format (BRC-111):* Labels MUST follow the format `p <moduleId> <payload>` where:

* `moduleId` is at least 1 character, no spaces
* A single space separates `moduleId` from `payload`
* `payload` is at least 1 character and MUST NOT start with a space

*Scope:* P-modules handle baskets (`listOutputs`, `relinquishOutput`, `createAction`, `internalizeAction`), protocols (`encrypt`, `decrypt`, `createHmac`, `verifyHmac`, `createSignature`, `verifySignature`, `getPublicKey`), and labels (`createAction`, `listActions`, `internalizeAction`).

*Bypass behavior:* P-module paths **completely bypass** the standard on-chain permission token system (DPACP/DBAP/DCAP/DSAP). The module's `onRequest` method is solely responsible for any authorization logic. Non-P baskets, protocols, and labels in the same operation still go through the standard permission checks.

*Chaining:* When multiple P-modules are involved in a single operation (e.g. `createAction` with baskets from one scheme and labels from another), `onRequest` calls are chained forward through all modules, and `onResponse` calls are chained in **reverse** order.

***

## References

This specification references or builds upon:

| BRC                                            | Title                                                 | Relevance                                                      |
| ---------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------- |
| [BRC-68](/peer-to-peer/0068)                   | Publishing Trust Anchor Details at an Internet Domain | `metanet.trust` manifest structure for trusted certifiers      |
| [BRC-73](/wallet/0073)                         | Group Permissions for App Access                      | `GroupedPermissions` interface for batched permission requests |
| [BRC-98](/wallet/0098), [BRC-99](/wallet/0099) | P Protocols and P Baskets                             | Extensible permission modules for P-prefix schemes             |
| [BRC-100](/wallet/0100)                        | Unified Wallet-to-Application Interface               | Underlying wallet API that the permissions manager wraps       |
| [BRC-111](/wallet/0111)                        | P Labels                                              | Label format `p <moduleId> <payload>` for permission modules   |

***

## Non-Goals

This document does not define:

* UI layout or copy
* Wallet-specific storage implementations
* Application UX strategies beyond enforcement expectations
* The full internal API surface of `WalletPermissionsManager`

***

## Backwards Compatibility (`babbage` -> `metanet`)

Prior versions of the wallet permissions system used a `babbage` namespace in the manifest instead of `metanet`. The previous format was:

```json
{
  "name": "My Application",
  "babbage": {
    "groupPermissions": { ... },
    "counterpartyPermissions": { ... },
    "trust": { ... }
  }
}
```

Compared to the `metanet` format defined by this specification, the legacy `babbage` format did not standardize:

* A versioned permissions namespace (`schemaVersion`)
* A W3C Web App Manifest-aligned structure for application metadata
* A normative compatibility model for consuming both legacy and current namespaces during migration

#### Migration Path

Applications SHOULD update their manifests to use the `metanet` namespace and W3C-compliant structure. The migration involves:

1. **Rename the namespace**: Change `"babbage"` to `"metanet"`.
2. **Add `schemaVersion`**: Add `"schemaVersion": 1` inside the `metanet` object.
3. **Add W3C fields**: Include standard manifest fields (`short_name`, `description`, `start_url`, `display`, `icons`) at the top level.
4. **Move trust data**: If using `babbage.trust`, rename to `metanet.trust` (structure is unchanged).

#### Wallet Compatibility

Wallets MUST support both namespaces during the transition period:

1. Check for `metanet` first. If present, use it.
2. If `metanet` is absent, fall back to `babbage` and treat it as equivalent to `metanet` without a `schemaVersion`.
3. Log a deprecation warning when a `babbage` namespace is encountered to encourage migration.

#### Timeline

The `babbage` namespace is **deprecated** as of this specification. Wallets SHOULD continue to support it for backwards compatibility until a future version removes support. Applications SHOULD migrate to `metanet` at their earliest convenience.


# Basket Permission Scheme Registry and Governance

Simon Bettison (<simon@bettison.org>)

## Abstract

We define a governance framework for [BRC-99](/wallet/0099) basket permission schemes. [BRC-99](/wallet/0099) reserves basket identifiers beginning with `p` for future permission schemes and establishes the format `p <scheme-id> <basket-name>`, but defines no process for registering scheme IDs, no taxonomy for classifying schemes, and no consolidated reference for the basket name reservations scattered across multiple wallet BRCs.

This specification addresses those gaps. It provides:

1. A **registry** of registered and reserved `p <scheme>` scheme IDs
2. A **taxonomy** that classifies schemes by purpose
3. A **registration process** for claiming new scheme IDs via BRC specifications
4. A **consolidated reference** of all basket name reservations across the wallet BRC family

No changes to [BRC-100](/wallet/0100)'s basket naming rules or character validation are required. This specification operates entirely within the existing `[a-z0-9 ]` character set and builds on the `p` reservation that [BRC-99](/wallet/0099) and [BRC-100](/wallet/0100) already enforce.

## Motivation

As the BSV wallet ecosystem grows, basket names are being used for increasingly diverse purposes: token tracking, payment receipts, UTXO management, permission tokens, and module-defined digital asset schemes. Each new protocol that uses baskets must choose a naming convention, and without a framework, the risk of collision increases with every new standard.

The current state is fragmented:

| Reservation        | Reserved by                    | Mechanism                                  |
| ------------------ | ------------------------------ | ------------------------------------------ |
| `default`          | [BRC-100](/wallet/0100)        | Wallet rejects at validation               |
| `admin *`          | [BRC-44](/key-derivation/0044) | Wallet rejects at validation               |
| `p <scheme> *`     | [BRC-99](/wallet/0099)         | Wallet rejects unless scheme supported     |
| `balance <basket>` | [BRC-112](/wallet/0112)        | Virtual prefix, reinterprets `listOutputs` |
| `* basket`         | [BRC-100](/wallet/0100)        | Wallet rejects (redundant suffix)          |

Each BRC independently grabs a prefix with no coordination, no registry, and no way to answer "is this scheme ID taken?" without reading every wallet-related BRC.

[BRC-99](/wallet/0099) provides the extensibility mechanism — the `p <scheme-id> <basket-name>` format — but deliberately defers the definition of actual schemes to future specifications. [BRC-116](/wallet/0116)<sup>1</sup> provides the implementation machinery — permission modules that route `p <scheme>` baskets to scheme-specific handlers. What is missing is the governance layer: a registry of scheme IDs, a process for claiming them, and a taxonomy that helps wallet implementers understand the landscape.

## Specification

### 1. Basket Name Space Overview

The [BRC-100](/wallet/0100) basket identifier space is partitioned as follows. This table is a summary of existing rules — this specification does not alter any of these partitions.

| Zone                 | Pattern                        | Governed by                                             | Example                 |
| -------------------- | ------------------------------ | ------------------------------------------------------- | ----------------------- |
| System-reserved      | `default`, `admin *`           | [BRC-44](/key-derivation/0044), [BRC-100](/wallet/0100) | `admin originator keys` |
| Virtual query prefix | `balance *`                    | [BRC-112](/wallet/0112)                                 | `balance savings`       |
| Permission schemes   | `p <scheme> *`                 | [BRC-99](/wallet/0099), **this spec**                   | `p token usd`           |
| Prohibited suffix    | `* basket`                     | [BRC-100](/wallet/0100)                                 | *(rejected)*            |
| Unreserved           | Everything else in `[a-z0-9 ]` | [BRC-100](/wallet/0100)                                 | `game inventory`        |

This specification governs the **permission schemes** zone — specifically, the process by which new `p <scheme>` scheme IDs are registered and classified.

### 2. Scheme Categories

Permission schemes registered under `p <scheme>` are classified into categories based on their purpose. The taxonomy is informational — a scheme's category does not change its validation or routing behaviour. The normative behaviour is always: the wallet encounters `p <scheme-id> <rest>`, looks up the scheme ID in its module registry ([BRC-116](/wallet/0116)<sup>1</sup> `permissionModules`), and routes to the module or rejects per [BRC-99](/wallet/0099).

#### Category 1: Asset Permission Schemes

Schemes that define permission rules for specific types of digital assets. The scheme governs what kinds of UTXOs can be placed in the basket and what access rules apply. Permissions may be based on locking scripts, script templates, tokenisation protocols, or value constraints.

This is the category [BRC-99](/wallet/0099) originally envisioned — for example, a scheme that allows access to a maximum of 10 dollars of tokenised fiat money per month within an application.

#### Category 2: Infrastructure Schemes

Schemes that provide wallet infrastructure capabilities. The basket requires specific wallet functionality to manage — the scheme exists not to restrict asset types but to ensure the wallet has the operational capability the basket demands.

For example, a scheme for managed UTXO provisioning would require the wallet to have specific output management capabilities before it can accept operations on baskets in that scheme.

#### Category 3: Reserved

Scheme IDs reserved for anticipated future use. A reserved scheme ID has no defining BRC and no active semantics — it serves as a placeholder to prevent the ID from being claimed for an incompatible purpose. Reserved IDs MAY be promoted to Category 1 or 2 when a defining BRC is written.

### 3. Scheme Registration Process

A new `p <scheme>` scheme ID is registered by defining it in a BRC specification and adding it to the registry in Section 4.

#### 3.1 Scheme ID Rules

The scheme ID (the first space-delimited token after `p` ) MUST satisfy:

* Matches `[a-z][a-z0-9]*` — starts with a lowercase letter, followed by zero or more lowercase letters or digits
* Minimum 2 characters (single-letter IDs are reserved for future core use)
* Maximum 30 characters
* Contains no spaces (per [BRC-99](/wallet/0099))
* Is unique — does not conflict with any registered or reserved scheme ID in the registry

#### 3.2 Required Documentation

The defining BRC MUST include a section titled "Basket Namespace" (or equivalent) that specifies:

1. **Scheme ID** — the claimed identifier
2. **Category** — per Section 2
3. **Basket name format** — the expected structure of the `<rest>` portion after `p <scheme-id>`
4. **Wallet capability** — what functionality the wallet must provide to handle baskets in this scheme
5. **Permission semantics** — how access is controlled, or a statement that permission enforcement is deferred to the module's implementation per [BRC-116](/wallet/0116)<sup>1</sup>

#### 3.3 Registration

A scheme ID is considered registered when its defining BRC is merged into the BRC repository. This specification's registry table (Section 4) SHOULD be updated in the same pull request.

### 4. Scheme Registry

| Scheme ID | Category     | Defining BRC   | Purpose                             | Example basket |
| --------- | ------------ | -------------- | ----------------------------------- | -------------- |
| `app`     | *(Reserved)* | *(Future BRC)* | Application-scoped basket isolation | *(TBD)*        |

The `app` scheme ID is reserved to allow a future specification to define application-scoped baskets where the originator's identity is encoded in the basket name. This supplements (rather than replaces) [BRC-116](/wallet/0116)<sup>1</sup> originator-based access control, which operates at the permission layer rather than the naming layer.

### 5. Validation Guidance

Wallets implementing [BRC-99](/wallet/0099) permission schemes SHOULD apply the following logic when encountering a basket name beginning with `p` :

1. Extract the scheme ID — the first space-delimited token after `p`
2. Look up the scheme ID in the wallet's registered permission modules ([BRC-116](/wallet/0116)<sup>1</sup> `permissionModules` configuration)
3. If a module is registered for the scheme ID, route the operation to the module
4. If no module is registered, reject the operation — per [BRC-99](/wallet/0099): "wallets must reject any operation requests made under basket IDs beginning with `p` " unless they explicitly support the scheme

This is a restatement of existing [BRC-99](/wallet/0099) and [BRC-116](/wallet/0116)<sup>1</sup> behaviour, consolidated here for clarity.

### 6. Consolidated Reservation Reference

The following table lists all basket name reservations across the wallet BRC family, presented as a single point of reference. Each reservation is normatively defined by its source BRC — this table is informational.

| Pattern            | Meaning                   | Source                                                              | Notes                                         |
| ------------------ | ------------------------- | ------------------------------------------------------------------- | --------------------------------------------- |
| `default`          | Wallet balance basket     | [BRC-100](/wallet/0100)                                             | Historically used for internal operations     |
| `admin *`          | Administrative baskets    | [BRC-44](/key-derivation/0044), [BRC-116](/wallet/0116)<sup>1</sup> | Wallet rejects from non-admin originators     |
| `balance <basket>` | Virtual query prefix      | [BRC-112](/wallet/0112)                                             | Not a real basket; reinterprets `listOutputs` |
| `p <scheme> *`     | Permission scheme baskets | [BRC-99](/wallet/0099)                                              | Wallet rejects unless scheme is supported     |
| `* basket`         | Prohibited suffix         | [BRC-100](/wallet/0100)                                             | Redundant naming; always rejected             |

## Backwards Compatibility

This specification introduces no changes to [BRC-100](/wallet/0100)'s basket naming rules. The character set remains `[a-z0-9 ]`. No existing basket name, validation rule, or wallet behaviour is affected.

The `p` reservation has been enforced by [BRC-100](/wallet/0100) since its introduction — wallets already reject `p` -prefixed baskets unless they support the scheme. This specification provides governance for which scheme IDs exist; it does not alter the mechanism by which they are enforced.

Wallets that do not implement any permission scheme modules will continue to reject all `p` baskets, which is the correct and safe behaviour defined by [BRC-99](/wallet/0099).

## References

* 1: [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)
* 2: [BRC-99: P Baskets](/wallet/0099)
* 3: [BRC-100: Wallet Interface](/wallet/0100)
* 4: [BRC-44: Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044)
* 5: [BRC-46: Wallet Transaction Output Tracking (Output Baskets)](/wallet/0046)
* 6: [BRC-112: Balance Baskets](/wallet/0112)


# Action Reference Labels for BRC-100 Wallets

David Case

## Abstract

This specification defines a backwards-compatible extension to [BRC-100](/wallet/0100) that exposes each action’s wallet `reference` as a synthetic label on `listActions` results. No new request or response fields are introduced, and the label is not persisted as ordinary action metadata.

## Motivation

Under [BRC-100](/wallet/0100), `createAction` may return `signableTransaction.reference`. That value is required for subsequent `signAction` and `abortAction` calls. The interface does not include `reference` on action records returned from `listActions`.

If an application loses the reference after a successful `createAction`—for example because of process failure, lost in-memory scope, or a delayed multi-step signing flow—there is no interoperable way to resume or abort the in-flight action through the public wallet interface.

Adding a new field to action records would change the `listActions` response shape, break WalletWire consumers, and require version or capability negotiation. Following the special-operation label pattern used by [BRC-114](/wallet/0114), this specification surfaces `reference` inside the existing `labels` array as response-derived metadata.

The BRC-100 reference implementation (`bsv-blockchain` ts-sdk / wallet-toolbox) normalizes labels by trimming and lowercasing before persistence and query matching, as documented in [BRC-100](/wallet/0100) and [BRC-65](/wallet/0065). Action `reference` values are `Base64String` and therefore case-sensitive, so the synthetic label encodes reference bytes as lowercase hex.

## Specification

### Reserved label form

This specification reserves labels of the form:

* `reference <hex>`

Where `<hex>` is the lowercase hexadecimal encoding of the raw bytes of the action’s wallet `reference` (`Base64String` decoded to bytes). That `reference` is the value defined by BRC-100 (`signableTransaction.reference`, and the `reference` argument to `signAction` / `abortAction`).

The prefix is exactly `reference` (lowercase, single trailing space before the value). The hex value must use only characters `0-9` and `a-f`, with even length and no `0x` prefix.

### Reserved prefix

Labels beginning with `reference` are reserved for this specification’s response annotation. The synthetic label must not be persisted as ordinary action metadata.

### Response reference label

When `listActions` is called with `includeLabels` set to `true`, each returned action must include exactly one synthetic label:

* `reference <hex>`

derived from that action’s stable wallet reference:

1. Take the action `reference` as a BRC-100 `Base64String`.
2. Decode it to raw bytes.
3. Encode those bytes as lowercase hex.

Rules:

* Include the label only when `includeLabels` is `true`.
* If the returned labels already contain any entry beginning with `reference` , replace those entries with the single wallet-authored synthetic label (do not leave forged or stale values alongside it).
* The value must round-trip to the same `reference` accepted by `signAction` and `abortAction` for that action.
* The underlying reference remains stable for the life of the action record across all statuses exposed by the wallet (including `unsigned`, `nosend`, `sending`, `unproven`, `completed`, and `failed`).
* The label must satisfy general label constraints in [BRC-100](/wallet/0100).

### listActions

No other list behavior is required beyond BRC-100. Callers recover references by listing with ordinary application labels and reading the synthetic `reference <hex>` entry when `includeLabels` is `true`.

How a wallet obtains `reference` internally (for example from local or remote storage) is an implementation concern outside this specification.

### Recovery

1. Create the action with an ordinary correlator label (e.g. `my-app-flow-xyz`).
2. If the in-memory reference is lost, call `listActions({ labels: ["my-app-flow-xyz"], includeLabels: true })`.
3. Read `reference <hex>` from the matching action’s `labels`.
4. Decode `<hex>` to bytes, encode those bytes as standard base64, and use the result as `reference` for `signAction` or `abortAction`.

## Conclusion

By exposing each action’s wallet `reference` as a synthetic `listActions` label, this specification closes the recovery gap for in-flight BRC-100 actions without changing the API surface, WalletWire encodings, or ordinary label storage.

## References

* [BRC-100](/wallet/0100) — Unified wallet-to-application interface
* [BRC-65](/wallet/0065) — Transaction labels and list actions
* [BRC-114](/wallet/0114) — Time labels for list actions


# Pluggable Backup Services for BRC-140 Share Vaults

Brandon Cryderman / HandCash (<brandongcryderman@gmail.com>)

## Abstract

This BRC defines an HTTP **backup service** profile for optional recovery of [BRC-140](/key-derivation/0140) key shares. A backup service stores **at most one** BRC-140 share per authenticated account and releases it only after authentication. Wallets may enroll a **list** of independent service URLs, deposit distinct shares across them under a threshold scheme, and reconstruct the key locally after obtaining enough shares.

A backup service MUST NOT be required for ordinary wallet operation under [BRC-100](/wallet/0100). It MUST NOT receive the root private key, and a conforming wallet MUST NOT send enough shares to any single service to meet the wallet’s recovery threshold.

## Motivation

[BRC-140](/key-derivation/0140) specifies threshold key sharing and the canonical share serialization. It does not define how optional network providers store and release those shares. Without a shared service profile, wallets invent incompatible recovery APIs and users concentrate recovery on a single operator.

This BRC specifies a minimal, pluggable wire contract so any operator can run a share vault and any wallet can enroll multiple providers interchangeably. Cryptography remains BRC-140; this document covers discovery metadata, authentication for release, share endpoints, and operator lifecycle signaling.

## Specification

### Terminology

| Term               | Meaning                                                                                         |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| **Backup service** | HTTP origin that stores ≤ 1 BRC-140 share per authenticated account and releases it after auth. |
| **Wallet**         | Client that splits/reconstructs keys per BRC-140 and speaks this profile.                       |
| **Enrollment**     | Successful deposit of exactly one share to one backup service for one authenticated account.    |
| **Recovery**       | Obtaining ≥ threshold distinct shares and reconstructing the key in the wallet.                 |

### Relationship to other BRCs

| Standard                                                                          | Role                                                                                    |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [BRC-140](/key-derivation/0140)                                                   | Share math and `x.y.threshold.integrity` serialization (normative for `share` payloads) |
| [BRC-75](/key-derivation/0075)                                                    | Offline mnemonic recovery; complementary and out of scope                               |
| [BRC-38](/outpoints/0038) / [BRC-39](/outpoints/0039) / [BRC-40](/outpoints/0040) | Wallet *data* export/sync; out of scope                                                 |
| [BRC-100](/wallet/0100)                                                           | App↔wallet interface; MUST remain usable with zero backup services configured           |

### Non-goals

* Threshold signatures or spend-time co-signing.
* Requiring backup services for send, receive, or BRC-100 connect.
* A global registry of providers.
* Wallet UTXO / history synchronization.
* Mandating a particular identity or KYC regime.

### Cryptographic model

1. The wallet splits its root private key with BRC-140.
2. The wallet deposits **at most one share per backup service**.
3. Reconstruction happens only in the wallet after enough shares are obtained.
4. Services MUST NEVER receive the reconstructed key.

**Invariant (service):** store at most one share per authenticated account; reject non-BRC-140 share strings.

**Invariant (wallet):** MUST NOT send the root private key, mnemonic, or ≥ threshold shares from one split to any single backup service.

A common enrollment scheme is threshold 2 of 3 total shares. Other thresholds are permitted if the wallet and user agree on the scheme before deposit.

### Account binding

The bearer session returned by authentication MUST be bound to a provider-local account. Share endpoints operate on that authenticated account and MUST NOT accept a caller-supplied email, handle, public key, or account hash to select another account's share.

The provider MAY use any internal account identifier. That identifier is not part of this wire profile and MUST NOT be treated as an anonymity mechanism: a provider can associate the stored share with the identity presented during authentication.

### HTTP profile

Base URL is an HTTPS origin (loopback HTTP MAY be used for development). Paths are relative to that origin. JSON UTF-8 bodies. Unknown JSON fields MUST be ignored.

#### `GET /info`

No auth required.

```json
{
  "name": "Example Backup",
  "version": "0.1.0",
  "role": "backup-service",
  "authMethods": ["com.example.connect", "webauthn", "email-otp"],
  "lifecycle": {
    "status": "active",
    "sunsetAt": null,
    "retireAt": null,
    "message": null,
    "successorUrl": null
  }
}
```

| Field         | Requirement | Meaning                                                        |
| ------------- | ----------- | -------------------------------------------------------------- |
| `name`        | MUST        | Operator display name                                          |
| `version`     | SHOULD      | Operator software version                                      |
| `role`        | SHOULD      | Constant `backup-service`                                      |
| `authMethods` | MUST        | Non-empty array of supported authentication method identifiers |
| `lifecycle`   | MUST        | Operator lifecycle block                                       |

#### Lifecycle

| `lifecycle.status` | Meaning                                        |
| ------------------ | ---------------------------------------------- |
| `active`           | Enroll and retrieve allowed                    |
| `sunset`           | Retrieve allowed; new enrolls MUST be rejected |
| `retired`          | Retrieve MUST fail                             |

| Field          | Requirement            | Meaning                                    |
| -------------- | ---------------------- | ------------------------------------------ |
| `sunsetAt`     | MAY                    | RFC 3339 UTC                               |
| `retireAt`     | SHOULD when sunsetting | Last expected retrieve time (RFC 3339 UTC) |
| `message`      | MAY                    | Operator explanation                       |
| `successorUrl` | MAY                    | Suggested replacement base URL             |

Wallets SHOULD refresh `/info` for enrolled URLs and stop new enrolls when status is not `active`. Operators SHOULD provide a sunset window before `retireAt`.

#### Authentication

Authentication is capability-negotiated. This BRC defines a common start/complete envelope but does not mandate a specific identity provider or factor.

`authMethods` contains identifiers for authentication profiles understood by the service. Provider-specific identifiers SHOULD use reverse-domain form (for example `com.example.connect`). Existing standardized identifiers such as `webauthn` MAY be used directly. Email OTP, redirect-based Connect providers, passkeys, and challenge-response systems are all possible profiles; none is required by this BRC. Wallets MUST ignore methods they do not implement.

An authentication method used for recovery MUST remain usable without possession of the secret being recovered. A signature key derived only from the BRC-140-protected root key MUST NOT be the sole recovery method, because that creates a circular dependency. Redirect or challenge methods are conforming only when their credential can be recovered independently.

**`POST /auth/start`**

```json
{
  "method": "com.example.connect",
  "params": {}
}
```

Response (`200`):

```json
{
  "requestId": "<opaque>",
  "expiresInSec": 600,
  "action": {
    "type": "redirect",
    "url": "https://identity.example/authorize?request=<opaque>"
  }
}
```

`params` and `action` are method-specific objects. Defined `action.type` values are:

* `redirect` — open `action.url`; the authorization result is supplied to `/auth/complete`.
* `challenge` — produce a proof over `action.challenge`.
* `outOfBand` — complete the externally delivered challenge (such as an OTP).

Unknown action fields MUST be ignored.

**`POST /auth/complete`**

```json
{
  "requestId": "<opaque>",
  "response": {}
}
```

Response (`200`):

```json
{
  "token": "<bearer secret>",
  "expiresInSec": 1800
}
```

`response` is method-specific. The service MUST validate that it completes the referenced request and MUST bind the returned bearer token to the authenticated provider-local account.

Share endpoints use `Authorization: Bearer <token>`. Tokens SHOULD be short-lived and scoped to backup-service operations.

#### Share endpoints

All share endpoints require a valid bearer session.

```json
{
  "share": "<BRC-140 backup string>"
}
```

(`share` only where noted.)

**`POST /share/enroll`**

* MUST validate `share` as BRC-140 backup serialization.
* MUST fail with `403` when lifecycle forbids enroll.
* Stores exactly one share for the authenticated account (overwrite policy is operator-defined; `/share/rotate` SHOULD be preferred for explicit replacement).

Response (`200`):

```json
{ "ok": true, "enrolledAt": "<RFC 3339>" }
```

**`POST /share/retrieve`**

Request: `{}`.

Response (`200`):

```json
{ "share": "<BRC-140 backup string>", "enrolledAt": "<RFC 3339>" }
```

* MUST NOT return a share without successful auth under the service’s policy.
* MUST fail when lifecycle is `retired` or past `retireAt`.
* MUST `404` when no share is stored.

**`POST /share/rotate` (SHOULD)**

Replace the stored share after auth. Same validation as enroll.

**`POST /share/delete`**

Request: `{}`. Idempotent; response `{ "ok": true }`.

### Wallet requirements

1. Support a list of backup-service base URLs; an empty list MUST be valid.
2. Ordinary spend / BRC-100 use MUST NOT depend on any backup service.
3. Deposit at most one share from a given split to a given service origin.
4. Reconstruct only locally.
5. Keep offline BRC-140 and/or BRC-75 recovery available without services.

### Security considerations

| Risk                       | Mitigation                                                                                                               |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Single service compromised | Threshold ≥ 2; one share insufficient                                                                                    |
| Colluding services         | Threshold and enrollment topology determine exposure; wallets MUST NOT send ≥ threshold shares to one party              |
| Authentication compromise  | Short-lived scoped tokens, rate limiting, and operator-appropriate factors                                               |
| Circular recovery auth     | A key derived solely from the protected root MUST NOT be the only release credential                                     |
| Identity↔share linkage     | The provider can associate its authenticated account with the share; treat providers as share custodians for that record |
| Operator disappearance     | Multi-provider enrollment and offline BRC-140 / BRC-75                                                                   |
| Malformed shares           | Reject non-BRC-140; verify integrity at reconstruct                                                                      |

Protect share transport with TLS. Minimize logging of share material. Encrypt shares at rest under an operator key that is not the user’s root key.

## Implementations

HandCash Desktop and its `backup-service` prototype implement multi-provider BRC-140 share storage on branch `feat/backup-services`. The prototype's original email-OTP exchange predates the capability-negotiated authentication envelope in this draft and is not presented as a complete conforming implementation.

## References

* 1: [BRC-140 — Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme](/key-derivation/0140)
* 2: [BRC-100 — Unified Wallet-to-Application Interface](/wallet/0100)
* 3: <https://github.com/HandCash/HANDCASH-DESKTOP> (branch `feat/backup-services`)


# Pull-Based Receive Discovery

BSVanon (<140332993+BSVanon@users.noreply.github.com>)

## Abstract

This standard specifies how a wallet **discovers unsolicited incoming payments** to addresses it has previously handed out, without cooperation from the sender and without scanning an unbounded derivation space. It defines a *tracked list* of explicitly handed-out receive addresses, a discovery procedure that internalizes each new deposit exactly once (SPV verification is performed within the internalize step), a pruning procedure that retires stale addresses while never orphaning funds, and a deterministic recovery procedure for a fresh device. A reference implementation with a fully injected engine, network, and persistence layer accompanies this document.

## Motivation

Every existing BSV receive mechanism is **cooperative (push)**:

* **BRC-29** has the *sender* derive the recipient key and relay the transaction.
* **BRC-50** has an *application* submit a finished transaction into the recipient's wallet.
* **`internalizeAction`** (BRC-100) has the recipient *handed* a completed, proven transaction to absorb.

Each requires the payer, or software acting for the payer, to actively deliver the payment to the recipient's wallet. None cover the **pull** case: a non-cooperating source — a custodial exchange processing a withdrawal, a swap/on-ramp service, or any party that simply holds one of the recipient's addresses — broadcasts a plain payment on-chain and notifies no one. The recipient's wallet must *notice* it.

The naive approaches are both poor. Reusing a single address destroys privacy and still requires monitoring. Scanning the full derivation space (BIP-44-style gap scanning) on every poll is `O(all derivations)`, leaks every candidate address to the queried indexer, and still risks missing a late payment beyond the gap limit. This standard takes a third approach: monitor only the **explicit, finite set of addresses actually handed out**, which is both tractable (`O(handed-out)`) and complete (a handed-out address is never outside the watched set). Pruning (below) further bounds the *actively-scanned* set to addresses that currently hold value plus recently handed-out empties, so per-poll cost does not grow with the wallet's lifetime payment count.

## Terminology

* **Handed-out address** — a receive address the wallet has generated and disclosed to some party (in a request, an invoice, a QR code, or directly).
* **Discovery state** — the ordered list of handed-out address entries plus a monotonic **counter** (`nextIndex`).
* **Hint** — an untrusted signal from a block explorer that an address may have on-chain activity.
* **Internalize** — the trusted step that fetches the deposit as a proven transaction, SPV-verifies it, and records it as spendable wallet state.

## Specification

### Data model

The discovery state is:

```
State = { nextIndex: uint, entries: Entry[] }
Entry = {
  index:      uint,                       // derivation index; the address's stable identity
  address:    string,
  keyID:      string,                     // derivation locator for the internalize step
  createdAt:  uint,                       // ms epoch, for staleness
  status:     "active" | "pruned",
  everFunded: boolean,                    // advisory, derived from the untrusted hint — not proof of receipt
  seen:       string[],                   // outpoints ("txid.vout") already internalized at this address
  amount?:    uint,                       // optional invoice context
  label?:     string,
  expiresAt?: uint                        // ms epoch; may retire the address before the age threshold
}
```

`index` is the address's durable identity. Because derivation is deterministic in `index`, every handed-out address re-derives from the identity key and its index on any device.

### Injected dependencies

A conformant implementation is defined over four capabilities so that it is independent of any particular wallet engine, network client, or store:

* `derive(index) -> { address, keyID }` — deterministic receive-key derivation (e.g. BRC-42). MUST be a pure function of the identity key and `index`.
* `scan(address) -> { utxos: [{ txid, vout, satoshis }], hasHistory }` — an **untrusted** hint source (typically a block explorer). MAY report unconfirmed (0-conf) outputs. See *Trust model*.
* `internalize({ address, keyID, index, utxo }) -> void` — obtain the deposit in **BEEF** form and validate it per **SPV** rules: Merkle/BUMP proofs verified for mined ancestors against the wallet's own header chain. An unconfirmed deposit MAY be accepted **provisionally** per engine policy and, if later reorged out, reversed via `unsee`. Record the accepted deposit as spendable and durably converge. MUST be **idempotent** per outpoint.
* `load() / save(state)` — durable persistence for the state (optional; an implementation MAY be in-memory).

Plus tuning parameters: `now()`, a staleness threshold `pruneAgeMs`, and a recovery `gapLimit`.

### Operations

* **`nextAddress({ amount?, label?, expiresAt? })`** — allocate `index = nextIndex`, derive, append an active entry, increment the counter, and return `{ index, address, keyID }`. Fresh by default; reuse is permitted but never forced.
* **`discover(onError)`** — for each **active** entry, `scan` it and, for each returned outpoint not already in `seen`, `internalize` it and then record the outpoint in `seen`. Marking `seen` only *after* a successful internalize makes a mid-flight failure retryable and never credits unproven funds. Returns the receipts internalized this pass. A `scan`/`internalize` failure on one address is isolated and reported, never blocking the rest.
* **`prune(onError)`** — retire stale addresses that hold no live value. "Stale" = `createdAt` older than `pruneAgeMs`, or past `expiresAt`. Staleness MUST be confirmed by a **fresh** `scan` at prune time, not a cached flag. An address that still holds **live UTXOs** is **internalized (absorbed) and kept active**, so pruning can never strand a late deposit it just observed. An address that is merely *drained* — prior on-chain history but no current UTXOs — is retired like a never-funded one; keeping it active on the strength of history alone would watch every once-paid address forever and let discovery grow without bound. Retiring only flips `status` to `pruned` (entries are never deleted), so a rare re-payment to a retired address remains recoverable via `deepScan`.
* **`deepScan({ onProgress, onError })`** — the explicit, warned, rate-limited scan over `pruned` addresses. A pruned address found funded is internalized and **resurrected** to `active`. `onProgress` is invoked before each address so the caller owns throttling and user warnings.
* **`deepScanRange()`** — list the pruned addresses, for a caller that drives its own deep scan or shows the list.
* **`unsee(index, outpoint)`** — reverse a `seen` mark after a 0-conf deposit is reorged away (learned from the engine's proof monitoring), so the next `discover` re-checks and, if it reappears, re-internalizes. The engine remains the source of truth for spendability, so no phantom is created either way.
* **`recover({ highestIndex, force, onProgress })`** — rebuild on a fresh device. With the synced counter (`highestIndex = nextIndex`, which MUST be a non-negative integer), rescan the **full** handed-out range `0..N-1` and re-add every address, funded or not, so a late payment to an address that was empty at restore time is still caught by a subsequent `discover`. Without the counter, fall back to a best-effort **gap-scan** that finds only already-funded addresses and stops after `gapLimit` consecutive empty indices. `recover` rebuilds **from scratch** — `seen` marks, invoice metadata, and `status` are not reconstructable from the chain — so an implementation SHOULD refuse or warn when the state is non-empty (this reference refuses unless `force` is set). After recovery, receipts returned by a subsequent `discover` are deposits **internalized this pass**, not first-time-credit notifications; the engine dedups the actual credit.
* **`counter()`** — the single integer (`nextIndex`) worth syncing so `recover` can go full-range.
* **`snapshot()`** — a read-only copy of the state.

### Callbacks and liveness

`onError` and `onProgress` are fire-and-forget telemetry. An implementation MUST invoke them defensively so that a throwing callback cannot break or abort a mutation, and a callback MAY `await` (so the caller can throttle a deep scan). A callback **MUST NOT** invoke a mutating operation on the same instance: mutating operations are serialized, so a re-entrant call queues behind the very operation that is awaiting the callback and deadlocks.

Because mutating operations are serialized, a long-running `discover`, `deepScan`, or `recover` holds the queue for its full duration, and an address hand-out may block behind it. Implementations that require low hand-out latency during long scans SHOULD scope transactions more finely (for example, per entry).

### Trust model

`scan` is only an **untrusted address-index hint**. It never credits funds and is not itself SPV. All crediting occurs in `internalize`, which MUST obtain the deposit in BEEF form and validate it per SPV rules — Merkle/BUMP proofs for mined ancestors against the wallet's own header chain — before recording it (an unconfirmed deposit accepted provisionally is reversible via `unsee`). Consequences:

* A lying or malicious explorer **cannot fabricate a credit** — an unprovable "hint" fails at internalize.
* A censoring or eclipsed explorer **can withhold a hint indefinitely** — for as long as it controls the client's view of the chain, not merely until the next scan.
* Untrusted input can **downgrade discovery**, though never fabricate or lose a credit: because `prune` re-scans through the same source, a persistently lying-empty hint can cause an active address to be pruned (leaving the watched set), after which a real deposit to it is recoverable only via the manual `deepScan`.

Implementations SHOULD therefore treat `scan` as replaceable and MAY use multiple independent sources, a self-hosted indexer, or an overlay lookup.

### Durability, atomicity, and read isolation

Every mutating operation MUST be **atomic** with respect to durable state: apply changes to a private copy, persist, and adopt the persisted copy as live state only after the save succeeds. A failure at any point — the save, a `derive`/`scan`, or a callback — MUST leave the live state unchanged (the private copy is discarded). Combined with an **idempotent** `internalize`, a failed-then-retried operation credits each deposit exactly once, with no phantom credit and none silently skipped.

External readers (`snapshot`, `counter`, `deepScanRange`) MUST observe only committed state, never an in-flight mutation. The state MUST be copy-isolated from the persistence adapter on both `load` and `save` so that a by-reference adapter cannot alias live state.

### Concurrency and multi-device allocation

Within a single instance, mutating operations MUST be **serialized** so that two overlapping `nextAddress` calls cannot read the same `nextIndex` and hand out the same address.

Across instances, collision-freedom of the counter holds only under a **single durable writer**. A deployment with multiple independent writers sharing one identity MUST choose one of:

1. **Explicit single-writer** — one device owns allocation; others request addresses from it.
2. **Device-scoped index ranges** — each writer draws from a disjoint arithmetic range (e.g. `index = offset_d + k·stride`), keeping recovery deterministic per device.
3. **Compare-and-set / lease** on the shared counter.

A collision here is a privacy/merge defect, not a loss of funds: both writers still internalize correctly.

### Privacy

Querying handed-out addresses at a public explorer correlates them to the user at that provider. Mitigations (out of scope of this standard): a self-hosted indexer, an overlay lookup, or query batching. Fresh-by-default addressing limits cross-payment linkage at the cost of a longer list.

## Implementations

[`receive-discovery`](https://github.com/BSVanon/receive-discovery) — a pure, dependency-injected reference implementation of this standard, written in TypeScript and released under the Open BSV License. The engine, network, and persistence are injected, so the full state machine (hand-out, discover, prune, deep-scan, recover, reorg, transactional persistence, concurrency) is exercised hermetically with no engine and no network.

### Test vectors

The reference implementation ships twenty-five hermetic tests that serve as executable test vectors, covering: deterministic sequential allocation and serialized concurrent allocation; discover with once-only internalize and dedup across passes; internalize-failure retry with no phantom credit; per-address scan-failure isolation; prune with fresh re-check, expiry, absorb-on-funded, and drained-address retirement; deep-scan resurrection; full-range and gap-scan recovery, the non-empty-state guard, and the non-negative-integer guard; reorg reversal and pruned-entry resurrection; malformed-state rejection; and transactional persistence — same-instance save-failure retry, copy-isolation against a by-reference adapter, and read isolation against a mid-save dirty read.

## References

* BRC-9, BRC-67 — Simplified Payment Verification
* BRC-29 — Simple Authenticated BSV P2PKH Payment Protocol
* BRC-42 — BSV Key Derivation Scheme (BKDS)
* BRC-50 — Submitting Received Payments to a Wallet
* BRC-62 — Background Evaluation Extended Format (BEEF) Transactions
* BRC-95 — Atomic BEEF Transactions
* BRC-100 — Wallet-to-Application Interface (`internalizeAction`)
* BIP-44 — gap-limit address scanning (contrast)


# Output Identity Tags for BRC-100 Wallets

David Case (<david.case@shruggr.cloud>)

## Abstract

This specification defines a **tag convention** for [BRC-100](/wallet/0100): an output tag prefix `id:` that carries a stable list key for a held output. Callers use it with ordinary `listOutputs` tag filters. It does not change the BRC-100 request or response shape, and it does not require special wallet storage behavior beyond normal tags.

## Motivation

Basket profiles and applications often need a stable handle for one wallet output — reload after restart, target a spend, correlate UI to storage — without scanning every row.

Stamping that handle as a normal, **queryable** tag under a short prefix makes `listOutputs` the lookup API, without new BRC-100 fields or pseudo-tag peel paths.

## Specification

### Tag form

This specification uses tags of the form:

```
id:<key>
```

* The prefix is exactly `id:` (lowercase, ASCII).
* `<key>` is a non-empty string chosen by the writer that stamps the tag (wallet or tool).
* Because reference BRC-100 clients commonly **trim and lowercase** tags before store and match, writers SHOULD use a case-insensitive alphabet for `<key>` (e.g. lowercase hex or decimal digits), and readers MUST treat tag equality as case-insensitive.

Tags beginning with `id:` are used for this output list-key role.

### Meaning

* `id:<key>` identifies a **held output row** for list and spend targeting inside that wallet storage.
* It is **not** a global asset id, token id, origin, outpoint, remittance field, or proof of ownership.
* Encoding of `<key>` is defined by the writer (e.g. decimal storage primary key, UUID). Only that writer must round-trip it.
* Uniqueness: `<key>` SHOULD be unique among that wallet’s held outputs (or a documented narrower scope such as per basket).
* Stability: for a given held row, `id:` SHOULD remain stable for the life of that row (including unproven / unmined outputs if they are listable).

### Behavior

`id:` is an ordinary BRC-100 output tag. No special `listOutputs` semantics, peel, inject, or foreign-tag overwrite is required of generic wallet storage.

Conforming **tools and wallets that adopt this profile** SHOULD:

1. Stamp `id:<key>` on the output when the row is created or first taken into custody (same path as any other tag).
2. Query with a full `id:<key>` tag via ordinary `tags` / `tagQueryMode`.
3. Treat `id:` as their own list key vocabulary — not as token id, origin, or on-chain identity. Basket profiles that need those notions use their own tags (e.g. `bsv21:<tokenId>`, `origin:…`).

Applications MAY read `id:<key>` from `listOutputs` (with `includeTags`) and reuse it in later filters or app state.

### Out of scope

* Changing BRC-100 method schemas or WalletWire encodings
* Basket names, token economics, or provenance proofs
* Requiring a particular key generation algorithm (UUID, counter, storage PK encoding, …)
* Mandating peel/inject or other non-tag storage paths in reference wallets

## Security considerations

* **Not authentication** — Knowing `id:<key>` only helps address a row inside a wallet that already authorized `listOutputs` / spend for that basket.
* **Authority** — The tag is only as trustworthy as the storage that stamped it. Counterparty remittance is not a substitute for the holding wallet’s (or tool’s) own stamp.
* **Case folding** — Assume tags are lowercased in storage; do not put case-sensitive secrets in `<key>`.
* **Ambiguity** — `id:` is short and may appear in other informal tag sets. Writers that need a stricter namespace MAY document a longer key form; this BRC does not reserve exclusivity of the two-letter prefix beyond the list-key meaning above for adopters.

## References

1. [BRC-100](/wallet/0100) — Unified Open BSV Wallet-to-Application Interface
2. [BRC-37](/outpoints/0037) — Basket and Custom Instructions Extension for Bitcoin Outpoints
3. [BRC-46](/wallet/0046) — Wallet Transaction Output Tracking (Output Baskets)
4. [BRC-153](/wallet/0153) — Action Reference Labels for BRC-100 Wallets
5. [BRC-147](/tokens/0147) — 1Sat Ordinals Basket Profile


# Wallet Permission Prompt Liveness Contract

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This BRC defines the liveness contract for wallet requests that require user permission. When an application sends a valid wallet request and the wallet requires user consent before responding, the request is allowed to remain pending until the user grants or denies the request. Application developers should treat such pending requests as normal wallet behavior, not as failures to be timed out. Wallets, in turn, must ensure that any permission-dependent pending request is visibly and reliably presented to the user on desktop and mobile.

## Motivation

Wallet permissions are a user decision, not a server-side availability problem. A user might read the permission details, compare context, switch apps, take a break, or return several minutes later before deciding. Aborting the request because a timer expired breaks the user experience and forces application developers to handle false failures that should never have existed.

The correct abstraction is simple:

1. If a wallet request is pending because user permission is required, the wallet must be asking the user.
2. If the wallet is not asking the user, the request must not remain pending for permission.
3. If a wallet implementation accepts a permission-dependent request but fails to surface the prompt, that is a wallet bug.
4. Applications should not be required to paper over invisible or broken wallet prompts with permission timeouts.

This BRC exists to make that contract explicit across desktop wallets, mobile wallets, embedded wallets, browser wallets, and application developers.

## Specification

### Definitions

* **Application**: Software that calls a BSV wallet interface, including web apps, native apps, servers, command-line tools, and background workers.
* **Wallet**: Software that implements a BSV wallet interface and controls keys, permissions, transactions, baskets, certificates, or related user-owned wallet state.
* **Permission-required request**: A valid wallet request that the wallet cannot complete until the user grants or denies permission.
* **Prompt**: The wallet-controlled user interface through which the user grants or denies a permission-required request.
* **Pending request**: A request for which the application has not yet received a success or error response.
* **Interactive wallet**: A wallet implementation with a user interface capable of asking the user for permission.
* **Non-interactive wallet**: A wallet implementation without a user interface capable of asking the user for permission, such as a server-side wallet, daemon, or CI wallet.

### Core Contract

When an interactive wallet receives a valid permission-required request, it must either:

1. present a prompt to the user and keep the application request pending until the user grants or denies it; or
2. immediately return an error indicating that the wallet cannot request the required permission.

An interactive wallet must not accept a permission-required request, fail to present a prompt, and leave the application request pending.

Applications should not impose permission-specific timeouts on wallet requests. A pending permission-required request means the wallet is waiting for the user. The application may show its own waiting state, but it should not abort the wallet request merely because the user has not yet answered.

Wallets and wallet communication substrates must not abort a permission-required request solely because a fixed amount of wall-clock time elapsed. If the wallet remains connected and the permission request is still active, the request may remain pending for as long as the user has not made a decision.

The intended binary is explicit: if a connected wallet request appears to "hang" at the application boundary, the application developer should be able to assume that the wallet is asking the user. If that assumption is false, the wallet is non-compliant.

### Prompt Visibility Requirements

If the wallet keeps a permission-required request pending, the prompt must be available to the user.

Desktop wallets must make the prompt visible or attention-requesting through normal desktop affordances. This can include focusing or raising the wallet window, opening a modal within an already visible wallet window, bouncing or highlighting the application icon, or otherwise making it clear that the wallet is asking for a decision.

Mobile wallets must present the prompt through a native or application-level surface appropriate for the platform. This can include an in-app modal, bottom sheet, native alert, foregrounded wallet activity, notification-driven return path, or equivalent platform mechanism.

In all cases, the prompt must identify enough context for the user to make a meaningful decision, including the requesting application or origin where available, the requested capability, and whether the request is one-time, persistent, or renewable where such distinction applies.

### Grant and Deny Semantics

When the user grants the request, the wallet must complete the original pending application request with the successful result for the requested wallet method.

When the user denies the request, the wallet must complete the original pending application request with an error indicating that permission was denied.

The wallet must not require the application to poll a separate endpoint, retry the original request, or infer the decision from side effects.

### Non-Interactive Wallets

Non-interactive wallets must not hang waiting for permission. Since there is no user prompt surface, a non-interactive wallet must either:

1. satisfy the request from existing policy, configuration, or pre-granted authority; or
2. reject the request with a clear permission or policy error.

Servers, CI systems, and daemons should therefore have deterministic wallet behavior. Permission-pending liveness is an interactive-wallet concept only.

### Requests That Do Not Seek Permission

If a request explicitly indicates that permission should not be sought, the wallet must not open a prompt for that request. If the request cannot be completed under existing permissions, the wallet must return a clear error rather than wait for user action.

If a request is a wallet status, readiness, network, version, or authentication probe, the wallet must not use that request to trigger a permission prompt.

### Mobile Continuity

Mobile wallets must preserve permission-required requests across ordinary mobile lifecycle events where the wallet process and request context remain alive. Backgrounding, app switching, screen lock, short suspension, orientation changes, and navigation within the wallet must not cause the application request to hang silently.

After resume, the wallet must either:

1. keep the original prompt available;
2. restore an equivalent prompt for the same pending request; or
3. complete the request with a clear cancellation or lifecycle error if the original request can no longer be represented safely.

A mobile wallet must not lose the prompt while leaving the application request pending.

### Disconnections and Process Termination

This BRC does not require wallets to preserve an in-memory request after process termination, device restart, browser tab destruction, transport disconnection, or operating-system termination of the wallet process.

If the request context is lost, the wallet or substrate should fail the request clearly when possible. If the communication channel itself is destroyed, normal transport failure rules apply.

### Application Developer Expectations

Application developers should treat a connected wallet request as having three broad outcomes:

1. success;
2. explicit wallet error, including permission denial; or
3. a pending user decision when the wallet requires permission.

Applications should avoid adding custom permission timeout handling. If an application displays a waiting screen, it should explain that the user may need to answer a wallet prompt.

Applications may still use ordinary transport-level failure handling for disconnected sockets, closed browser tabs, process exits, HTTP connection failures, or other conditions where the wallet is no longer connected. These are not permission timeouts.

### Wallet Developer Requirements

Wallet developers must treat invisible prompts, lost prompts, unresponsive prompt buttons, hidden prompt windows, unresumable mobile prompts, and pending requests without a visible decision path as wallet defects.

Wallets should test and instrument the full permission lifecycle:

1. request accepted;
2. prompt rendered or otherwise made available;
3. grant or deny selected;
4. original request resolved or rejected;
5. prompt dismissed and wallet state updated.

The purpose of this instrumentation is wallet correctness, not application exception handling.

## Compatibility

This BRC is compatible with existing BSV wallet interfaces. It does not define new wallet methods or change the payload shape of existing request and response messages. It clarifies the expected liveness behavior of permission-required requests.

Wallets that currently apply fixed permission-request timeouts should remove them or limit them only to transport/session failure conditions where the wallet is no longer capable of showing a prompt or receiving a user decision.

Applications that currently abort wallet permission requests after a fixed duration should instead leave the wallet request pending and present user-facing guidance to answer the wallet prompt.

## Implementations

Interactive desktop wallets can implement this BRC by making every permission-required request produce a visible wallet prompt and by keeping the original application request open until the user grants or denies it.

Interactive mobile wallets can implement this BRC by binding pending permission requests to durable prompt state that survives normal app foreground/background and navigation lifecycle events.

Non-interactive wallets can implement this BRC by enforcing deterministic policy and rejecting requests that require unavailable user consent.

## References

* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-56: Unified Abstract Wallet-to-Application Messaging Layer](/wallet/0056)
* [BRC-73: Group Permissions for App Access](/wallet/0073)
* [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100)


# Everett-style Transaction Envelopes

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This document outlines the BRC-8 standard for Everett-style Transaction Envelopes. The standard defines the structure and format of the envelopes used to send Bitcoin transactions. The primary goal of this standard is to ensure interoperability between different Bitcoin wallet software and services, allowing them to exchange transaction data in a consistent manner. This standard also aims to facilitate the implementation of the Simplified Payment Verification (SPV) process, as defined in [BRC-9](/transactions/0009), by providing a standardized methodology for exchanging merkle proofs and input transactions.

## Motivation

The use of Bitcoin as a means of payment and store of value has gained significant traction in recent years. As the adoption of Bitcoin continues to increase, there is a need for a standard format for the envelopes that contain Bitcoin transactions. The lack of a standardized format for envelopes can lead to interoperability issues and hinder the development of new Bitcoin-native applications.

The SPV process, outlined in [BRC-9](/transactions/0009), allows payment recipients to verify the validity of a transaction without downloading and verifying the entire blockchain. However, without a standard methodology for exchanging the required merkle proofs and input transactions, applications and businesses are unable to fully benefit from adopting SPV. BRC-8 addresses this issue by providing a consistent format for transaction envelopes that can be used for SPV.

## Status Note

BRC-8 is a historical JSON transaction-envelope format. Current wallet, payment, and overlay implementations in `bsv-blockchain/ts-sdk` and `bsv-blockchain/wallet-toolbox` generally use [BRC-62](/transactions/0062) BEEF and [BRC-95](/transactions/0095) Atomic BEEF instead of BRC-8 envelopes. Specs that previously embedded or returned BRC-8 envelopes should be read as legacy unless they explicitly opt into this format for backwards compatibility.

## Specification:

We specify that the Everett-style Transaction Envelope is a JSON object that consists of the following fields:

* `rawTx` (required): The [BRC-12](/transactions/0012) hex transaction contained in the envelope in serialized hex string format.
* `inputs` (required unless `proof` is provided): An object whose keys are TXIDs of each of the outpoints spent by the transaction. The values are objects containing:
  * `rawTx` (required): The transaction that was spent in serialized hex string format.
  * `proof` (required unless `inputs` is provided): If the transaction is confirmed in a block, the SPV proof of the transaction's inclusion is given in [BRC-10](/transactions/0010) format as a JSON object.
  * `mapiResponses` (optional): An array of signed mAPI response objects, each of which is an object conforming to the mAPI specifications (either a successful broadcast acknowledgment or an affirmative status response).
  * `inputs` (optional): If the transaction is not yet confirmed in a block, this field is the same as the field from the root object, except referring to the previous transaction instead of the root transaction. The field nests recursively back in the tree of the previous unconfirmed transactions until each of the branches can ultimately be traced back to the chain of blocks with a `proof` at the end.
* `proof` (optional): If the root transaction in the envelope is already confirmed in a block, the SPV proof of the transaction’s inclusion is given in [BRC-10](/transactions/0010) format as a JSON object.
* `mapiResponses` (optional): An array of signed mAPI response objects, each of which is an object conforming to the mAPI specifications (either a successful broadcast acknowledgment or an affirmative status response).
* `headers` (optional): An array of 80-byte block header strings in hex format, starting with the block after the recipient's latest known block (indicated and communicated out-of-band) and ending with the latest header known to the sender.
* `pruned: true` (optional): Indicates references to the same TXID are omitted in the inputs object, known as deduplication.

The `inputs` object is required unless the `proof` field is provided for the transaction. The `inputs` object can be used to create a tree data structure that nests recursively back in the tree of the previous unconfirmed transactions until each of the branches can ultimately be traced back to the chain of blocks with a `proof` at the end. The `inputs` object is optional when the transaction is already confirmed in a block and `proof` field is provided. The `proof` field serves as the proof of the transaction's inclusion in a block and is given in [BRC-10](/transactions/0010) format as a JSON object.

The `mapiResponses` field is an array of signed mAPI response objects that are optional as per this specification, but may be required by higher-layer payment protocols such as [BRC-29](/payments/0029). If included, it should be omitted if proof is given. mAPI responses provide assurance that the transaction has been accepted and broadcast to multiple miners, making double-spend attacks harder to achieve.

The `headers` field is an optional array of 80-byte block header strings in hex format. This field can be omitted if no new headers are necessary for verification. The mechanism by which the parties negotiate about which headers are to be provided is beyond the scope of this specification.

### Tree Structure

The Everett-style Transaction Envelope uses a tree structure instead of a flat list structure to avoid providing information for the same transaction more than once. The tree structure has advantages as it can easily be validated with a simple recursive function, allowing parallelized processing of nodes further back in the tree at the same time as parents without needing to keep and reference a copy of the entire envelope while validating each node. This approach also allows for a degree of modularity because each node in the tree is itself a fully-valid envelope, and there is no need to re-scan and re-calculate which other transactions would be required to get a valid sub-envelope (very efficient for adding/removing/working with chains of dependent transactions).

A special case can be added when nodes at different depths in the tree depend on a common transaction, to remove tree-level duplications. The first time a transaction appears, it can be included as normal. Subsequent references to the same TXID can be omitted where they appear in the inputs object. To indicate that an envelope has been deduplicated in this way, the root object MUST contain `pruned: true`. With the issue of duplication solved, we believe a tree structure is the most intuitive and efficient approach.

The use of mAPI is optional and can be disabled by those who do not wish to use it in their implementations. Disabling mAPI allows “receiver-only broadcast” implementations to work. By not allowing mAPI as part of the data structure, the control is taken away from application developers and stakeholders who have widely varying requirements.

### Extensions and Added Fields

There are a wide array of potential applications, higher-layer payment protocols and use-cases for these envelopes. We stipulate that many systems will append (or even remove) certain fields from various nodes in these structures. This specification defines a baseline, and deviations from this baseline need only be documented by other standards which inherit from this one, such as [BRC-29](/payments/0029).

## Implementations

A [web tool](https://hashwrap.babbage.systems) has been implemented which, given any TXID, will produce a valid Everett-style Transaction Envelope.

Developers are encouraged to create their own implementations and contribute to the ecosystem by sharing their work with the community. By doing so, they can help improve the interoperability between different Bitcoin wallet software and services, and further promote the adoption of the BRC-8 standard.

In addition to the web tool, developers should consult the [BRC-9](/transactions/0009) and [BRC-10](/transactions/0010) specifications for more information on implementing the Simplified Payment Verification (SPV) process and the format for SPV proofs, respectively. By adhering to these specifications, developers can ensure their implementations are compatible with existing and future Bitcoin wallet software and services that also follow the BRC standards.

## Future Work

While this standard provides a solid foundation for Everett-style Transaction Envelopes, there is always room for improvement and expansion. Future work could include:

* Developing more efficient algorithms for constructing and validating the tree structure of transaction envelopes.
* Expanding on the negotiation process for providing block headers, making it more seamless and efficient.
* Creating additional tools and libraries to simplify the implementation of the BRC-8 standard in various programming languages and platforms.
* Defining higher-layer protocols for live-updating non-final transactions, peer-to-peer data exchange, and efficient tree state reconciliation.


# Simplified Payment Verification

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The BRC-9 specification defines a process for Simplified Payment Verification (SPV) that allows payment recipients to verify the validity of a transaction without downloading and verifying the entire blockchain. The process involves verifying the headers and proofs of a [BRC-8](/transactions/0008) Transaction Envelope, ensuring that all SPV proofs link back to the genesis block and the chain with the most proof of-work, and confirming that the transaction is valid according to the rules of Bitcoin. This standard aims to facilitate fast, secure, and efficient payment processing for merchants and users.

## Motivation

The Bitcoin blockchain is a decentralized, distributed ledger that records every transaction on the network. However, downloading and verifying the entire blockchain can be time-consuming and resource-intensive, making it impractical for users who want to make quick, simple transactions. Simplified Payment Verification (SPV) provides a way for payment recipients to verify the validity of a transaction without the need for a full blockchain download. This protocol is essential for enabling fast and efficient payment processing, especially for small, casual transactions.

## Specification

### Status Note

BRC-9 describes SPV validation for the older [BRC-8](/transactions/0008) JSON envelope and mAPI response model. Current `bsv-blockchain/ts-sdk` and `bsv-blockchain/wallet-toolbox` wallet/payment flows generally use [BRC-62](/transactions/0062) BEEF, [BRC-95](/transactions/0095) Atomic BEEF, BUMP proofs, and chain trackers instead of `mapiResponses`.

The validation principles in this document remain useful, but implementers should treat the BRC-8 envelope and mAPI-specific checks below as historical unless they are maintaining a legacy protocol that explicitly still exchanges BRC-8 envelopes.

The steps for verifying the validity of a payment transaction using the BRC-9 SPV protocol are as follows:

1. Ensure that the received [BRC-8](/transactions/0008) Envelope is in the correct format
2. Add any previously-unknown headers provided in the [BRC-8](/transactions/0008) Envelope to the chain of headers
3. Ensure that all SPV proofs link back to the genesis block and the chain with the most proof of-work
4. Ensure that all signatures on provided mAPI responses are valid
5. Ensure that all specified minerID public keys have endorsed all mAPI responses
6. Ensure that, if only confirmed inputs were allowed, every specified input transaction is directly proven with an SPV proof rather than relying on a chain of mAPI responses
7. Ensure that the transaction is valid according to the rules of Bitcoin (sum of input amounts is greater than or equal to the sum of output amounts)
8. Ensure that the lock time of the transaction is as requested in the invoice
9. Ensure that, if the lock time is in the future, all sequence numbers are UINT\_MAX, unless you are working with an application that supports continuously-updating payment channel transactions and expect to get another updated transaction later
10. Ensure that evaluating the locking scripts from the input transactions and then the unlocking scripts provided in each input is successful, thus ensuring that all inputs were properly spent
11. Ensure that the transaction was properly signed
12. Ensure that the transaction contains the output scripts and amounts that were requested in the invoice
13. Ensure that the transaction pays a sufficient fee according to the rules of the fee model you are using

The above steps ensure that the transaction is valid and secure according to the Bitcoin protocol. Merchants may take additional measures to increase security, such as subscribing to mAPI status updates and double spend notifications, verifying the identity of the sender, waiting for confirmations, or using an escrow service.

## Implementation

Any wallet that has access to a source of block headers is capable of verifying merkle proofs, and any computer with the resources needed to execute the Bitcoin script programs from the chain of spends is capable of verifying the chain of custody. This means that almost any modern digital computer can easily perform Simplified Payment Verification by following the steps outlined in this document.


# Merkle proof standardised format

## Abstract

This BRC serves as a reference point for the TSC Merkle Proof Standardized Format. It is identified as BRC-10 and is used to avoid ambiguity in referring to the TSC standard, just like other proposals like [BRC-11](/transactions/0011) (which revise or extend this proposal) are referenced by their BRC numbers.

## Motivation

To avoid ambiguity in referring to the TSC Merkle Proof Standardized Format, it is important to have a specific identifier. The use of BRC-10 as a reference point allows for clear and unambiguous communication among developers and the community. The BRC-10 designation is similar to other proposals, such as [BRC-32](/key-derivation/0032), which are also referenced by their BRC numbers.

## Specification

You can read the TSC standard [on the website](https://tsc.bitcoinassociation.net/standards/merkle-proof-standardised-format/).

## Discussion

As with any other BRC, feel free to open issues in the BRC repository that discuss extensions of BRC-10.


# TSC Proof Format with Heights

Tone Engel (<tone@kizmet.org>), TonesNotes

## Abstract

This is an argument for using block height as the standard for indexing block headers within merkle proofs.

It argues for a backward compatible protocol extension in the short term and through deprecation, a long term reduction in the space cost of a usable local copy of block headers by a factor of two.

The referenced specification, the "Spec": [TSC Merkle proof standardised format](https://tsc.bitcoinassociation.net/standards/merkle-proof-standardised-format/)

## Motivation

The Spec implies that a compatible implementation support the capability to lookup block hash and merkle root values, to confirm that they correspond to actual mined blocks.

The Project Babbage team has implemented a block header management system called Chaintracks primarily to support validating merkle proofs as defined in the Spec.

We have a focus on space efficiency as we target the full range of client application deployment scenarios, including mobile and IoT.

You hear that a full set of block headers is only 50MB and only grows at 4MB per year - and this is true - but it refers to the serialized binary space required for just the headers, without the indices required to implement the Spec.

Because the required indices are hash values (merkle root and block header hash), which together make up 64 of the 80 block header bytes, it is likely unavoidable that any implementation will be approximately the size of the headers themselves: To turn a hash into a height, the indices must list all the hash values paired with their height values.

While it is possible to use partial indices and trade index size for IO operations, the indices are not actually needed at all. As Elon says, "the best part is no part."

### Stepping Back: What's the Point of Merkle Proofs?

The purpose of a merkle proof is to prove that a particular bit of data, a bitcoin transaction, was recorded at a particular location in the block chain.

The `nodes` in the proof enable the computation of what the merkle root should be for a block containing the transaction at a particular index position.

To complete the proof, it must be verified that the computed merkle root matches the actual merkle root of the target block.

Being given a merkle root value as part of the proof does NOT complete the proof.

The only thing that completes the proof is to confirm from a trusted copy of block headers that one exists with the computed merkle root value.

Being given a merkle root, block hash, or block header as part of the proof is only useful as an identifier for which block header to check in the trusted copy. If a matching block header isn't found, the proof is invalid, no matter what additional target values are provided.

Furthermore, if the trusted copy includes a merkle root index, then proof can be completed directly from the computed merkle root value: Look it up in your own index, if it is found, the proof is valid and the block is identified.

It is therefore the case, that the existing `target` and `targetType` values in a proof per the Spec are unnecessary.

It may actually be the case, that the only useful `targetType` would be `height`. With a height value, no indices are needed at all, the target block header can be found directly since all headers are exactly 80 bytes long. The trusted copy does not require any indices.

## Specification

### 1. Add `height` as a supported `targetType`

In "Binary form", proofs reserve two bits of "flags" bytes (bits 1 and 2) to identify what the "JSON form" calls the `targetType` of the proof.

Here are the current and proposed values for `targetType`:

| Name         |          | Binary Flags Value | JSON targetType Value | Binary target format | JSON target format |
| ------------ | -------- | ------------------ | --------------------- | -------------------- | ------------------ |
| Block Hash   | existing | 0                  | 'hash' (default)      | 32 bytes             | 64 hex chars       |
| Block Header | existing | 1                  | 'header'              | 80 bytes             | 160 hex chars      |
| Merkle Root  | existing | 2                  | 'merkleRoot'          | 32 bytes             | 64 hex chars       |
| Height       | new      | 3                  | 'height'              | Bitcoin VarInt       | integer            |

### 2. Deprecate `targetType` Other Than `height`

As discussed in Motivation above, the existing target types and target values are irrelevant to being able to complete a merkle proof given the remaining values.

## Implementations

The Project Babbage Chaintracks package currently implements the Spec with this extension.

A comparison of Chaintracks and the Pulse block header packages is listed under References.

## References

* The "Spec": [TSC Merkle proof standardised format](https://tsc.bitcoinassociation.net/standards/merkle-proof-standardised-format/)
* [Bitcoin VarInt](https://github.com/bitcoinj/bitcoinj/blob/3db1b651c9f4d4383ebf48ebd525e71b97acaf08/core/src/main/java/org/bitcoinj/core/VarInt.java#L37)
* [Review of Pulse and Comparison With Chaintracks](https://gist.github.com/tonesnotes/03d44bb99d841b37f1a2644bb314b614)


# Raw Transaction Format

## Abstract

This BRC specifies the format used for raw hex Bitcoin transactions, which are a widely-used way of representing Bitcoin transactions on the network. The specification includes details on the layout and various fields within Bitcoin transactions.

## Motivation

Bitcoin transactions are the mechanism for transferring custody of bitcoin tokens from one party to another. It is crucial to have a clear and unambiguous specification for their format. The raw hex format is widely-used and understanding its structure is important for developers and other stakeholders in the Bitcoin ecosystem.

## Specification

A Bitcoin transaction consists of a version number, a locktime value, a list of inputs, and a list of outputs. The format for a raw hex Bitcoin transaction is as follows:

* Version: 4-byte integer (little-endian)
* Input Count: variable-length integer
* Inputs: a list of input objects, where each input object has the following fields:
  * Previous Transaction Hash: 32-byte hash (little-endian)
  * Previous Transaction Output Index: 4-byte integer (little-endian)
  * Script Length: variable-length integer
  * Unlocking Script: variable-length script
  * Sequence Number: 4-byte integer (little-endian)
* Output Count: variable-length integer
* Outputs: a list of output objects, where each output object has the following fields:
  * Value: 8-byte integer (little-endian)
  * Script Length: variable-length integer
  * Locking Script: variable-length script
* Locktime: 4-byte integer (little-endian)

### Variable Integers

The variable-length integer is a compact representation of an integer value. The first byte of the integer determines the format of the integer:

* If the first byte is less than 0xfd, then the integer is that byte value.
* If the first byte is 0xfd, then the integer is the next two bytes in little-endian format.
* If the first byte is 0xfe, then the integer is the next four bytes in little-endian format.
* If the first byte is 0xff, then the integer is the next eight bytes in little-endian format.

The script fields in the input and output objects are interpreted as bytecode for a [Bitcoin Script](/scripts/0014), which is a stack-based language used to define spending conditions for bitcoin.

The transaction hash (referred to as the "TXID") is calculated by taking the double-SHA256 hash of the entire transaction. This hash is used as a unique identifier for the transaction on the Bitcoin network.

## Wire Frame Format

Raw transactions are transported over the network inside a frame envelope. The frame consists of a fixed-size header followed by the raw transaction payload described above. All multi-byte integers in the header are big-endian.

### Frame Header (44 bytes)

```
| Offset | Size | Field            | Value / Notes                                    |
|--------|------|------------------|--------------------------------------------------|
| 0      | 4    | Network Magic    | `0xE3E1F3E8` (BSV mainnet P2P magic)            |
| 4      | 2    | Protocol Version | `0x02BF` (703, BSV large-block baseline)        |
| 6      | 1    | Frame Version    | `0x01` (BRC-12 legacy)                          |
| 7      | 1    | Reserved         | Must be `0x00`                                  |
| 8      | 32   | Transaction ID   | Raw 256-bit TXID in internal byte order         |
| 40     | 4    | Payload Length   | `uint32` BE; byte count of payload that follows |
| 44     | *    | Payload          | Raw serialized transaction (this specification) |
```

Total header size: **44 bytes**.

### Field Definitions

#### Network Magic (bytes 0–3)

The value `0xE3E1F3E8`, the BSV mainnet P2P network magic. This allows standard BSV firewall rules and network monitoring tools to classify frames correctly. Receivers must reject frames with any other value.

#### Protocol Version (bytes 4–5)

The value `0x02BF` (703 decimal), the BSV node protocol version that introduced the large-block policy. This field is informational; receivers do not validate it.

#### Frame Version (byte 6)

The value `0x01` for frames following this specification. Receivers must reject frames with unknown version bytes.

#### Reserved (byte 7)

Must be `0x00`. Reserved for future protocol extensions.

#### Transaction ID (bytes 8–39)

The 32-byte transaction hash in **internal byte order** as used in the BSV P2P protocol. This is the byte-reversed form of the display TXID shown by block explorers.

#### Payload Length (bytes 40–43)

A 32-bit unsigned integer (big-endian) specifying the number of bytes in the payload immediately following the header.

#### Payload (byte 44 onward)

The raw serialized BSV transaction as defined in the Specification section above.

### Example Frame Hex Dump

A BRC-12 frame carrying a 200-byte transaction:

```
// Header (44 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
01                                                                // Frame Version (0x01 = BRC-12)
00                                                                // Reserved
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471  // Transaction ID
000000C8                                                          // Payload Length (200)

// Payload (200 bytes of raw transaction data)
0200000001...00000000
```


# TXO Transaction Object Format

* Ty Everett (<ty@projectbabbage.com>)
* \_unwriter

## Abstract

This document presents the technical standard for the TXO (Transaction Object) format, a structured representation of Bitcoin transactions that enables powerful queries, processes, and filters. The TXO format captures transaction data in a hierarchical structure, facilitating its storage in document databases (e.g., MongoDB) and enabling real-time filtering with JSON filter libraries (e.g., JQ).

## Motivation

Traditional raw one-dimensional Bitcoin transactions, as described in [BRC-12](/transactions/0012), are challenging to process and filter, which hinders the development of applications that rely on transaction data. The TXO format overcomes these limitations by transforming raw Bitcoin transactions into a structured, queryable, and hierarchical format, thereby enabling developers to build powerful applications and services using transaction data.

## Specification

The TXO format represents a Bitcoin transaction as a JSON object with the following high-level structure:

```json
{
  "tx": {
    "h": [TRANSACTION HASH],
    "r": [RAW TRANSACTION]
  },
  "blk": {
    "i": [BLOCK INDEX],
    "h": [BLOCK HASH],
    "t": [BLOCK TIME]
  },
  "in": [
    INPUT1,
    INPUT2,
    ...
  ],
  "out": [
    OUTPUT1,
    OUTPUT2,
    ...
  ],
  "coinbase": [COINBASE]
}
```

### Level 1: Transaction

At the top level, the TXO format includes two objects: tx and blk.

* `tx` (transaction): contains:
  * `h` (transaction hash)
  * `r` (raw transaction)
* `blk` (block): contains:
  * `i` (block index)
  * `h` (block hash)
  * `t` (block time)

### Level 2: Script

The next level of the TXO format includes two arrays: `in` (input scripts) and `out` (output scripts).

Each input and output is essentially a Bitcoin script. Attributes for each script include:

* `i`: the index of the current script within the `in` (input) or `out` (output) array.
* `b0`, `b1`, `b2`, ...: the script's push data.
  * Opcodes: stored as a JSON object with a single key op and the Bitcoin opcode value (e.g., `{"op": 106}` for the 'return' opcode).
  * Non-Opcodes: stored as a base64 encoded string.
* `lb0`, `lb1`, `lb2`, ...: base64 encoded string, used when the push data size is larger than 512 bytes.
* `s0`, `s1`, `s2`, ...: UTF8 encoded representation of the push data, used for full-text search and simple string matching.
* `ls0`, `ls1`, `ls2`, ...: UTF8 encoded representation, used when the push data size is larger than 512 bytes.
* `str`: full string representation of the script.
* `e`: contains the graph structure of each transaction.

### Level 3: Graphs

The third level of a TXO interpreted transaction focuses on the graphs that represent the relationships between transactions. Graphs play a crucial role in understanding the flow of transactions in the Bitcoin network. Each item in the in and out arrays has an attribute called `e` (edge), which represents the graph structure of each transaction. The edge attribute helps in connecting the inputs and outputs of a transaction, as well as tracking the flow of satoshis between addresses.

For inputs, the edge is an "incoming edge" that represents the outputs from the previous linked transaction. It contains the following fields:

* `h`: The hash of the transaction that contains the previous output.
* `i`: The index of the previous output within its transaction output set.
* `a`: The sender address if it can be parsed into an address.

For outputs, the edge is an "outgoing edge" that represents the outputs to the next linked transaction. It contains the following fields:

* `v`: The amount of satoshis sent.
* `i`: The output index within the transaction.
* `a`: The receiver address if the output is linking to an address.

## Implementation

To implement the TXO format, follow these steps:

1. Deserialize the raw Bitcoin transaction into its input and output scripts.
2. Parse the input and output scripts into a JSON object following the TXO schema described in the specification section. This includes constructing the transaction, script, and graph levels for each transaction.
3. If applicable, store the TXO JSON object in a document database (such as MongoDB) to enable powerful queries, or filter the transactions in real-time using JSON filter libraries (such as JQ).
4. Ensure compatibility with the TXO format by including all specified fields and adhering to the defined structure.

The reference implementation was written by \_unwriter of the 21st Century Motor Company and is available [on NPM](https://www.npmjs.com/package/txo).


# Transaction Extended Format (EF)

## Abstract

Regular Bitcoin transactions do not contain all the data that is needed to verify that the signatures in the transactions are valid. To sign an input of a Bitcoin transaction, the signer needs to know the transaction ID, output index, output satoshis and the locking script of the input transaction. When sending a Bitcoin transaction to a node, only the previous transaction ID and the output index are part of the serialized transaction, the node will look up the locking script and output amount of the input transaction.

We propose an Extended Format (EF) for a Bitcoin transaction, that includes the locking script and the amount in satoshis of all inputs of the transaction. This allows a broadcast service to validate all aspects of a transaction without having to contact a node or an indexer for the utxos of the inputs of a transaction, speeding up the validation.

## Copyright

This BRC is licensed under the Open BSV license.

## Motivation

Verifying that a transaction is valid, including all signatures, is not possible at the moment without getting the unspent transaction outputs (utxos) from the transactions that are used as inputs from a Bitcoin node (or a Bitcoin indexer). This lookup of the utxos always happens inside a Bitcoin node when validating a transaction, but for a broadcast service to be able to fully validate a transaction (including the fee being paid) it also needs to look up the utxos being spent, which complicates scalability, since this lookup needs to happen on a node (via RPC), that might be too busy to react within an acceptable time frame.

A broadcast service would be able to validate a transaction almost in full if the sender would also send the missing data (previous locking scripts and satoshi outputs) from the utxos being used in the transaction. When creating a new transaction, the previous locking scripts and satoshi outputs are needed to be able to properly sign the transaction, so the missing data is available at the time of the transaction creation. Serializing the transaction to Extended Format, instead of the standard format, is at the point of creating the transaction no extra work, but does make it much easier for a broadcast service to validate the transaction when being received, before sending the transaction to a node.

The main motivation for this proposal is therefore scalability. When incoming transactions contain all the data that is needed to validate them, without having to contact an external service for missing data, the broadcast service becomes much more scalable.

## Specification

Current Transaction format:

| Field           | Description                                                                                            | Size                                |
| --------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| Version no      | currently 2                                                                                            | 4 bytes                             |
| In-counter      | positive integer VI = \[\[VarInt]]                                                                     | 1 - 9 bytes                         |
| list of inputs  | Transaction Input Structure                                                                            | qty with variable length per input  |
| Out-counter     | positive integer VI = \[\[VarInt]]                                                                     | 1 - 9 bytes                         |
| list of outputs | Transaction Output Structure                                                                           | qty with variable length per output |
| nLocktime       | if non-zero and sequence numbers are < 0xFFFFFFFF: block height or timestamp when transaction is final | 4 bytes                             |

The Extended Format adds a marker to the transaction format:

| Field           | Description                                                                                            | Size                                |
| --------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------- |
| Version no      | currently 2                                                                                            | 4 bytes                             |
| **EF marker**   | **marker for extended format**                                                                         | **0000000000EF**                    |
| In-counter      | positive integer VI = \[\[VarInt]]                                                                     | 1 - 9 bytes                         |
| list of inputs  | **Extended Format** transaction Input Structure                                                        | qty with variable length per input  |
| Out-counter     | positive integer VI = \[\[VarInt]]                                                                     | 1 - 9 bytes                         |
| list of outputs | Transaction Output Structure                                                                           | qty with variable length per output |
| nLocktime       | if non-zero and sequence numbers are < 0xFFFFFFFF: block height or timestamp when transaction is final | 4 bytes                             |

The Extended Format marker allows a library that supports the format to recognize that it is dealing with a transaction in extended format, while a library that does not support extended format will read the transaction as having 0 inputs, 0 outputs and a future nLock time. This has been done to minimize the possible problems a legacy library will have when reading the extended format. It can in no way be recognized as a valid transaction.

The input structure is the only additional thing that is changed in the Extended Format. The current input structure looks like this:

| Field                     | Description                                                                                 | Size        |
| ------------------------- | ------------------------------------------------------------------------------------------- | ----------- |
| Previous Transaction hash | TXID of the transaction the output was created in                                           | 32 bytes    |
| Previous Txout-index      | Index of the output (Non negative integer)                                                  | 4 bytes     |
| Txin-script length        | Non negative integer VI = VarInt                                                            | 1 - 9 bytes |
| Txin-script / scriptSig   | Script                                                                                      | -many bytes |
| Sequence\_no              | Used to iterate inputs inside a payment channel. Input is final when nSequence = 0xFFFFFFFF | 4 bytes     |

In the Extended Format, we extend the input structure to include the previous locking script and satoshi outputs:

| Field                          | Description                                                                                 | Size                            |
| ------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------- |
| Previous Transaction hash      | TXID of the transaction the output was created in                                           | 32 bytes                        |
| Previous Txout-index           | Index of the output (Non negative integer)                                                  | 4 bytes                         |
| Txin-script length             | Non negative integer VI = VarInt                                                            | 1 - 9 bytes                     |
| Txin-script / scriptSig        | Script                                                                                      | -many bytes                     |
| Sequence\_no                   | Used to iterate inputs inside a payment channel. Input is final when nSequence = 0xFFFFFFFF | 4 bytes                         |
| **Previous TX satoshi output** | **Output value in satoshis of previous input**                                              | **4 bytes**                     |
| **Previous TX script length**  | **Non negative integer VI = VarInt**                                                        | **1 - 9 bytes**                 |
| **Previous TX locking script** | **Script**                                                                                  | **\<script length>-many bytes** |

## Backward compatibility

The Extended Format is not backwards compatible, but has been designed in such a way that existing software should not read a transaction in Extend Format as a valid (partial) transaction. The Extended Format header (0000000000EF) will be read as an empty transaction with a future nLock time in a library that does not support the Extended Format.

## Implementation

The Extended Format has been implemented in [go-bt](https://github.com/libsv/go-bt) and a standalone JavaScript library [bitcoin-ef](https://github.com/TAAL-GmbH/bitcoin-ef).

```
  BIP: 239
  Layer: Applications
  Title: Transaction Extended Format (TEF)
  Author: 
      Simon Ordish (@ordishs)
      Siggi Oskarsson (@icellan)
  Comments-Summary: No comments yet. 
  Comments-URI: - 
  Status: Proposal 
  Type: Standards Track 
  Created: 2022-11-09
```


# Merkle Path JSON format

Deggen (<deggen@kschw.com>)

## Abstract

We present a data model and JSON format for a Merkle Path which will be passed from a Transaction Lookup service to a Lite Client via web API.

## Motivation

[BRC-10](/transactions/0010) otherwise known as the TSC Merkle Proof Standardized Format has a few competing ideas baked into it rather than it being a clear standard to be used in one way only. This has lead to an ugly solution which can be ambiguous. This JSON format is a simple recommendation for how we might use a prettier structure in future to clarify the intended use.

The name Merkle Proof suggests that it can on its own prove something. I don't think this is the case as a blockheader is required to validate. Therefore the suggested name is a "Merkle Path".

## Specification

A Merkle Path needs to indicate the txid of the transaction in question, but this is assumed to be the key of the object rather than contained within the object itself. Also needed is an index number which indicates position with a block.

The proposed JSON is:

```json
// filename or key is a 32 byte txid in hex

{ 
   "index": 12, // JSON Number - technically has no MAX size
   "path": [
      "...leaf of the merkle path", // hex string 32 bytes
      "...leaf of the merkle path",
      "...leaf of the merkle path",
      "...leaf of the merkle path"
   ]
}
```

## Use

The idea is to use the Merkle Path like so:

1. Convert all hex string into reverse bytes, txid and all leaves.
2. Calculate the Merkle root by hashing the txid with each element in the path.
   1. Checking the last bit of the index number, if it's 1 then the txid should be on the right `workingHash = sha256d([...leaf, ...txid])` else other way around `workingHash = sha256d([...txid, ...leaf])`
   2. Right shift the index number
   3. Check the new last bit, if it's 1 then the workingHash should be on the right `workingHash = sha256d([...leaf, ...workingHash])` else other way round `workingHash = sha256d([...workingHash, ...leaf])`
3. Once all leaves are accumulated into a single hash with the above method - we have the Merkle root which we reverse and convert back into a hex string.
4. This root can be used to look up a block header.
5. If the blockheader is part of the longest chain of work then we have a "Merkle Proof" that the tx is included in that block.

### Merkle Proof JSON

You would only likely see a Merkle Proof when you are in a position where you have to present evidence to a court for dispute resolution or something to that effect. In day to day use the Merkle Path format detailed above is all that need be kept within the transaction store, the block header information can be kept separately.

```json
// fake data example merkle proof
{
   "txid": "cefffc5415620292081f7e941bb74d11a3188144312c4d7550c462b2a151c64d", 
   "index": 657813,
   "path": [
	"6cf512411d03ab9b61643515e7aa9afd005bf29e1052ade95410b3475f02820c",
	"cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b",
	"b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef",
	"3470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e"
   ]
   "block": {
      "header": {
         "version": 536870912,
         "prevBlockHash": "0000000000000000005d2328b618e043d80d0e5cd33f79b8351965305482cb6b",
         "merkleRoot": "d66e56fb408763e36e8622eb56a8a1072ccc606476fe9e0765cca0dff95949b1",
         "creationTimestamp": 1534851560,
         "difficultyTarget": 402787433,
         "nonce": 3785175761,
      },
      "hash": "000000000000000001fc2f61db1087c820da44599a82bda8ede1f3c82f67098c",
      "work": 2305305885491475308752,
      "height": 544379
   }
```

## Discussion

The main difference between this and the TSC Merkle Proof Standard Format is renaming a few labels:

### Labels

* `txOrId` is dropped in favor of `txid` because the ambiguity is unnecessary, and full transactions have their own format considerations to focus on, hence separation of concerns.
* `nodes` are renamed to `path` because this more accurately describes the specific hashes we are referring to as those without children in a Merkle tree, and also disambiguates between these and bitcoin nodes or network nodes in general. Citing the original [Merkle Tree patent](https://worldwide.espacenet.com/patent/search/family/022107098/publication/US4309569A?q=pn%3DUS4309569) we see this referred to as "path", with no mention of nodes anywhere.

### Order

The order of items in the JSON is not strict but does imply a better general understanding. As a sentence: This transaction in this block is at index 12 with path... etc.


# Compound Merkle Path Format

Deggen (<deggen@kschw.com>) Damian Orzepowski (<damian.orzepowski@4chain.studio>)

## Abstract

We propose a binary format for Compound Merkle Paths (CMP hereon) optimized for minimal data bandwidth during transmission.

## Explainer Video

[![Watch Explainer](https://img.youtube.com/vi/xuc9g4_T4Ko/default.jpg)](https://youtu.be/xuc9g4_T4Ko)

## Copyright

This BRC is licensed under the Open BSV license.

## Motivation

Current format standards do not cover merkle paths for multiple txids within the same block. This would help reduce the overall size of data needed to express any set of paths from the same block. The larger the set the bigger the space saving.

## Specification

For each level of the merkle path the opposite hash from the one which can be calculated is provided.

#### Data Types

| Field   | Description                                     | Size      |
| ------- | ----------------------------------------------- | --------- |
| height  | The height of the tree up to a max 64           | 1 byte    |
| nLeaves | `VarInt` number of leaves at this height        | 1-9 bytes |
| offset  | `VarInt` offset from left hand side within tree | 1-9 bytes |
| leaf    | Each leaf is a 32 byte hash                     | 32 bytes  |

> #### Formatting Syntax
>
> 1. `offset` and `leaf` are repeated for `nLeaves` at each height
> 2. `height` does not need to be repeated, the inference is that height starts as the max height of the tree and is decremented by one each time we reach the end of the current set of leaves. Once `height === -1` we stop parsing.
> 3. `nLeaves` is repeated for each height, followed by the corresponding `offset` and `leaf` for each.

## Example

### Important Note

We must include the txid and offset within a block at height 0. This is the most efficient way to store the index data required to pull out individual paths when given only a txid. In the example below we encode txids at indices 0 and 3:

| index | txid                                                             |
| ----- | ---------------------------------------------------------------- |
| 0     | e86ec5732f55490a73677fe88a37c875cea49f572e4bc822b83fe96093bb008c |
| 3     | 3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5 |

By convention we reverse the bytes of a txid hex string so these sequences will be seen in their inverse endian form below.

### Hex

```
020101cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b02003470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e01c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c04008c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee801da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb02b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef03e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b
```

### Bytewise Breakdown

```javascript
02 // height = 2
01 // nLeafs at this height VarInt
// ----------------------
01 // offset VarInt
cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b // 32 byte hash
// ----------------------
// implied end of leaves at this height
// height of next leaves is therefore 1
02 // nLeafs at this height VarInt
// ----------------------
00 // offset VarInt
3470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e // 32 byte hash
// ----------------------
01 // offset VarInt
c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c // 32 byte hash
// ----------------------
// implied end of leaves at this height
// height of next leaves is therefore 0
04 // nLeafs at this height VarInt
// ----------------------
00 // offset VarInt
8c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee8 // 32 byte hash (this is the txid at index 0)
// ----------------------
01 // offset VarInt
da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb // 32 byte hash
// ----------------------
02 // offset VarInt
b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef // 32 byte hash
// ----------------------
03 // offset VarInt
e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b // 32 byte hash (this is the txid at index 3)
// ----------------------
// implied end of data because new height would be -1
```

## Implementation

Let's start by dumping this format as hex into a Buffer and parsing it into an object with a Buffer Reader. Then we construct an object

```javascript
const { Br } = require('openspv')
const reader = new Br()
reader.buf = Buffer.from('020101cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b02003470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e01c58e40f22b9e9fcd05a09689a9b19e6e62dbfd3335c5253d09a7a7cd755d9a3c04008c00bb9360e93fb822c84b2e579fa4ce75c8378ae87f67730a49552f73c56ee801da256f78ae0ad74bbf539662cdb9122aa02ba9a9d883f1d52468d96290515adb02b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef03e5b331f4961d764373f3a4e2751954e75489fb17902aad583eedbb41dc165a3b', 'hex')

let maxHeight = parseInt(reader.read(1).toString('hex'), 16)
let compoundPath = Array(maxHeight + 1).fill(0).map(() => ({}))
let height = maxHeight
let x = 0
let nLeavesAtThisHeight = reader.readVarIntNum()
let startOfNextHeight = nLeavesAtThisHeight
while (height >= 0) {
  offset = reader.readVarIntNum()
  hash = reader.read(32).reverse().toString('hex')
  compoundPath[height][hash] = offset
  x++
  if (x == startOfNextHeight) {
    height--
    if (height < 0) break
    nLeavesAtThisHeight = reader.readVarIntNum()
    startOfNextHeight = nLeavesAtThisHeight + x
  }
}

console.log({ compoundPath })
```

## JSON Encoding of a Compound Merkle Path

If we JSON encode the leaves we get the following. Height is encoded as the position of the leaf object within the outermost array.

```javascript
[ // index within the outer array corresponds to the height
	{ // within each height there must be one or more hashes with their corresponding offsets { [hash]: offset }
		"e86ec5732f55490a73677fe88a37c875cea49f572e4bc822b83fe96093bb008c": 0, // txid at index 0
		"db5a519062d96824d5f183d8a9a92ba02a12b9cd629653bf4bd70aae786f25da": 1,
		"efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4": 2,
		"3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5": 3 // txid at index 3
	},
    {
		"1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034": 0,
		"3c9a5d75cda7a7093d25c53533fddb626e9eb1a98996a005cd9f9e2bf2408ec5": 1
	},
	{
		"3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd": 1
	}
]
```

You can derive individual paths for particular transaction indices as necessary using the following algorithm:

Reading index 3 from the Compound Merkle Path.

```javascript

// let's say we want to derive the path for txid 3b5a16dc41bbed3e58ad2a9017fb8954e7541975e2a4f37343761d96f431b3e5 
// first we determine from the 0th array in the compound merkle path that the block index associated is 3.

const example = {
  index: 3,
  path: []
} 

compoundPath.map((leaves, height) => {
  const indexOffset = example.index >> height ^ 1
  for (const hash in leaves) {
    if (leaves[hash] === indexOffset) {
      example.path.push(hash)
      return true
    }
  }
  return Error(`We do not have a hash for this index at height: ${height}`)
})
```

Which yields:

```javascript
{
  index: 3,
  path: [
    '3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd',
    '1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034',
    'efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4'
  ]
}
```

Important to understand that we only kept the paths for indices 0 and 3 for this particular example. If you attempt to run the algo above for any other indices, an error would be thrown. This allowed us to keep 7 hashes out of 14 total. Keeping each path separately we'd have to keep minimum 6 hashes, and if we added another index then the compound method would only require one more hash, whereas saving individual paths would require another 3. The total bandwidth saving would be significant if we had thousands of transactions all in the same block.

## Merkle Proof

We use this to prove inclusion in a block by running a Merkle Proof algorithm on the txid, index, and path. We arrive at a Merkle root hash. This can then be used as the key in a Block Header lookup to determine whether the txid is included within a block which is part of the longest chain of work.

```javascript
// pseudocode
function merkleProof(txid, index, path) {
  try {
    const root = deriveRootFromPath(txid, index, path)
    const blockHeader = await lookupHeaderByRoot(root)
    if (blockHeader.state === 'LONGEST_CHAIN') return true
    else return false
  } catch (error) {
    console.log({ error })
    return false
  }
}
```


# Background Evaluation Extended Format (BEEF) Transactions

Deggen (<deggen@kschw.com>)\
Damian Orzepowski (<damian.orzepowski@4chain.studio>)\
Wojciech Regulski (<wojciech.regulski@4chain.studio>)\
Arkadiusz Osowski (<arkadiusz.osowski@4chain.studio>)

## Abstract

We propose a binary format for sending Transactions between peers to allow [Simple Payment Verification](/transactions/0067) (SPV). The format is optimized for minimal bandwidth while maintaining data required to independently validate the transaction in full.

Assumption: Every user has an independent source of Block Headers indexed by Merkle Root.

The simplest form is two transactions, one confirmed in a block which has a corresponding Merkle path and includes an output which is being spent in the second. The second is a newly signed transaction which constitutes the actual payment.

In cases where one or more inputs are not yet mined, each ancestral transaction is included. We step back through the Transaction DAG until every input has a corresponding parent transaction with a Merkle path.

This format is aligned with Dr. Craig Wright's explanation of SPV from this [article](https://craigwright.net/blog/bitcoin-blockchain-tech/merkle-trees-and-spv/). He makes reference to a new paradigm, this format is an attempt to bring forth that paradigm.

Users can adopt this format to transmit the data required to independently verify that a transaction is valid and is spending a real utxo from an existing block. The control mechanism to ensure no previous spend of the utxo is the economics and law. The format is intended for micropayments, so the risk is low. It is also the case that there is a cost to faking the data, since the attacker would really have to have previously owned an actual utxo. Any malfeasant would be signing incriminating evidence and sending it directly to the plaintiff if they were to defraud someone using this format. Without Merkle paths the data would be easier to fake, and there is no skin in the game required since the data could be randomly generated. Considering all these factors, the validation process is detailed in a later section.

## Copyright

This BRC is licensed under the Open BSV license.

## Motivation

Simplified Payment Verification formats for transmitting transactions between peers has yet to see wide adoption. This proposal advocates for complete ecosystem adoption of the principles of SPV; acknowledges that the system is secured by economic incentives, and law; and lays out a binary format for transmitting the data between parties optimizing for minimal bandwidth.

Three prior formats should be mentioned:

Extended Format [BRC-30](/transactions/0030) incorporates the utxo script for script evaluation and satoshis for checking amounts. This format would still work when sending to nodes as they have a utxo lookup which is indexed by a hash of the extended data, meaning that invalid EF data would be detected immediately.

[Tx Ancestors](https://tsc.bitcoinsv.com/standards/transaction-ancestors/) which was created for use within the [Lite Client Toolbox](https://docs.bitcoinsv.io), this uses an array of rawtxs, Merkle proofs, and Mapi responses to transport the data required for SPV.

Everett-style Transaction Envelopes [BRC-8](/transactions/0008) uses a recursive JSON format which is otherwise similar to the above format originally designed for use within [DPP](/payments/0027). One of the ideas which this proposal highlights is that the target of merkle proofs could be height rather than blockhash or root, to save on bytes. Thinking along these lines, we propose that no target be provided at all, and the root simply be calculated at the receieving end as needed. Using the root to look up headers also avoids any implementation complexity associated with competing blocks. For example, when a competing block encapsulates the transactions it may not have the same height, if all transactions are being stored with paths at a height rather than blockhash or merkle root (which are unique to specific versions of a block) then updating a set of the transactions with merkle paths from the new block will be difficult to sort out. If they're indexed by blockhash then it would be trivial to set them all as "in need of updated paths" without the need for disambiguation.

Mapi is to be deprecated in the near future, so any new recommended formats should not include Mapi Responses as part of the solution.

The array of rawtxs within the Tx Ancestors spec makes some sense in that there are strange cases where the same rawtx has two outputs which are spent in different transactions, both within the ancestry of the tx we are validating. You don't want to have embedded a copy of the same proof twice, hence a hash map would make more sense than just including the merkle path bytes in the tx itself. Everett-style Transaction Envelope deals with this well even given its recursive object design. Txids are used as pointers to existing transactions when complex dependency graphs occur. This proposal uses the same thinking, but for a binary format lists are used rather than recursive objects.

The ordering of data has streaming validation in mind - an idea raised in EF [BRC-30](/transactions/0030) - such that a receiver can start processing the validation immediately on receipt of the first few bytes, and any later bytes rely on previous bytes for their validation to even begin.

* Merkle Paths
* Oldest Tx Anchored by Path
* Newer Txs depending on Oldest parent
* Newest Tx

As soon as the Merkle Paths are receieved we can calculate the roots and lookup their blockheaders. If they're not valid then validation can stop there - rejected. Then we look at the oldest ancestor - if it's valid then its children can be validated, and so on until we reach the most recent tx.

## Specification

BEEF combines thinking from several formats into one binary stream, prefixed with a very specific version number for disambiguation.

* Raw Transaction Format: [BRC-12](/transactions/0012)
* BSV Universal Merkle Path (BUMP): [BRC-74](/transactions/0074)

The encoding version number 4022206465 is chosen such that when seen in hex encoded as 4 bytes little endian it reads: `0100BEEF`. This is to allow multiple versions to be defined in future while keeping the data minimal and leaving an obvious sequence which developers can eyeball. Often Bitcoin developers will see a sequence 0100000000... for rawtx. When they instead see 0100BEEF... they will know this data is BEEF format when debugging and so on. If there are future improvements then the next version would be `0200BEEF` for example, this marker will remain "BEEF" for tens of thousands of versions until we reach 4022271999 which is obviously much more than would ever be required.

| Field           | Description                                                                                                    | Size                |
| --------------- | -------------------------------------------------------------------------------------------------------------- | ------------------- |
| Version no      | Version number starts at 4022206465, encoded Uint32LE => `0100BEEF`                                            | 4 bytes             |
| nBUMPs          | VarInt number of BSV Unified Merkle Paths which follow                                                         | 1-9 bytes           |
| BUMP data       | All of the BUMPs required to prove inclusion of inputs in longest chain of blocks [BRC-74](/transactions/0074) | many bytes x nBUMPs |
| nTransactions   | VarInt number of transactions which follow                                                                     | 1-9 bytes           |
| Raw Transaction | RawTx bytes as in standard format [BRC-12](/transactions/0012)                                                 | many bytes          |
| Has BUMP        | `01` if so, followed by the BUMP index; `00` if not, followed by nothing.                                      | 1 byte              |
| BUMP index      | VarInt index number - indicating the BUMP to which the prior tx belongs if there is one.                       | 1-9 bytes           |

### Ordering

Order is important - we must ensure that we end with the tx being evaluated, and its inputs are above, and their inputs are above that. [Khan's algorithm](https://en.wikipedia.org/wiki/Topological_sorting) is a well known solution to the problem in Graph Theory. Running this on the transaction DAG subset is recommended for complex transaction chains where order is not clear. Example below to demonstrate with extraneous data removed for clarity.

```javascript
// khan's algorithm
function khanTopologicalSort(graph) {
    const inDegree = {}
    const queue = []
    const result = []
    for (let node in graph) {
        inDegree[node] = 0
    }
    for (let node in graph) {
        for (let neighbor in graph[node]) {
            inDegree[neighbor]++
        }
    }
    for (let node in inDegree) {
        if (inDegree[node] === 0) {
            queue.push(node)
        }
    }
    while (queue.length) {
        let node = queue.shift()
        result.push(node)
        for (let neighbor in graph[node]) {
            inDegree[neighbor]--
            if (inDegree[neighbor] === 0) {
                queue.push(neighbor)
            }
        }
    }
    return result.reverse()
}

const txs = [
    {
        txid: '2222222222222222222222222222222222222222222222222222222222222222',
        inputs: ['1111111111111111111111111111111111111111111111111111111111111111'],
    },
    {
        txid: '1111111111111111111111111111111111111111111111111111111111111111',
        inputs: ['0000000000000000000000000000000000000000000000000000000000000000'],
    },
    {
        txid: '0000000000000000000000000000000000000000000000000000000000000000',
        inputs: [],
    },
    {
        txid: '4444444444444444444444444444444444444444444444444444444444444444',
        inputs: [
            '3333333333333333333333333333333333333333333333333333333333333333',
            '2222222222222222222222222222222222222222222222222222222222222222',
        ],
    },
    {
        txid: '3333333333333333333333333333333333333333333333333333333333333333',
        inputs: [
            '2222222222222222222222222222222222222222222222222222222222222222',
            '1111111111111111111111111111111111111111111111111111111111111111',
        ],
    },
]

const graph = {}
for (let tx of txs) {
    graph[tx.txid] = {}
    for (let input of tx.inputs) {
        graph[tx.txid][input] = true
    }
}
console.log({ graph })
console.log({ correctOrder: khanTopologicalSort(graph) })
```

## BEEF Example

### Hex

```
0100beef01fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331020100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000001000100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac0000000000
```

### Bytewise Breakdown

```javascript
0100beef // version
01 // VarInt nBUMPs
fe636d0c0007021400fe507c0c7aa754cef1f7889d5fd395cf1f785dd7de98eed895dbedfe4e5bc70d1502ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e010b00bc4ff395efd11719b277694cface5aa50d085a0bb81f613f70313acd28cf4557010400574b2d9142b8d28b61d88e3b2c3f44d858411356b49a28a4643b6d1a6a092a5201030051a05fc84d531b5d250c23f4f886f6812f9fe3f402d61607f977b4ecd2701c19010000fd781529d58fc2523cf396a7f25440b409857e7e221766c57214b1d38c7b481f01010062f542f45ea3660f86c013ced80534cb5fd4c19d66c56e7e8c5d4bf2d40acc5e010100b121e91836fd7cd5102b654e9f72f3cf6fdbfd0b161c53a9c54b12c841126331 // see BRC-74 for details of BUMP format
02 // VarInt nTransactions = 2
// rawtx parent follows
0100000001cd4e4cac3c7b56920d1e7655e7e260d31f29d9a388d04910f1bbd72304a79029010000006b483045022100e75279a205a547c445719420aa3138bf14743e3f42618e5f86a19bde14bb95f7022064777d34776b05d816daf1699493fcdf2ef5a5ab1ad710d9c97bfb5b8f7cef3641210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013e660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac00000000
01 // above tx has merkle path
00 // VarInt the index of the path for this tx in the above list
// rawtx current payment follows
0100000001ac4e164f5bc16746bb0868404292ac8318bbac3800e4aad13a014da427adce3e000000006a47304402203a61a2e931612b4bda08d541cfb980885173b8dcf64a3471238ae7abcd368d6402204cbf24f04b9aa2256d8901f0ed97866603d2be8324c2bfb7a37bf8fc90edd5b441210263e2dee22b1ddc5e11f6fab8bcd2378bdd19580d640501ea956ec0e786f93e76ffffffff013c660000000000001976a9146bfd5c7fbe21529d45803dbcf0c87dd3c71efbc288ac00000000
00 // above tx doesn't have merkle path, but instead has local parent
```

## Validation Process

1. We parse the BUMPs storing each in an array so that we can address them by index later.
2. We parse each transaction.
   1. RawTx bytes double sha256 to get the txid.
   2. Store in hashmap from txid => parsedTx
   3. If there is a Merkle path after the tx then we lookup the BUMP using the BUMP index number.
      1. Lookup the txid within level 0 leaves of the BUMP to get the index of the txid within a block.
      2. Calculate the Merkle root with the index, txid, and BUMP data.
      3. Add the merkle root to an array which will be used in a request to our local header service once all transactions have been parsed.
      4. Mark the tx as valid or not as soon as the header service responds.
   4. Otherwise we run local validation on the transaction, stopping at the soonest failure.
      1. Check the txid exists in memory, and is marked as valid.
      2. Check that all scripts evaluate to TRUE.
      3. Check that the sum of satoshis in > satoshis out + fee
      4. Mark the tx as valid.
3. We make a request to the local header service as soon as we have all merkle roots calculated.
   1. This involves sending a list of Merkle roots to the Pulse service which will validate that the merkle roots provided are all part of headers within the longest chain.
   2. If any of the roots are not part of the longest chain, the response is negative and the whole validation has failed.
4. We await the final transaction being marked as valid since it depends on all other processes.

## Implementation

The format will be built into BUX to begin real world testing. Thereafter common libraries such as [bsv](https://www.npmjs.com/package/bsv) and [go-bt](https://github.com/libsv/go-bt) for easy incorporation into existing stacks.


# Simplified Payment Verification

## Abstract

Defining Simplified Payment Verification (SPV) without external reference for sake of clarity.

## Motivation

Bitcoin's security model is built upon the idea of widespread distribution of blockheaders, where applications run SPV. This ensures that anyone can easily identify any attempt to commit fraud using tampered data. To ascertain the validity of data received from peers, any client on the network can employ Simplified Payment Verification. We require a clear and definitive reference that outlines the specific criteria. This way, implementations with variations in finer details can always refer back to the fundamental principles that remain constant over time.

Certain checks that are unique to each implementation, such as detecting encoding errors or malformed transactions, are not covered here. Although these checks are essential, they vary according to the implementation and are not included in the definitive list presented below.

## Verification Steps

Checks made on receipt of a transaction from a counterparty:

1. Script evaluation of each unlocking script results in `TRUE`.
2. The sum of the satoshis-in must be greater than the sum of the satoshis-out.
3. Each input must be associated with a Merkle path to a block.
4. nLocktime, and nSequence of each input are set to the expected values.

### Script Evaluation

Often this is referred to as "check the signatures" which is indeed usually the case but it is possible to have transactions which do not require signatures so for the sake of technical exactitude - running script evaluation is really what is happening here. The interpretation requires that each input unlocking script is concatenated with the previous locking script and the interpreter runs given that input. The result should be a truthy value left on the stack after execution, otherwise the predicate has failed and the utxo has not been unlocked.

### Check Fees

Each input is a pointer to a previous output, each output has a satoshi value. Therefore to calculate the input satoshis we must have details of the previous transactions from which we are spending utxos. We use this information to determine the fees paid by the transaction. We compare these fees with the size of the Tx in bytes to arrive at a rate: sats/byte. The acceptable rate is well known, various services publish this information. At the time of publishing this rate is equivalent to 1 satoshi for a standard transaction, therefore the check could simply be:

```
sumInputAmounts > sumOutputAmounts
```

### Merkle Path

If all inputs come from Transactions which are mined in a block, then the associated Merkle path is one which leads that txid to a Merkle root. If some inputs are not in a block then we must include previous transaction data, and follow the history of all inputs until we arrive at a point where all inputs are associated with previous inputs which appear in a mined block. Various formats will handle this differently, but the universal rule is that SPV requires that we prove that all inputs come from legitimate transactions.

In a long chain of transactions conducted while not connected to the internet, each new transaction is appended to the end of the SPV data such that all prior transaction ancestry is propagated to all counterparties, until they are broadcasted. So we would expect large SPV data payloads only when many transactions happen offline, which in today's age will be extremely rare.

![all inputs lead to a block](https://github.com/bsv-blockchain/BRCs/assets/8416253/0977b132-7a7b-4f22-8ee1-5819ce42590f)

### Locktime and Sequence

The nLocktime default is 00000000, and nSequence default is FFFFFFFF. If these values are not default then there is a naunced condition to the transaction which is explained [here](https://wiki.bitcoinsv.io/index.php/NLocktime_and_nSequence).


# Merkle Path Binary Format

Deggen (<deggen@kschw.com>)

## Abstract

We propose a binary format for a Single Merkle Path optimized for storage in a key value database.

## Copyright

This BRC is licensed under the Open BSV license.

## Motivation

The [TSC format](https://tsc.bsvblockchain.org/standards/merkle-proof-standardised-format/) includes oddities in it for future extensions which are no longer necessary since they are covered by the compound merkle path format defined in [BRC-61](/transactions/0061). So now we attempt to specify the smallest possible encoding of a simple merkle path.

## Specification

We take the JSON version from [BRC-58](/transactions/0058) eg.

```json

{ 
   "index": 136,
   "path": [
	"6cf512411d03ab9b61643515e7aa9afd005bf29e1052ade95410b3475f02820c",
	"cd73c0c6bb645581816fa960fd2f1636062fcbf23cb57981074ab8d708a76e3b",
	"b4c8d919190a090e77b73ffcd52b85babaaeeb62da000473102aca7f070facef",
	"3470d882cf556a4b943639eba15dc795dffdbebdc98b9a98e3637fda96e3811e"
    ]
}
```

Encoding in bytes we start with a VarInt for index, followed by nPath being the number of leaves to follow, followed by 32 byte leaves.

#### Data Types

| Field   | Description                                  | Size               |
| ------- | -------------------------------------------- | ------------------ |
| index   | `VarInt` tx index number from within a block | 1-9 bytes          |
| nLeaves | `VarInt` number of leaves which follow       | 1-9 bytes          |
| leaf    | Each leaf of the path is a 32 byte hash      | 32 bytes x nLeaves |

## Example

### Hex

```
88040c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cdefac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b41e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034
```

### Bytewise Breakdown

```javascript
88 // index VarInt
04 // nLeaves
0c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c // leaf
3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cd // etc.
efac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b4
1e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034
```

## Implementation

Let's start by dumping this format as hex into a Buffer and parsing it into an object with a Buffer Reader. Then we construct an object

```javascript
const { Br } = require('bsv')
const reader = new Br()
reader.buf = Buffer.from('88040c82025f47b31054e9ad52109ef25b00fd9aaae7153564619bab031d4112f56c3b6ea708d7b84a078179b53cf2cb2f0636162ffd60a96f81815564bbc6c073cdefac0f077fca2a10730400da62ebaebaba852bd5fc3fb7770e090a1919d9c8b41e81e396da7f63e3989a8bc9bdbefddf95c75da1eb3936944b6a55cf82d87034', 'hex')

let merklePath = { path: [] }
merklePath.index = reader.readVarIntNum()
let nLeaves = reader.readVarIntNum()
for (x = 0;x < nLeaves; x++) {
  const leaf = reader.read(32).reverse().toString('hex')
  merklePath.path.push(leaf)
}

	
console.log({ merklePath })
```


# BSV Unified Merkle Path (BUMP) Format

Darren Kellenschwiler (<deggen@kschw.com>), Deggen\
Tone Engel (<tone@kizmet.org>), TonesNotes\
Ty Everett (<ty@projectbabbage.com>)\
Damian Orzepowski (<damian.orzepowski@4chain.studio>)\
Arkadiusz Osowski (<arkadiusz.osowski@4chain.studio>)

## Abstract

We propose the BSV Unified Merkle Path format in both binary and JSON encoding optimized for generation by transaction processors, and also happens to be convenient for proof validating clients.

At a high level the format encodes a number of txids which all exist within one particular block, along with each of their merkle paths and the blockHeight.

The blockHeight is encoded first, followed by level 0 of the Merkle tree, which includes the txids of interest, and their corresponding siblings. Thereafter we encode each level of the tree thereafter, but only include branches of the tree which are required to calculate the Merkle root the txids which are of interest to us. For example if we only have one txid of interest, we will include it and its sibling, followed by one leaf per level of the tree.

## Copyright

This BRC is licensed under the Open BSV license.

## Visualization

[BUMP Showcase](https://bitcoin-sv.github.io/showcase-merkle-paths/) can help form an understanding of how BUMP works to encode all necessary data.

## Motivation

Several formats have made their own improvements to the original format which was returned by a Bitcoin node via json-rpc method `getmerkleproof`.

Improvements include:

* [BRC-10](/transactions/0010) a TSC creation which was subsequently returned by the node's json-rpc method `getmerkleproof2`
* [BRC-11](/transactions/0011) removing the need for specifying targets, replacing with height to improve validation speed.
* [BRC-58](/transactions/0058) removal of all extraneous data to minimize data size.
* [BRC-61](/transactions/0061) introduction of a compound path encoding which allows representation of multiple paths within the same block.

The purpose of defining this new specification is to capture the incremental improvements under one spec which encapsulates the pros of each, and removes the cons. This new spec should allow:

* Inclusion of height makes lookup extremely fast while only adding maximum 9 bytes to the data size.
* Multiple paths can be expressed in the same data model.
* One format for everything, so that there is no need to convert from single to compound path.
* Size optimization allowing us to skip encoding of far right leaves when duplication of working hash would suffice.

## Binary Encoding

### Global

The top level encoding specifies a block height and a tree height.

| Field        | Description                                                      | Size      |
| ------------ | ---------------------------------------------------------------- | --------- |
| block height | `VarInt` block height in which the transactions are encapsulated | 1-9 bytes |
| tree height  | The height of the Merkle Tree in this block, max 64              | 1 byte    |

### Level

Thereafter the number of leaves at the top height is specified, and the leaves for this height follow.

| Field   | Description                              | Size              |
| ------- | ---------------------------------------- | ----------------- |
| nLeaves | `VarInt` number of leaves at this height | 1-9 bytes         |
| leaves  | Each leaf encoded in the format below.   | sum of leaf sizes |

### Leaf

Once all leaves at this height have been specified, an implied increment of the height in the tree occurs and we specify the number of leaves in the next level up, and so on until we have specified the leaves at level (treeHeight - 1) at which point we stop. We do not need to encode the root hash as it is always calculable.

| Field  | Description                                                           | Size          |
| ------ | --------------------------------------------------------------------- | ------------- |
| offset | `VarInt` offset from left hand side within tree                       | 1-9 bytes     |
| flags  | Flags can be `00`, or `01`, or `02` - detailed meaning in table below | 1 byte        |
| hash   | A hash representing a txid, sibling hash, or a branch                 | 0 or 32 bytes |

### Flags

The first flag is to indicate whether or not to duplicate the working hash or use the following data. The second flag indicates whether the hash is a relevant txid or just a sibling hash.

| bits      | byte | meaning                                 |
| --------- | ---- | --------------------------------------- |
| 0000 0000 | 00   | data follows, not a client txid         |
| 0000 0001 | 01   | nothing follows, duplicate working hash |
| 0000 0010 | 02   | data follows, and is a client txid      |

### Hex String

```
fe8a6a0c000c04fde80b0011774f01d26412f0d16ea3f0447be0b5ebec67b0782e321a7a01cbdf7f734e30fde90b02004e53753e3fe4667073063a17987292cfdea278824e9888e52180581d7188d8fdea0b025e441996fc53f0191d649e68a200e752fb5f39e0d5617083408fa179ddc5c998fdeb0b0102fdf405000671394f72237d08a4277f4435e5b6edf7adc272f25effef27cdfe805ce71a81fdf50500262bccabec6c4af3ed00cc7a7414edea9c5efa92fb8623dd6160a001450a528201fdfb020101fd7c010093b3efca9b77ddec914f8effac691ecb54e2c81d0ab81cbc4c4b93befe418e8501bf01015e005881826eb6973c54003a02118fe270f03d46d02681c8bc71cd44c613e86302f8012e00e07a2bb8bb75e5accff266022e1e5e6e7b4d6d943a04faadcf2ab4a22f796ff30116008120cafa17309c0bb0e0ffce835286b3a2dcae48e4497ae2d2b7ced4f051507d010a00502e59ac92f46543c23006bff855d96f5e648043f0fb87a7a5949e6a9bebae430104001ccd9f8f64f4d0489b30cc815351cf425e0e78ad79a589350e4341ac165dbe45010301010000af8764ce7e1cc132ab5ed2229a005c87201c9a5ee15c0f91dd53eff31ab30cd4
```

### Bytewise Breakdown

```javascript
fe8a6a0c00 // blockHeight (813706), VarInt
0c // treeHeight (12), byte
// Level 0, client TXIDs and sibling TXIDs (TXIDs required only to compute internal tree hash).
04 // nLeaves, VarInt
fde80b // offset, VarInt
00 // flags
11774f01d26412f0d16ea3f0447be0b5ebec67b0782e321a7a01cbdf7f734e30 // hash
fde90b // offset VarInt
02 // flags = CLIENT_TXID
004e53753e3fe4667073063a17987292cfdea278824e9888e52180581d7188d8 // hash
fdea0b // offset VarInt
02 // flags = CLIENT_TXID
5e441996fc53f0191d649e68a200e752fb5f39e0d5617083408fa179ddc5c998 // hash
fdeb0b // offset VarInt
01 // flags = DUPLICATE_WORKING_HASH
// Level 1, internal merkle tree hashes
02 // nLeaves, VarInt
fdf405 // offset, VarInt
00 // flags
0671394f72237d08a4277f4435e5b6edf7adc272f25effef27cdfe805ce71a81 // hash
fdf505 // offset VarInt
00 // flags
262bccabec6c4af3ed00cc7a7414edea9c5efa92fb8623dd6160a001450a5282 // hash
// Level 2, internal merkle tree hashes
01 // nLeaves VarInt at level 2
fdfb02 // offset VarInt
01 // flags = DUPLICATE_WORKING_HASH
// Level 3, internal merkle tree hashes
01 // nLeaves VarInt at level 3
fd7c01 // offset VarInt (three hundred and eighty)
00 // flags
93b3efca9b77ddec914f8effac691ecb54e2c81d0ab81cbc4c4b93befe418e85 // hash
// Level 4, internal merkle tree hashes
01 // nLeaves VarInt at level 4
bf // offset VarInt
01 // flags = DUPLICATE_WORKING_HASH
// Level 5, internal merkle tree hashes
01 // nLeaves VarInt at level 5
5e // offset VarInt
00 // flags
5881826eb6973c54003a02118fe270f03d46d02681c8bc71cd44c613e86302f8 // hash
// Level 6, internal merkle tree hashes
01 // nLeaves VarInt at level 6
2e // offset VarInt
00 // flags
e07a2bb8bb75e5accff266022e1e5e6e7b4d6d943a04faadcf2ab4a22f796ff3 // hash
// Level 7, internal merkle tree hashes
01 // nLeaves VarInt at level 7
16 // offset VarInt
00 // flags
8120cafa17309c0bb0e0ffce835286b3a2dcae48e4497ae2d2b7ced4f051507d // hash
// Level 8, internal merkle tree hashes
01 // nLeaves VarInt at level 8
0a // offset VarInt
00 // flags
502e59ac92f46543c23006bff855d96f5e648043f0fb87a7a5949e6a9bebae43 // hash
// Level 9, internal merkle tree hashes
01 // nLeaves VarInt at level 9
04 // offset VarInt
00 // flags
1ccd9f8f64f4d0489b30cc815351cf425e0e78ad79a589350e4341ac165dbe45 // hash
// Level 10, internal merkle tree hashes
01 // nLeaves VarInt at level 10
03 // offset VarInt
01 // flags = DUPLICATE_WORKING_HASH
// Level 11, internal merkle tree hashes
01 // nLeaves VarInt at level 11
00 // offset VarInt
00 // flags
af8764ce7e1cc132ab5ed2229a005c87201c9a5ee15c0f91dd53eff31ab30cd4 // hash
```

## JSON Encoding

In the JSON encoding - we start with a height of a block in which transactions from BUMP are mined. A path Array index corresponds to the height within the Merkle tree, so we start with level 0 which includes all of the txid's of interest to a client in this block and the txid's of additional transactions required to begin the merkle root computation. Within each array element we contain an array of one or more leaves which are specified as a leaf.

Within the leaf itself we have am `offset` - the only required parameter, along with optional `hash`, `txid` and `duplicate`. The `hash` is a hex string encoding reversed bytes of the hash at this position in the Merkle tree, the `duplicate` true is a boolean and represents a "no data" for this position, this is to encode for the right hand side of the merkle tree. The expected behavior is for a parser to duplicate the working hash in this case, therefore no further data is required. A `txid` boolean is included if true - to indicate whether the hash in question is considered a relevant txid to the receiving party, rather than just a sibling hash needed to calculate the root.

### JSON Example

```json
{
  "blockHeight": 813706,
  "path": [
    [
      {
        "offset": 3048,
        "hash": "304e737fdfcb017a1a322e78b067ecebb5e07b44f0a36ed1f01264d2014f7711"
      },
      {
        "offset": 3049,
        "txid": true,
        "hash": "d888711d588021e588984e8278a2decf927298173a06737066e43f3e75534e00"
      },
      {
        "offset": 3050,
        "txid": true,
        "hash": "98c9c5dd79a18f40837061d5e0395ffb52e700a2689e641d19f053fc9619445e"
      },
      {
        "offset": 3051,
        "duplicate": true
      }
    ],
    [
      {
        "offset": 1524,
        "hash": "811ae75c80fecd27efff5ef272c2adf7edb6e535447f27a4087d23724f397106"
      },
      {
        "offset": 1525,
        "hash": "82520a4501a06061dd2386fb92fa5e9ceaed14747acc00edf34a6cecabcc2b26"
      }
    ],
    [
      {
        "offset": 763,
        "duplicate": true
      }
    ],
    [
      {
        "offset": 380,
        "hash": "858e41febe934b4cbc1cb80a1dc8e254cb1e69acff8e4f91ecdd779bcaefb393"
      }
    ],
    [
      {
        "offset": 191,
        "duplicate": true
      }
    ],
    [
      {
        "offset": 94,
        "hash": "f80263e813c644cd71bcc88126d0463df070e28f11023a00543c97b66e828158"
      }
    ],
    [
      {
        "offset": 46,
        "hash": "f36f792fa2b42acfadfa043a946d4d7b6e5e1e2e0266f2cface575bbb82b7ae0"
      }
    ],
    [
      {
        "offset": 22,
        "hash": "7d5051f0d4ceb7d2e27a49e448aedca2b3865283ceffe0b00b9c3017faca2081"
      }
    ],
    [
      {
        "offset": 10,
        "hash": "43aeeb9b6a9e94a5a787fbf04380645e6fd955f8bf0630c24365f492ac592e50"
      }
    ],
    [
      {
        "offset": 4,
        "hash": "45be5d16ac41430e3589a579ad780e5e42cf515381cc309b48d0f4648f9fcd1c"
      }
    ],
    [
      {
        "offset": 3,
        "duplicate": true
      }
    ],
    [
      {
        "offset": 0,
        "hash": "d40cb31af3ef53dd910f5ce15e9a1c20875c009a22d25eab32c11c7ece6487af"
      }
    ]
  ]
}
```

## Calculating the Merkle Root from a BUMP

Let's start by dumping this format as hex into a Buffer in JavaScript and parsing it into an object with a Buffer Reader. Then we can calculate the merkle root from any of the included txids.

```javascript
const { createHash } = require('crypto')
const { Br, Bw } = require('bsv')

// Displaying hashes as hex strings in reverse byte order is a matter of convention with respect to txids. 
// The functions below handle the conversions such that when we "hash()" something, we are running sha256 - 
// digesting the reverse bytes of a hex string, and returning the reverse bytes encoded as a hex string.
const hexRevToBuf = (str) => Buffer.from(str, 'hex').reverse()
const bufRevToHex = (buf) => Buffer.from(buf.toString('hex'), 'hex').reverse().toString('hex')
const hash = (str) => bufRevToHex(createHash('sha256').update(createHash('sha256').update(hexRevToBuf(str)).digest()).digest())

function bumpHexToJSON(str) {
  const reader = new Br()
  reader.buf = Buffer.from(str, 'hex')
  let blockHeight = reader.readVarIntNum()
  let treeHeight = parseInt(reader.read(1).toString('hex'), 16)
  let path = Array(treeHeight).fill(0).map(() => ([]))
  let flags, offset, nLeavesAtThisHeight
  for (let level = 0; level < treeHeight; level++) {
    nLeavesAtThisHeight = reader.readVarIntNum()
    while (nLeavesAtThisHeight) {
      offset = reader.readVarIntNum()
      flags = parseInt(reader.read(1).toString('hex'), 16)
      const leaf = { offset }
      if (flags & 1) {
        leaf.duplicate = true
      } else {
        if (flags & 2) leaf.txid = true
        leaf.hash = reader.read(32).reverse().toString('hex')
      }
      path[level].push(leaf)
      nLeavesAtThisHeight--
    }
    path[level].sort((a, b) => a.offset - b.offset)
  }
  return { blockHeight, path }
}

function bumpJSONtoHex({ blockHeight, path }) {
  const bw = new Bw()
  bw.writeVarIntNum(blockHeight)
  let treeHeight = path.length
  bw.writeUInt8(treeHeight)
  for (let level = 0; level < treeHeight; level++) {
    let nLeaves = Object.keys(path[level]).length
    bw.writeVarIntNum(nLeaves)
    for (const leaf of path[level]) {
      bw.writeVarIntNum(leaf.offset)
      let flags = 0
      if (!!leaf?.duplicate) flags |= 1
      if (!!leaf?.txid) flags |= 2
      bw.writeUInt8(flags)
      if ((flags & 1) === 0)
        bw.write(hexRevToBuf(leaf.hash))
    }
  }
  return bw.toBuffer().toString('hex')
}

function calculateMerkleRootFromBUMP(bump, txid) {
  // Find the index of the txid at the lowest level of the Merkle tree
  const index = bump.path[0].find(l => l.hash === txid).offset
  if (!index) throw Error(`The BUMP does not contain the txid: ${txid}`)
  // Calculate the root using the index as a way to determine which direction to concatenate.
  let workingHash = txid
  bump.path.map((leaves, height) => {
    const offset = index >> height ^ 1
    const leaf = leaves.find(l => l.offset === offset)
    if (!leaf) throw new Error(`We do not have a hash for this index at height: ${height}`)
    if (leaf.duplicate) {
      workingHash = hash(workingHash + workingHash)
    } else if (offset % 2) {
      workingHash = hash(leaf.hash + workingHash)
    } else {
      workingHash = hash(workingHash + leaf.hash)
    }
  })
  return workingHash
}


const bump = bumpHexToJSON('fe8a6a0c000c04fde80b0011774f01d26412f0d16ea3f0447be0b5ebec67b0782e321a7a01cbdf7f734e30fde90b02004e53753e3fe4667073063a17987292cfdea278824e9888e52180581d7188d8fdea0b025e441996fc53f0191d649e68a200e752fb5f39e0d5617083408fa179ddc5c998fdeb0b0102fdf405000671394f72237d08a4277f4435e5b6edf7adc272f25effef27cdfe805ce71a81fdf50500262bccabec6c4af3ed00cc7a7414edea9c5efa92fb8623dd6160a001450a528201fdfb020101fd7c010093b3efca9b77ddec914f8effac691ecb54e2c81d0ab81cbc4c4b93befe418e8501bf01015e005881826eb6973c54003a02118fe270f03d46d02681c8bc71cd44c613e86302f8012e00e07a2bb8bb75e5accff266022e1e5e6e7b4d6d943a04faadcf2ab4a22f796ff30116008120cafa17309c0bb0e0ffce835286b3a2dcae48e4497ae2d2b7ced4f051507d010a00502e59ac92f46543c23006bff855d96f5e648043f0fb87a7a5949e6a9bebae430104001ccd9f8f64f4d0489b30cc815351cf425e0e78ad79a589350e4341ac165dbe45010301010000af8764ce7e1cc132ab5ed2229a005c87201c9a5ee15c0f91dd53eff31ab30cd4')

calculateMerkleRootFromBUMP(bump, '304e737fdfcb017a1a322e78b067ecebb5e07b44f0a36ed1f01264d2014f7711') // '57aab6e6fb1b697174ffb64e062c4728f2ffd33ddcfa02a43b64d8cd29b483b4'
calculateMerkleRootFromBUMP(bump, 'd888711d588021e588984e8278a2decf927298173a06737066e43f3e75534e00') // '57aab6e6fb1b697174ffb64e062c4728f2ffd33ddcfa02a43b64d8cd29b483b4'
calculateMerkleRootFromBUMP(bump, '98c9c5dd79a18f40837061d5e0395ffb52e700a2689e641d19f053fc9619445e') // '57aab6e6fb1b697174ffb64e062c4728f2ffd33ddcfa02a43b64d8cd29b483b4'
bumpJSONtoHex(bump)
```

## Merging

A note on compounding multiple BUMPs together. The first check should always be the blockHeight - ensure it matches. The second check is the root. Each BUMP calculates its root, and if they don't match - you cannot combine them. If they match then the process is a simple inclusion of all leaves, dropping duplicates.

```javascript
function combinePaths(one, two) {
  if (one.blockHeight !== two.blockHeight) 
    throw Error('You cannot combine paths which do not have the same blockHeight.')
  const txid1 = one.path[0].find(leaf => !!leaf?.hash).hash
  const root1 = calculateMerkleRootFromBUMP(one, txid1)
  const txid2 = two.path[0].find(leaf => !!leaf?.hash).hash
  const root2 = calculateMerkleRootFromBUMP(two, txid2)
  if (root1 !== root2) 
    throw Error('You cannot combine paths which do not have the same root.')
  const combinedPath = []
  for (let h = 0; h < one.path.length; h++) {
    combinedPath.push([])
    for (let l = 0; l < one.path[h].length; l++) {
      combinedPath[h].push(one.path[h][l])
    }
    for (let l = 0; l < two.path[h].length; l++) {
      if (!combinedPath[h].find(leaf => leaf.offset === two.path[h][l].offset)) {
        combinedPath[h].push(two.path[h][l])
      } else {
        // Ensure that any elements which appear in both are not downgraded to a non txid.
        if (!!two.path[h][l]?.txid) 
          combinedPath[h].find(leaf => leaf.offset === two.path[h][l]).txid = true
      }
    }
  }
  return { blockHeight: one.blockHeight, path: combinedPath }
}
```

## Implementations

TypeScript - [`@bsv/sdk` in ts-stack](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk) Golang - [go-sdk](https://github.com/bsv-blockchain/go-sdk)\
Python - [py-sdk](https://github.com/bsv-blockchain/py-sdk)


# Graph Aware Sync Protocol

Ragnar Friedman <ragnar.friedman@proton.me>

## Abstract

GASP is designed to synchronize transaction data between two parties in a blockchain environment. It ensures the legitimacy and completeness of transaction data using a recursive reconciliation method.

## Status Note

This document is an exploratory graph synchronization proposal. It is not the current wallet-toolbox user wallet storage synchronization protocol. Current wallet-toolbox storage synchronization is the chunked, resumable storage-provider protocol specified by [BRC-40](/outpoints/0040). Current transaction exchange and wallet APIs generally use BEEF / Atomic BEEF rather than the bloom-filter INV flow described here.

## Participants

* **Alice**: Initiates the sync process.
* **Bob**: Responds and participates in the sync process.

## Protocol Steps

1. **Initialization**
   * Alice starts by sending a bloom filter containing all current spendable TXID+VOUTs as elements.
2. **Receiving and Building List**
   * Bob receives the filter and builds a list of his items that are not members of the set.
3. **Transaction Verification**
   * Bob sends an INV (Inventory) message to Alice for each item not in the set.
   * The INV includes:
     * Output, transaction, and associated merkle proof.
     * Any metadata (such as labels, descriptions, or local timestamps) associated with transactions or outputs.
     * A list of VOUTs spent by its inputs and associated metadata hashes.
     * The preimage for each hash, including all metadata and the merkle proof or broadcast response.
4. **Alice's Response**
   * For each INV, Alice responds with a list of input transactions she does not know about.
   * If Alice has the transaction but the metadata hash differs, she requests updated metadata.
   * If Alice lacks the transaction, she requests the entire transaction.
5. **Recursive Transaction Sync**
   * Bob responds to Alice's requests with an INV containing the encompassing transaction, done recursively.
     * When including the full transaction, all metadata is provided.
     * When only metadata is included, it encompasses the list of spent VOUTs and associated metadata hashes.
6. **Error Handling and Recovery**
   * In case of errors, affected transactions are ignored and not synced.
   * If errors prevent a party from fully anchoring transactions back to the blockchain, these transactions are ignored.
   * Failures experienced by one party are not communicated to the other due to the declaratory nature of the protocol.
7. **Finalization of Sync**
   * The process continues until there are no more INVs for Bob to send.
   * Once complete, all of Bob's records are considered synced with Alice.
8. **Role Reversal**
   * The roles reverse, with Bob sending Alice a bloom filter.
   * The parties then exchange data in the other direction, following the same steps.

## Security Measures

* Verification of merkle proofs and the longest chain of block headers.
* Recursively requesting information until all inputs are fully proven.
* Invalidating transactions that cannot be linked back to a valid proof.

## Notes

* The protocol is adaptable to various blockchain environments.
* The recursive nature ensures thorough and complete data synchronization.
* The protocol emphasizes security, efficiency, and data integrity.

## Conclusion

GASP offers a robust and secure method for synchronizing transaction data between parties in a blockchain network, leveraging recursive data exchange and thorough verification mechanisms.


# Scalable Transaction Processing in the BSV Network

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The BSV network is designed to process transactions at scale, leveraging a robust, distributed system of miners, validators, and aggregators. This system balances parallel processing with sequential validation, ensuring transactions are both processed rapidly and recorded in the correct order. Below, we document the detailed processes by which BSV miners will come to handle transactions at scale.

## 1. Transaction Submission

Users and services across the globe create and sign Bitcoin transactions, broadcasting them to the network. The network's miners collect these transactions at numerous Points of Presence (PoPs), which act as edge nodes in the BSV ecosystem. The following steps outline the process:

* **Preliminary Checks:** Upon receiving a transaction, miners at PoPs verify its scripts, ensuring they comply with network standards and contain valid signatures. If valid, the transaction proceeds to the next stage.
* **Batching:** After initial verification, the miners batch transactions, preparing them for forwarding to regional aggregators. This batching process occurs periodically, facilitating efficient transfer and further processing.

## 2. Transaction Stripping

After initial verification, transactions undergo a stripping process, which reduces each transaction to its essential elements:

* **Stripped Data:** The remaining data includes:
  * The TXID and output indices of the consumed outputs.
  * The TXID of the transaction itself.
  * The number of new outputs it creates.
* **Graph Representation:** The stripped transaction thus forms a node in the transaction graph, containing only the necessary information to link it to other transactions.

## 3. Edge-Level Stripped Transaction Graph Summarization

Where possible, edge validators summarize stripped transaction graphs before sending them to regional aggregators. This process is crucial in reducing the workload of higher-level aggregators:

* **Internal Transactions:** The summarized graph only needs to include:
  * The TXIDs and output indices of previously consumed outputs.
  * The newly created outputs.
  * A merkle root for the transactions it includes.
  * The total amount of fees left for the miners.
* **Merkle Paths:** The summarized graph also contains partial merkle paths, linking all transactions or child graphs to the merkle root of the summarized graph. However, this information only needs to be retained locally and never shared upwards.

## 4. Regional Aggregation

After receiving stripped transactions and summarized graphs, regional aggregators build subtrees of transactions:

* **Subtree Formation:** Subtrees are groups of transactions or graphs, each forming a summarized graph with a merkle root and lists of outputs consumed and created.
* **Multi-Level Aggregation:** Multiple levels of regional aggregation may occur in parallel, gradually integrating more of the completed transaction graph into larger structures.
* **Fee Tracking:** Fees from child graphs are aggregated to track the total fees collected by the parent graph.

## 5. Global Aggregation and Block Assembly

Regional aggregators send subtree summaries to global aggregation systems, which then prepare the final block:

* **Graph Consistency:** The system checks graph edges for consistency, ensuring no edge is consumed by one graph before being created by another.
* **Block Template:** The block assembler combines these subtrees, including the coinbase transaction with the correct amount of fees, forming a finalized block template ready for hashing.
* **Proof of Work:** Upon finding a valid proof-of-work header, the new block header is immediately propagated throughout the network for parallel validation by all systems.

## 6. Proof Completion

Once a valid header is found, it triggers a cascading parallel proof completion process:

* **Downstream Propagation:** The header propagates down to all regional aggregators, who append the upper layers of the merkle tree to their subgraph roots.
* **Notifier Chain:** Each regional aggregator notifies its child aggregators or edge validators, which in turn notify their children. This chain of notifications continues until all edge validators have completed proofs for all transactions they submitted.
* **End-User Notification:** Finally, edge validators notify network end-users of the completed proofs available for their transactions.

## 7. Summary

The BSV network effectively balances parallel processing and sequential validation to achieve scalable transaction processing. By employing edge-level validation, regional aggregation, and global assembly, the system reduces the workload at higher levels, ensuring rapid, consistent processing. This layered, hierarchical approach to aggregation, alongside the use of transaction stripping and summarization, makes the BSV network a robust solution for scalable blockchain processing.

## 8. Future Work: Modeling the BSV Network

To model and describe the BSV network quantitatively, it is essential to focus on the parameters and metrics that influence the performance and scalability of the system. Here are some key components and steps to construct a quantitative model of the BSV transaction processing system:

### 8.1. Parameters and Metrics

#### Key Parameters

* **Transaction Arrival Rate**: The rate at which transactions are received at edge nodes, typically expressed in transactions per second (tps).
* **Batch Size**: The number of transactions processed together in batches at edge nodes before being sent to regional aggregators.
* **Transaction Processing Time**: Time taken to verify and strip a single transaction.
* **Propagation Delay**: The time it takes for information to travel from the edge nodes to the regional and global aggregators.
* **Aggregation Time**: Time required to aggregate transactions into subtrees and summarize them into merkle roots at various levels (edge, regional, global).

#### Performance Metrics

* **Throughput**: The total number of transactions processed per unit time across the network.
* **Latency**: The total time taken from when a transaction is submitted until it is included in a validated block.
* **Resource Utilization**: Measures how efficiently resources (computational, bandwidth) are used.
* **Scalability**: The ability of the network to handle increasing transaction loads without a proportional increase in latency or resource costs.

### 8.2. Modeling Steps

#### Step 1: Transaction Input Modeling

* Model the transaction inputs as a Poisson process or another suitable stochastic process that reflects real-world arrival patterns.

#### Step 2: Process Modeling at Edge Nodes

* Model the batch processing as a queue where transactions are collected, verified, and stripped. Queuing theory, specifically a batch service queue model, can be applied here.
* Calculate the processing rate of batches based on the transaction processing time and batch size.

#### Step 3: Summarization and Aggregation Modeling

* Use a hierarchical tree model to represent the multi-level aggregation process. Each node in the tree represents an aggregation point (edge, regional, global).
* Model the propagation of transaction summaries through this tree, accounting for delays and processing times at each node.

#### Step 4: Block Assembly and Propagation

* Model the final block assembly as a sequential process that only begins once all necessary summaries and proofs are available.
* Consider the proof-of-work process and its impact on block propagation and finality.

#### Step 5: Analytical Model Construction

* Construct a series of equations or a simulation model that integrates these components. For example, use differential equations to represent the rate of change in the queue sizes, or develop a discrete-event simulation to model the dynamic interactions and delays.

#### Step 6: Validation and Simulation

* Validate the model against real data or simulated data to ensure it accurately represents the BSV network's behavior.
* Use the model to simulate different scenarios, such as increased transaction loads, changes in batch sizes, or different network delays, to study their impacts on throughput, latency, and scalability.

### 8.3. Utilizing the Model

* **Optimization**: Use the model to find optimal parameters (e.g., batch size, number of edge nodes) that maximize throughput or minimize latency.
* **Capacity Planning**: Estimate the resources needed to achieve certain performance metrics under expected future loads.
* **Scenario Analysis**: Assess the impact of network changes or growth on performance and resource needs.

By quantitatively modeling these processes, the BSV network's design and operation can be better understood, optimized, and scaled to meet the demands of a high-volume, real-time transaction processing environment.


# Atomic BEEF Transactions

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This BRC defines Atomic Background Evaluation Extended Format (Atomic BEEF) Transactions, a variant of the BEEF transaction format (as defined in [BRC-62](/transactions/0062)) that focuses on atomicity—specifically, ensuring that all the data within a BEEF structure relates to a single "subject transaction." This format disallows unrelated transactions within the BEEF structure, reinforcing a clear, minimalistic data set for transaction validation while maintaining efficiency in bandwidth usage.

Atomic BEEF is designed to solve problems arising from multiple graphs of unrelated transactions being aggregated into a single BEEF, which can complicate validation and increase data complexity. Atomic BEEF introduces a strict structure optimized for single-transaction use cases, ensuring that every transaction in the BEEF relates to a defined subject transaction. This format is particularly suited for applications where the verification of a singular transaction and its direct ancestry is critical.

## Motivation

The introduction of the BEEF format (BRC-62) provided a solution for transmitting transaction data in a binary format to allow [Simplified Payment Verification](/transactions/0067) (SPV) with minimal bandwidth. However, BEEF allows the inclusion of multiple, potentially unrelated transactions aggregated within the same structure, making it flexible but not ideal for cases where strict atomicity is needed.

Atomic BEEF is motivated by the following challenges:

1. **Transaction Ambiguity**: In BEEF, there is no explicit constraint to keep transactions related to a single subject transaction. This can lead to ambiguity when unrelated transaction graphs are included in the same BEEF structure, making validation and verification more complex.
2. **Simplified Use Cases**: Many use cases of SPV revolve around validating a single payment or transaction, not multiple. Atomic BEEF optimizes for this by focusing entirely on the recursive transaction graph relevant to a specific transaction.
3. **Data Minimalism**: For micropayments or systems requiring highly efficient data transfer, limiting the number of transactions included in the BEEF to only the required set for validation of a single subject transaction reduces unnecessary overhead.
4. **Streamlined Validation**: By restricting the BEEF to a single subject transaction and its dependencies, Atomic BEEF reduces the complexity of the validation process, as there is a clear expectation of the data's scope.

In response to these motivations, Atomic BEEF introduces a new mechanism for enforcing the atomicity of BEEF transactions.

## Specification

Atomic BEEF is an extension of BEEF (BRC-62) with specific structural and validation rules that ensure atomicity. It adheres to the same encoding and transaction structure principles, with the following additional constraints:

### Header Structure

Atomic BEEF introduces a unique header prefix that ensures the atomicity of the BEEF structure. The Atomic BEEF format starts with a constant prefix followed by the TXID of the "subject transaction." This TXID serves as the reference transaction that the entire BEEF structure must relate to.

* **Prefix**: The first 4 bytes of the Atomic BEEF structure must be `0x01010101`, a fixed constant to indicate Atomic BEEF.
* **Subject TXID**: The next 32 bytes are the TXID of the "subject transaction"—the primary transaction that the rest of the structure must validate and verify.

| Field         | Description                                                                  | Size     |
| ------------- | ---------------------------------------------------------------------------- | -------- |
| Atomic Prefix | Fixed constant `0x01010101`, indicates the start of an Atomic BEEF structure | 4 bytes  |
| Subject TXID  | The TXID of the subject transaction that this BEEF structure will validate   | 32 bytes |

### Transaction Inclusion Rules

Atomic BEEF strictly enforces that all transactions included in the structure must be part of the subject transaction's dependency graph. This means that every transaction in the BEEF structure must either be:

1. The subject transaction itself, or
2. An ancestor transaction required to validate the inputs of the subject transaction.

Transactions that fall outside of this graph, or are unrelated, are not allowed in Atomic BEEF. This ensures that the BEEF is entirely focused on a single, verifiable transaction and its direct dependencies.

### Validation Process

The validation of an Atomic BEEF structure follows these steps:

1. **Subject Transaction Identification**:
   * The first step in validation is to extract the subject TXID from the header. If the BEEF structure does not include the subject transaction or if any unrelated transactions are present, the validation fails.
2. **Transaction Graph Validation**:
   * The BEEF structure must contain all transactions necessary to validate the subject transaction, recursively including all ancestor transactions up to the point where each input can be confirmed with a Merkle proof.
   * The structure must not contain any transactions that are not part of the subject transaction's dependency graph.
3. **Merkle Proof Validation**:
   * As with the original BEEF format, any included transactions that have been mined must be accompanied by BSV Universal Merkle Path (BUMP) data to prove their inclusion in the longest chain of blocks.
   * The Merkle roots derived from the BUMP data are verified against the local header service.
4. **Final Transaction Validation**:
   * The final validation is complete when all necessary transactions (including script evaluations) and proofs have been processed, and the subject transaction is confirmed to be valid based on its dependencies.

### Error Conditions

Atomic BEEF introduces two specific error conditions that must cause validation to fail:

1. **Missing Subject Transaction**: If the BEEF structure does not include the subject transaction identified by the subject TXID in the header, validation fails.
2. **Unrelated Transactions**: If any transactions within the BEEF structure fall outside the dependency graph of the subject transaction, validation fails.

These error conditions ensure that Atomic BEEF maintains strict atomicity and avoids the ambiguity and complexity of unrelated transactions within the same structure.

## Bytewise Breakdown:

```
01010101 // Atomic BEEF Prefix
2222222222222222222222222222222222222222222222222222222222222222 // Subject TXID
0100beef // Start of standard BEEF structure...
...
```


# BEEF V2 Txid Only Extension

Tone Engel (<tone@projectbabbage.com>)

## Abstract

The BEEF serialization format (as defined in [BRC-62](/transactions/0062)) is essentially two things:

1. An array of mined transaction proof validation data (BUMPS [BRC-74](/transactions/0074))
2. An array serialized Bitcoin transactions

In practical use, BEEFs are exchanged between parties to build and process new transactions as well as validate arbitrary data recorded on the blockchain.

The extension proposed here is to allow for "agreed upon transactions" to be represented in the array of transactions as just their transaction hash (txid),

This avoids the need to include potentially significant amounts of data already known to the parties exchanging BEEFs.

In addition, this extension formally acknowledges that a BEEF may contain mulitple transaction "roots".

## Motivation

Consider when two parties cooperate over a short amount of time to construct one or more transactions.

Inputs may be added by either party. New inputs may come from unmined and mined transactions.

At each exchange of information along this process, one party sends a BEEF to the other to validate the new inputs or transaction(s) they originate.

The BEEF standard is well suited to this with two extensions:

1. In the general case, a BEEF does not need to be a single transaction tree.
2. A method of referencing what the process has previously validated.

For example, when adding new inputs to an incomplete transaction, the set of source transactions for these inputs may need to be transmitted to a second party. A BEEF of this validation information might include multiple trees of unmined transactions.

Furthermore, some of these inputs might be from transactions created earlier in the process for which complete validation data - back to mined transactions - has already been shared.

Consider in particular if some of these transactions are truly large or if the rate of linked transaction generation is high.

## Specification

The serialized format for the transaction array is updated and the BEEF version number is incremented to `0200BEEF`.

For each serialized transaction, the byte previously used to indicate BUMP (`01`) or no BUMP (`00`) is renamed the Tx Data Format, it is now the first thing written for each transaction, and the value of (`02`) is now used when only a 32 byte txid is serialized.

| Field           | Description                                                                                                    | Size                |
| --------------- | -------------------------------------------------------------------------------------------------------------- | ------------------- |
| Version no      | Version number starts at 4022206466, encoded Uint32LE => `0200BEEF`                                            | 4 bytes             |
| nBUMPs          | VarInt number of BSV Unified Merkle Paths which follow                                                         | 1-9 bytes           |
| BUMP data       | All of the BUMPs required to prove inclusion of inputs in longest chain of blocks [BRC-74](/transactions/0074) | many bytes x nBUMPs |
| nTransactions   | VarInt number of transactions which follow                                                                     | 1-9 bytes           |
| Tx Data Format  | `00` raw transaction without BUMP index; `01` raw transaction followed by BUMP index; `02` txid only           | 1 byte              |
| BUMP index      | Format `01`: VarInt index number indicating the BUMP to which Raw Transaction belongs.                         | 1-9 bytes           |
| Raw Transaction | Format `00` or `01`: RawTx bytes as in standard format [BRC-12](/transactions/0012)                            | many bytes          |
| txid only       | Format `02`: 32 byte transaction hash in reverse byte order                                                    | 32 bytes            |

### Validation Semantics

A txid only transaction is treated as implicitly valid, no raw transaction or BUMP index is included and no BUMP data is required.

Additional transactions that consume outputs from a txid only transaction treat those inputs as fully validated.

The ordering of transactions obeys the V1 rules: Parents, including txid only, must occur before children.

## Discussion of Alternatives

### A1: Why not just send multiple beefs with one rooted transaction in each?

The essential function of the BEEF format is to efficiently represent transaction validation data.

When multiple beefs are merged, all common BUMPS, merkle paths, and parent transactions are collapsed to a single copy.


# SubTree Unified Merkle Path (STUMP) Format

Dylan Murray (<dylan.murray@bsvassociation.org>) Darren Kellenschwiler (<d.kellenschwiler@bsvassociation.org>) Siggi Oskarsson (<siggi.oskarsson@bsvassociation.org>)

## Abstract

We define the SubTree Unified Merkle Path (STUMP) format for encoding merkle paths within a subtree of a block, rather than across an entire block. A STUMP adapts the binary encoding specified in [BRC-74](/transactions/0074) (BSV Unified Merkle Path / BUMP) for subtree scope, replacing the block height with a single-byte tree height header and an optional trailing block hash. The level and leaf encoding is identical to BRC-74. This enables scalable merkle proof generation and delivery in architectures where blocks are partitioned into subtrees for parallel processing.

## Copyright

This BRC is licensed under the Open BSV license.

## Motivation

As Bitcoin blocks scale to contain millions of transactions, computing and transmitting full-block merkle paths becomes increasingly expensive. Modern transaction processors such as Teranode partition each block's transactions into subtrees, each containing a bounded number of transaction IDs. Subtrees are announced, stored, and processed independently, enabling horizontal scaling of merkle proof infrastructure.

The challenge is delivering merkle proofs for registered transactions as quickly as possible after a block is mined. A service that processes subtrees independently needs a format to represent partial merkle paths within a single subtree. These subtree-scoped proofs can then be delivered via callbacks and later composed with the block-level merkle path (from subtree root to block merkle root) to produce a complete [BRC-74](/transactions/0074) BUMP for SPV verification.

STUMP solves this by adapting the BRC-74 level and leaf encoding for subtree scope. The block height field present in BUMP is omitted — it is not meaningful at the subtree level and would waste bytes in every encoded STUMP. Instead, the header is a single byte: the tree height. An optional 32-byte block hash may be appended after the final level. The block hash is optional because STUMPs are typically built before the block hash is known, and can be associated with a block hash later via delivery metadata.

This design means:

* The level and leaf encoding is identical to BRC-74 — existing parsers need only handle the different header.
* STUMPs are self-delimiting, so multiple STUMPs can be concatenated safely.
* STUMPs can be merged using the same algorithm defined in BRC-74 for combining BUMPs.
* The header is a single byte (tree height). The optional block hash is appended at the end, requiring no flags or version bytes.

## Specification

### Relationship to BRC-74

A STUMP shares the same level and leaf encoding as [BRC-74](/transactions/0074) BUMP, but differs in scope and header:

* The **block height** field present in BUMP is omitted. Block height is not meaningful at subtree scope and is conveyed separately via delivery metadata or composition with the block-level path.
* An optional 32-byte **block hash** may be appended after the final level. Because the level encoding is self-delimiting, a parser can detect its presence by checking for 32 remaining bytes after parsing all levels. The block hash is optional because STUMPs are typically constructed during subtree processing, before the block hash is known.
* The **tree height** reflects the height of the subtree's internal merkle tree, not the full block merkle tree.
* The **leaf offsets** at level 0 are positions within the subtree (0-indexed from the first transaction in the subtree), not positions within the full block.
* The **root** computed by walking the path is the subtree merkle root, not the block merkle root.

The level and leaf encoding (VarInt offsets, flag semantics, 32-byte hashes, duplicate handling) is identical to BRC-74.

### Binary Encoding

#### Header

| Field       | Description                                          | Size   |
| ----------- | ---------------------------------------------------- | ------ |
| tree height | Height of the subtree's internal merkle tree, max 64 | 1 byte |

#### Level

Repeated `treeHeight` times (level 0 through `treeHeight - 1`):

| Field   | Description                             | Size              |
| ------- | --------------------------------------- | ----------------- |
| nLeaves | `VarInt` number of leaves at this level | 1-9 bytes         |
| leaves  | Each leaf encoded in the format below   | sum of leaf sizes |

#### Leaf

| Field  | Description                                                                 | Size          |
| ------ | --------------------------------------------------------------------------- | ------------- |
| offset | `VarInt` offset from left hand side within the subtree's tree at this level | 1-9 bytes     |
| flags  | `00`, `01`, or `02` - detailed meaning in table below                       | 1 byte        |
| hash   | A hash representing a txid, sibling hash, or internal node                  | 0 or 32 bytes |

#### Flags

| bits      | byte | meaning                                 |
| --------- | ---- | --------------------------------------- |
| 0000 0000 | 00   | data follows, not a client txid         |
| 0000 0001 | 01   | nothing follows, duplicate working hash |
| 0000 0010 | 02   | data follows, and is a client txid      |

#### Trailing Block Hash (Optional)

| Field      | Description                                       | Size     |
| ---------- | ------------------------------------------------- | -------- |
| block hash | SHA256d hash of the block containing this subtree | 32 bytes |

After parsing all levels, if exactly 32 bytes remain in the buffer, they are the block hash. If 0 bytes remain, no block hash is present. Because the level and leaf encoding is self-delimiting (the tree height determines the number of levels, each level declares its leaf count, and each leaf's flags determine whether a 32-byte hash follows), a parser always knows the exact byte position where levels end.

### Tree Height Calculation

Given `n` leaf transactions in a subtree, the tree is padded to the next power of two. The tree height is the number of levels from the leaves to the root, exclusive of the root:

```
nextPowerOfTwo = smallest power of 2 >= n
treeHeight = log2(nextPowerOfTwo)
```

For example:

* 2 leaves: treeHeight = 1
* 3-4 leaves: treeHeight = 2
* 5-8 leaves: treeHeight = 3
* 9-16 leaves: treeHeight = 4
* 10 leaves (padded to 16): treeHeight = 4

The root hash (at level `treeHeight`) is never encoded, as it is always calculable from the path data.

### Duplicate Handling

When a subtree has a non-power-of-two number of transactions, leaf positions beyond the real transaction count are padded. During merkle tree construction, a node whose right child is a padding node duplicates its left child hash. In the STUMP encoding, these positions are represented with `flags = 0x01` (duplicate), and no hash data follows. A verifier encountering this flag duplicates the current working hash in place of reading a sibling.

### Building a STUMP

Given a subtree with `n` leaf transactions and a set of registered transaction indices:

1. Pad the leaf array to the next power of two and compute the full internal merkle tree using double-SHA256: `SHA256(SHA256(left || right))`.
2. Calculate `treeHeight` as described above.
3. For each level from 0 to `treeHeight - 1`:
   * At level 0, include each registered txid with `flags = 0x02` and its 32-byte hash.
   * At every level, include the sibling of each tracked index:
     * Sibling index = `index XOR 1`.
     * If the sibling is beyond the real node count at this level, mark it `flags = 0x01` (duplicate, no hash).
     * Otherwise, include the sibling's hash with `flags = 0x00`.
     * If the sibling is also a registered txid (at level 0), mark it `flags = 0x02`.
   * Advance tracked indices to the next level: `index = index / 2`.
   * Sort all leaves at this level by offset for deterministic output.

### Verifying a STUMP

Verification follows the standard BRC-74 algorithm, computing the subtree merkle root:

1. For each registered txid at level 0, set `workingHash = txid hash` and `offset = txid offset`.
2. For each level from 0 to `treeHeight - 1`:
   * Find the sibling at `offset XOR 1`.
   * If the sibling has `flags = 0x01`, set `siblingHash = workingHash` (duplicate).
   * Otherwise, `siblingHash` is the sibling's 32-byte hash.
   * If `offset` is even: `workingHash = SHA256(SHA256(workingHash || siblingHash))`.
   * If `offset` is odd: `workingHash = SHA256(SHA256(siblingHash || workingHash))`.
   * `offset = offset / 2`.
3. The final `workingHash` should equal the known subtree merkle root.

### Merging STUMPs

Multiple STUMPs from the same subtree (e.g., built for different sets of registered transactions) can be merged using the same algorithm as BRC-74 BUMP merging:

1. Verify that all STUMPs share the same `treeHeight` (and `blockHash`, if present).
2. For each level, collect all leaves from all STUMPs.
3. Deduplicate by offset. If the same offset appears in multiple STUMPs, prefer the leaf with `TxID = true` (flags `0x02`) over one without.
4. Sort the merged leaves by offset.

Because STUMPs are self-delimiting, multiple encoded STUMPs can also be safely concatenated into a single byte stream. A receiver can parse them sequentially, reading each STUMP's header and levels in order.

### Callback Delivery

In a typical deployment, STUMPs are delivered to registered callback URLs as part of a JSON payload:

```json
{
  "txids": ["aabbcc...", "ddeeff..."],
  "status": "MINED",
  "stumpData": "<base64-encoded STUMP binary>",
  "blockHash": "000000..."
}
```

The `stumpData` field contains the STUMP binary as a base64 string. When a callback carries multiple STUMPs (from different subtrees in the same block), the binary representations are concatenated before base64 encoding.

The `blockHash` in the JSON payload provides block context regardless of whether the STUMP itself includes a block hash in its header. When the block hash is known at STUMP construction time, it may be encoded in the STUMP header as well; when it is not yet known (e.g., during subtree processing before the block is finalized), the JSON payload provides it after the fact.

## Composing a Full BUMP

A STUMP proves that a transaction exists within a subtree. To produce a complete SPV proof linking the transaction to a block header, a receiver must compose the STUMP with an additional merkle path from the subtree root to the block merkle root. This composition yields a standard BRC-74 BUMP suitable for full SPV verification as described in [BRC-67](/transactions/0067).

The composition process:

1. Verify the STUMP against the known subtree merkle root.
2. Obtain the merkle path from the subtree root to the block merkle root (e.g., from the block-level merkle tree or a coinbase BEEF envelope).
3. Concatenate the STUMP path levels with the subtree-to-root path levels, adjusting offsets appropriately for the subtree's position within the block.

## Hex Examples

### Without Block Hash

A STUMP with tree height 1, containing two registered txids that are each other's sibling:

#### Hex String

```
0102000211c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed8664710102baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5
```

#### Bytewise Breakdown

```
01     // treeHeight (1), byte
// Level 0
02     // nLeaves (2), VarInt
00     // offset (0), VarInt
02     // leaf flags = CLIENT_TXID
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471 // 32-byte txid
01     // offset (1), VarInt
02     // leaf flags = CLIENT_TXID
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5 // 32-byte txid
// No trailing bytes — no block hash
```

### With Block Hash

The same STUMP with a block hash appended:

#### Hex String

```
0102000211c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed8664710102baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b59fd16010222eea8719743ed124d41b7cf7a555137647b0ac9b12114339b2fff9
```

#### Bytewise Breakdown

```
01     // treeHeight (1), byte
// Level 0
02     // nLeaves (2), VarInt
00     // offset (0), VarInt
02     // leaf flags = CLIENT_TXID
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471 // 32-byte txid
01     // offset (1), VarInt
02     // leaf flags = CLIENT_TXID
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5 // 32-byte txid
// Trailing 32 bytes — block hash
9fd16010222eea8719743ed124d41b7cf7a555137647b0ac9b12114339b2fff9 // 32-byte block hash
```

## Implementations

Golang - [merkle-service](https://github.com/bsv-blockchain/merkle-service) (`internal/stump` package)

## References

* [BRC-74: BSV Unified Merkle Path (BUMP) Format](/transactions/0074)
* [BRC-67: Simplified Payment Verification](/transactions/0067)
* [BRC-61: Compound Merkle Path Format](/transactions/0061)
* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062)


# Multicast Transaction Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the wire format for transporting BSV transactions over IPv6 multicast and TCP/UDP unicast. The format extends the legacy BRC-12 frame with additional fields for flow identification, sequence tracking, and subtree identification. All fields are 8-byte aligned for efficient memory access on 64-bit architectures. The frame is transport-agnostic: the on-wire bytes are identical under Any-Source (ASM) and Source-Specific (SSM) multicast. Multicast group addressing, scopes, and the SSM `(S,G)` join and source-discovery model are specified in [BRC-129](/transactions/0129); the frame's source is the emitting proxy (`senderIPv6`, the first `HashKey` ingredient).

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

As BSV transaction throughput scales to billions of transactions per second, efficient distribution infrastructure becomes critical. IPv6 multicast provides a scalable one-to-many transport mechanism, but globally scalable transaction distribution requires:

1. **Deterministic sharding** — routing transactions to specific multicast groups based on transaction ID
2. **Subtree identification** — filtering batches of related transactions at the network layer
3. **Flow and Sequence tracking** — stable per-flow hashing and monotonic sequencing for gap tracking and NACK-based retransmission

This frame format addresses these requirements while maintaining compatibility with the existing BRC-12 transaction payload format. It uses a 92-byte header that provides the metadata necessary for high-throughput multicast distribution with best-effort reliability, complementing the legacy 44-byte header defined in BRC-12.

**Conceptual Attribution:** The IPv6 multicast transaction broadcast architecture from which this software draws inspiration was articulated by Dr. Craig S. Wright in [Multicast Within Multicast: Anycast, Sharded Resends, and Hierarchical Distribution for Transaction and Block Propagation](https://singulargrit.substack.com/p/multicast-within-multicast-anycast).

## Specification

### Frame Structure Overview

A frame consists of a 92-byte header followed by a variable-length payload containing a BRC-12 raw transaction. All multi-byte integers are big-endian. All fields from offset 8 onward are 8-byte aligned.

### Header Format (92 bytes)

| Offset | Size | Alignment | Field            | Description                                   |
| ------ | ---- | --------- | ---------------- | --------------------------------------------- |
| 0      | 4    | —         | Network Magic    | `0xE3E1F3E8` (BSV mainnet P2P magic)          |
| 4      | 2    | —         | Protocol Version | `0x02BF` (703, BSV large-block baseline)      |
| 6      | 1    | —         | Frame Version    | `0x02`                                        |
| 7      | 1    | —         | Reserved         | Must be `0x00`                                |
| 8      | 32   | 8-byte    | Transaction ID   | Raw 256-bit TXID (internal byte order)        |
| 40     | 8    | 8-byte    | HashKey          | Stable per-flow XXH64 identifier; `0` = unset |
| 48     | 8    | 8-byte    | SeqNum           | Monotonic per-flow counter; `0` = unset       |
| 56     | 32   | 8-byte    | Subtree ID       | 32-byte batch identifier; zeros = unset       |
| 88     | 4    | 8-byte    | Payload Length   | `uint32` BE                                   |
| 92     | \*   | —         | Payload          | BRC-12 raw transaction bytes                  |

### Field Definitions

#### Network Magic (bytes 0–3)

The value `0xE3E1F3E8` (BSV mainnet P2P network magic). This enables standard BSV firewall rules and network monitoring tools to correctly classify shard frames. Frames with incorrect magic are rejected.

#### Protocol Version (bytes 4–5)

The value `0x02BF` (703 in decimal), representing the BSV node protocol version that introduced the large-block policy. This field is informational; receivers do not validate it.

#### Frame Version (byte 6)

The value `0x02` for frames following this specification. The legacy format defined in BRC-12 uses `0x01` (see Compatibility section). Any other value causes the frame to be rejected.

#### Reserved (byte 7)

Must be `0x00`. Reserved for future protocol extensions.

#### Transaction ID (bytes 8–39)

The 32-byte transaction hash in internal byte order (as used in BSV P2P protocol), **not** the reversed display order used by block explorers.

#### HashKey (bytes 40–47)

A 64-bit unsigned integer (big-endian) containing a stable per-flow identifier computed as:

```
HashKey = XXH64(senderIPv6 [16B] ∥ groupIdx [4B BE] ∥ subtreeID [32B])
```

The 52-byte hash input combines the sender's IPv6 address, the derived multicast group index, and the subtree identifier to produce an 8-byte key that is constant for all frames in a flow. This enables per-flow gap tracking, NACK dispatch, and cache lookup without carrying the full 52 bytes in every frame.

This field is stamped in-place by the ingress proxy before multicast forwarding. Senders (generators) set it to `0`; a value of `0` indicates the frame has not been stamped and gap tracking is skipped.

#### SeqNum (bytes 48–55)

A 64-bit unsigned integer (big-endian) containing a monotonic counter per flow, starting at 1. Incremented by 1 for each frame in a `(sender, group, subtree)` flow. Used by receivers to detect gaps: a gap is detected when the incoming `SeqNum` advances by more than 1 from the last-seen value for the same `HashKey`.

The pair `(HashKey, SeqNum)` forms the 16-byte cache key used by retry endpoints for NACK-based retransmission.

This field is stamped in-place by the ingress proxy alongside `HashKey`. A value of `0` indicates the frame has not been stamped.

#### Subtree ID (bytes 56–87)

A 32-byte opaque batch identifier. A subtree is an ordered set of related transactions sharing a common batch context. This field enables downstream subscribers to filter frames by batch at the application layer. All-zero bytes indicate the field is unset.

#### Payload Length (bytes 88–91)

A 32-bit unsigned integer (big-endian) specifying the number of payload bytes immediately following the header. It is up to the application to determine the maximum allowed payload size.

#### Payload (byte 92 onward)

Raw serialized BSV transaction in BRC-12 format: version (4 bytes LE) + input vector + output vector + locktime (4 bytes LE). No additional envelope wraps the transaction.

### Alignment Verification

| Field          | Offset | Offset % 8 |
| -------------- | ------ | ---------- |
| Transaction ID | 8      | 0 ✓        |
| HashKey        | 40     | 0 ✓        |
| SeqNum         | 48     | 0 ✓        |
| Subtree ID     | 56     | 0 ✓        |
| Payload Length | 88     | 0 ✓        |
| Payload        | 92     | 4          |

### Compatibility with BRC-12 Legacy Format

Legacy BRC-12 frames (Frame Version `0x01`) use a 44-byte header with no `HashKey`, `SeqNum`, or subtree fields:

| Offset | Size | Field                           |
| ------ | ---- | ------------------------------- |
| 0      | 4    | Network Magic                   |
| 4      | 2    | Protocol Version                |
| 6      | 1    | Frame Version (`0x01` = legacy) |
| 7      | 1    | Reserved                        |
| 8      | 32   | Transaction ID                  |
| 40     | 4    | Payload Length                  |
| 44     | \*   | Payload                         |

### TCP Transport

When transported over TCP, frames are concatenated end-to-end with no additional envelope. The reader:

1. Reads 44 bytes (sufficient for legacy header or BRC-124 header start)
2. Inspects byte 6 (Frame Version)
   * **Version 0x01 (legacy):** header complete; read `PayLen` at bytes 40–43
   * **Version 0x02 (BRC-124):** read 48 more bytes; read `PayLen` at bytes 88–91
3. Reads exactly `PayLen` bytes
4. Processes the complete frame

### Frame Hex Dump

A frame following this specification carrying a 200-byte transaction:

```
// Header (92 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
02                                                                // Frame Version
00                                                                // Reserved
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471  // TXID
A1B2C3D400000001                                                  // HashKey (XXH64 of sender+group+subtree)
00000000000004D2                                                  // SeqNum = 1234
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5  // Subtree ID
000000C8                                                          // Payload Length (200)

// Payload (200 bytes of BRC-12 transaction data)
0200000001...00000000
```

### Legacy Frame Hex Dump

The same transaction in legacy (BRC-12) format:

```
// Header (44 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
01                                                                // Frame Version (0x01 = legacy)
00                                                                // Reserved
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471  // TXID
000000C8                                                          // Payload Length (200)

// Payload (200 bytes)
0200000001...00000000
```

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — Payload format for transaction data
* [BRC-74: BSV Unified Merkle Path (BUMP) Format](/transactions/0074) — Merkle proof format used for transaction verification
* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — Defines the Subtree ID concept referenced in this frame format
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery

## Constants Reference

| Name               | Value      | Hex          | Description                   |
| ------------------ | ---------- | ------------ | ----------------------------- |
| `MagicBSV`         | 3823236072 | `0xE3E1F3E8` | BSV mainnet P2P magic         |
| `ProtoVer`         | 703        | `0x02BF`     | Protocol version              |
| `FrameVerLegacy`   | 1          | `0x01`       | Legacy BRC-12 frame version   |
| `FrameVerBRC124`   | 2          | `0x02`       | Current BRC-124 frame version |
| `HeaderSizeLegacy` | 44         | `0x2C`       | Legacy header size in bytes   |
| `HeaderSize`       | 92         | `0x5C`       | BRC-124 header size in bytes  |


# Multicast Transaction NACK Retransmission Protocol

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the NACK-based retransmission and endpoint discovery protocol for the BSV multicast transaction distribution pipeline. It defines five UDP datagram formats — ADVERT, NACK, MISS, ACK, and THROTTLED — along with tier/preference-based endpoint selection, an escalation state machine, and configurable retransmit modes. The protocol operates on top of the BRC-124 data-plane and enables reliable gap recovery without requiring connection state at the ingress or listener tiers.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

The BRC-124 data-plane delivers BSV transactions over IPv6 multicast with best-effort semantics. Network congestion, interface buffer overflows, and MLD snooping transitions can cause individual frames to be lost. Because BRC-124 frames carry a stable per-flow `HashKey` and a monotonic `SeqNum` stamped by the ingress proxy, listeners can detect exactly which frame is missing without a central sequence authority.

This BRC adds the reliability layer that allows listeners to recover those gaps:

1. **Gap detection** — listeners identify missing frames when a frame's `SeqNum` advances by more than 1 from the last-seen value for a given `HashKey` flow.
2. **NACK dispatch** — listeners send a 64-byte NACK datagram to a retry endpoint identifying the missing frame by its flow (`HashKey`) and sequence number.
3. **ACK/MISS/THROTTLED responses** — every served NACK receives a deterministic 16-byte response; ACK confirms retransmit dispatched, MISS triggers immediate escalation, and the optional THROTTLED signals honest congestion (hold and retry the same endpoint without escalating).
4. **Endpoint discovery** — retry endpoints periodically multicast a 56-byte ADVERT beacon; listeners maintain a dynamic registry sorted by tier and preference, with no manual configuration required in well-connected deployments.
5. **Flood prevention** — deduplication and fill-suppression mechanisms prevent retransmit storms at scale.

## Specification

### Common Message Preamble

All BRC-126 datagrams begin with a 7-byte preamble:

| Offset | Size | Field         | Description                              |
| ------ | ---- | ------------- | ---------------------------------------- |
| 0      | 4    | Network Magic | `0xE3E1F3E8` (BSV mainnet P2P magic)     |
| 4      | 2    | Protocol Ver  | `0x02BF` (703, BSV large-block baseline) |
| 6      | 1    | MsgType       | Identifies the message type (see below)  |

The `MsgType` byte at offset 6 is in the same position as `FrameVersion` in BRC-124 data frames. Values `0x10`–`0x2F` are reserved for BRC-126 control messages and are distinct from the data-frame version codes (`0x01`–`0x07`).

### MsgType Values

| MsgType | Name      | Size | Direction                     |
| ------- | --------- | ---- | ----------------------------- |
| `0x10`  | NACK      | 64 B | Listener → Retry endpoint     |
| `0x11`  | MISS      | 16 B | Retry endpoint → Listener     |
| `0x12`  | ACK       | 16 B | Retry endpoint → Listener     |
| `0x13`  | THROTTLED | 16 B | Retry endpoint → Listener     |
| `0x20`  | ADVERT    | 56 B | Retry endpoint → Beacon group |

***

### ADVERT Wire Format (`MsgType 0x20`) — 56 bytes

Sent periodically by retry endpoints to the beacon multicast group. Listeners use it to build and maintain their endpoint registry.

| Offset | Size | Field          | Description                                                   |
| ------ | ---- | -------------- | ------------------------------------------------------------- |
| 0      | 4    | Network Magic  | `0xE3E1F3E8`                                                  |
| 4      | 2    | Protocol Ver   | `0x02BF`                                                      |
| 6      | 1    | MsgType        | `0x20` (ADVERT)                                               |
| 7      | 1    | Scope          | `0x05` = site-local, `0x08` = org, `0x0E` = global            |
| 8      | 16   | NACKAddr       | IPv6 unicast address listeners use for NACK requests          |
| 24     | 2    | NACKPort       | UDP port for NACK requests (default `9300`)                   |
| 26     | 1    | Tier           | Operator-assigned proximity tier; `0` = source-adjacent       |
| 27     | 1    | Preference     | Within-tier priority; higher = more preferred (default `128`) |
| 28     | 2    | BeaconInterval | Beacon send interval in seconds; listeners TTL = `3 × this`   |
| 30     | 2    | Flags          | Capability bitmask (see below)                                |
| 32     | 4    | InstanceID     | CRC32c of hostname; stable across restarts                    |
| 36     | 4    | Reserved       | Must be `0x00000000`                                          |
| 40     | 16   | Reserved       | Must be all zeros; reserved for future capability bitmap      |

#### ADVERT Flags Bitmask

| Bit    | Name                | Meaning                                                    |
| ------ | ------------------- | ---------------------------------------------------------- |
| `0x01` | *(reserved)*        | Unused; must be zero                                       |
| `0x02` | HasParent           | Endpoint forwards cache-miss NACKs to an upstream endpoint |
| `0x04` | Draining            | Entering shutdown; listeners should stop routing new NACKs |
| `0x08` | UnicastRetransmit   | Supports unicast frame delivery to the NACK source         |
| `0x10` | MulticastRetransmit | Retransmits via multicast to the original shard group      |

**HasParent:** When set, the endpoint maintains a connection to a parent (higher-tier) endpoint and forwards NACKs on local cache miss. Listeners need only know their local tier; inter-tier forwarding is handled transparently.

**Beacon group addresses** are derived from the shard address scheme using the reserved control-plane index `0xFFFD`:

| Scope           | Beacon Group   |
| --------------- | -------------- |
| Site (`0x05`)   | `FF05::B:FFFD` |
| Org (`0x08`)    | `FF08::B:FFFD` |
| Global (`0x0E`) | `FF0E::B:FFFD` |

Addresses use the IANA-aligned layout: bytes 0–1 carry the scope prefix, bytes 2–11 are zero (IANA 96-bit boundary), bytes 12–13 carry the IANA Bitcoin group-id (`0x000B`), and bytes 14–15 carry the group index. Operators MAY override the group-id via `-mc-group-id`.

***

### NACK Wire Format (`MsgType 0x10`) — 64 bytes

Sent by a listener to a retry endpoint when a gap is detected. Identifies the missing frame by its flow (`HashKey`) and sequence number range. For current single-frame retrieval, `StartSeq == EndSeq`; range requests (`StartSeq < EndSeq`) are reserved for future use.

| Offset | Size | Field     | Description                                                                     |
| ------ | ---- | --------- | ------------------------------------------------------------------------------- |
| 0      | 4    | Magic     | `0xE3E1F3E8`                                                                    |
| 4      | 2    | ProtoVer  | `0x02BF`                                                                        |
| 6      | 1    | MsgType   | `0x10` (NACK)                                                                   |
| 7      | 1    | Flags     | Bit 0 (`0x01`) = Proxied; bits 1–7 reserved, must be `0`                        |
| 8      | 8    | HashKey   | Stable per-flow XXH64 identifier; from BRC-124 frame bytes 40–47                |
| 16     | 8    | StartSeq  | First missing `SeqNum` (inclusive)                                              |
| 24     | 8    | EndSeq    | Last missing `SeqNum` (inclusive); equals `StartSeq` for single-frame retrieval |
| 32     | 32   | SubtreeID | 32-byte batch identifier; from BRC-124 frame bytes 56–87; zeros = unset         |

The listener opens a per-request ephemeral UDP socket (`[::]:0`), sends the NACK, and waits up to 300 ms for a single response (`MISS` or `ACK`).

> **HashKey** (offset 8) is the `HashKey` field from the BRC-124 frame, computed as `XXH64(senderIPv6 ∥ groupIdx ∥ subtreeID)`. It uniquely identifies the flow. The retry endpoint uses `HashKey` as a per-flow rate-limiting key (NACK storm cap). A value of `0` bypasses the per-flow check.
>
> **StartSeq / EndSeq** (offsets 16/24) specify the range of missing sequence numbers. For current single-frame retrieval, `StartSeq == EndSeq`. The retry endpoint looks up the frame using the 16-byte cache key `HashKey ∥ StartSeq`.
>
> **SubtreeID** (offset 32) is carried for informational purposes; the cache key is `HashKey ∥ SeqNum` and does not require SubtreeID for disambiguation.
>
> **Flags / Proxied** (offset 7, bit `0x01`) marks a NACK that an endpoint issued on behalf of a downstream multicast domain (cross-domain proxying — see [NACK Proxying](#nack-proxying-cross-domain-recovery)). An endpoint receiving a NACK with this bit set MUST serve it from its own cache but MUST NOT re-proxy it, bounding any proxy chain to a single hop. The bit was previously reserved and is ignored by legacy endpoints, which simply never re-proxy.

***

### NACK Proxying (cross-domain recovery)

A retry endpoint serving a downstream multicast domain (one fed by a listener's multicast egress rather than directly by the ingress proxy) can only cache what the listener actually emitted. A frame the listener never put on the downstream wire — egress send error, interface flap, or in-fabric loss — is missed identically by the downstream endpoint and every downstream consumer, so a downstream-only cache cannot repair it. NACK proxying recovers such frames from an upstream endpoint that received them directly from the ingress proxy:

1. A downstream consumer NACKs the downstream endpoint, which suffers a local cache miss and returns `MISS` immediately.
2. The downstream endpoint forwards the NACK to a statically configured upstream endpoint with the **Proxied** flag set (`0x01`). Recovery is asynchronous ("cache-warm"); no NACK worker is held waiting.
3. The upstream endpoint serves the proxied NACK from its cache. Because the requester (the downstream endpoint) is not joined to the upstream shard groups, the frame is returned by **unicast** to the NACK source — a proxied NACK is always served a unicast copy regardless of the upstream's advertised retransmit mode.
4. The downstream endpoint re-caches the recovered frame (keyed `HashKey ∥ SeqNum`, per-FrameVer TTL) and multicast-retransmits it into the downstream domain; the consumer's gap auto-fills via `Tracker.Fill()`.

**One-hop bound.** The `Proxied` flag prevents an upstream endpoint from re-proxying, so any proxy chain is at most one hop. Upstream discovery is by static configuration, because a separated downstream domain generally cannot receive upstream multicast ADVERT beacons. The `HasParent` ADVERT flag (`0x02`) signals that an endpoint has an upstream parent configured. When several downstream endpoints run with proxying enabled against a shared cache backend, an in-flight claim deduplicates the upstream NACKs so only one endpoint recovers each gap.

***

### MISS Response (`MsgType 0x11`) — 16 bytes

Sent unicast to the NACK source when the requested frame is not in the endpoint's cache. On receipt, the listener advances immediately to the next endpoint (no backoff delay).

| Offset | Size | Field    | Description              |
| ------ | ---- | -------- | ------------------------ |
| 0      | 4    | Magic    | `0xE3E1F3E8`             |
| 4      | 2    | ProtoVer | `0x02BF`                 |
| 6      | 1    | MsgType  | `0x11` (MISS)            |
| 7      | 1    | Flags    | Reserved; must be `0x00` |
| 8      | 8    | SeqNum   | Always `0` on MISS       |

***

### ACK Response (`MsgType 0x12`) — 16 bytes

Sent unicast to the NACK source when the frame was found and retransmit dispatched. Whether the listener may cancel the gap on receipt depends on the `Flags` byte — see *Repair confirmation* below.

| Offset | Size | Field    | Description                                                    |
| ------ | ---- | -------- | -------------------------------------------------------------- |
| 0      | 4    | Magic    | `0xE3E1F3E8`                                                   |
| 4      | 2    | ProtoVer | `0x02BF`                                                       |
| 6      | 1    | MsgType  | `0x12` (ACK)                                                   |
| 7      | 1    | Flags    | `0x01` = multicast sent; `0x02` = unicast sent                 |
| 8      | 8    | SeqNum   | `SeqNum` of the retransmitted frame (from BRC-124 bytes 48–55) |

The listener uses the `SeqNum` echo to confirm the gap entry matches before cancelling it.

**Repair confirmation.** An ACK asserts that a retransmit was *dispatched*, not that it was *delivered*, so the `Flags` byte determines whether the listener may close the gap. On an ACK with `0x02` (unicast sent) the listener MUST NOT cancel the gap entry: it MUST wait for the retransmitted frame itself and, if the frame does not arrive, MUST resume escalation as though the attempt had timed out. This matters because the retransmit can be lost on the same path that dropped the original. An ACK without `0x02` conveys no such confirmation channel, so a listener MAY cancel on the ACK alone, accepting that a lost retransmit will not be retried; deployments that require repair to be verifiable SHOULD therefore enable unicast retransmit.

***

### THROTTLED Response (`MsgType 0x13`) — 16 bytes

Sent unicast to the NACK source when the request was rejected by a congestion-control tier that limits per-gap or per-flow request rate (see [Flood Prevention](#flood-prevention)). It is a flow-control signal, **not** a failure: the endpoint is healthy, and for a per-gap throttle a retransmit for this exact gap was likely just served and is propagating over the multicast data plane. On receipt a listener MUST hold the gap for the hinted backoff and retry the **same** endpoint; it MUST NOT escalate to another endpoint and MUST NOT count the throttle as a recovery failure.

| Offset | Size | Field    | Description                                |
| ------ | ---- | -------- | ------------------------------------------ |
| 0      | 4    | Magic    | `0xE3E1F3E8`                               |
| 4      | 2    | ProtoVer | `0x02BF`                                   |
| 6      | 1    | MsgType  | `0x13` (THROTTLED)                         |
| 7      | 1    | Flags    | Bits 0-3 = backoff bucket; 4-7 reserved    |
| 8      | 8    | SeqNum   | Echo of the throttled request's `StartSeq` |

**Backoff hint.** The suggested hold is `ThrottleHintBase << bucket`, where `ThrottleHintBase = 125 ms` and `bucket` is the Flags low nibble. Endpoints SHOULD use bucket `2` (\~500 ms) for a per-gap throttle and bucket `3` (\~1 s) for a per-flow throttle. Listeners SHOULD apply jitter (e.g. uniform over `[hold/2, hold]`) to de-synchronise and MAY clamp the hold to a local maximum. The gap's absolute TTL remains the upper bound; a multicast repair cancels the gap regardless.

**Emission rules.**

* THROTTLED is OPTIONAL and SHOULD default to disabled; it is a load-shedding refinement for high-fan-out deployments.
* An endpoint MUST NOT send THROTTLED for a flood-tier (per-source) rejection: that tier sheds abusive or spoofed-source load, and answering it would permit reflection. (The 16-byte response is smaller than the 64-byte NACK, so the protocol is never a bandwidth *amplifier* regardless.)
* An endpoint that does not implement THROTTLED stays silent; the listener falls back to timeout + backoff.
* A listener that does not recognise `0x13` treats it as an unparseable response (timeout-equivalent), so emission is backward-compatible.

***

### Tier / Preference Model

Retry endpoints are organised into tiers representing proximity to the transaction source, and assigned a preference within each tier.

| Tier   | Meaning                                           |
| ------ | ------------------------------------------------- |
| `0`    | Same AS as the ingress proxy (source-adjacent)    |
| `1`    | One AS boundary from source                       |
| `N`    | N AS hops from source                             |
| `0xFF` | Static seed (no beacon received; lowest priority) |

Endpoints discovered via ADVERT carry their operator-assigned `Tier` (0–254) and `Preference` (0–255). Endpoints registered via static configuration seed the registry at `Tier=0xFF, Preference=0`.

Listeners sort the endpoint registry by `(Tier ASC, Preference DESC)`. NACK dispatch always selects the head of the sorted list; on MISS or timeout, the listener advances to the next position.

#### Escalation State Machine

```
  ┌──────────┐
  │ PENDING  │  gap registered; hold-off jitter applied
  └────┬─────┘
       │ timer fires
       ▼
  ┌────────────────┐
  │ NACKED(Tier-K) │  NACK sent to current (tier, preference) endpoint
  └───┬────┬───┬───┘
      │    │   │
      │    │   └── Timeout ──► exponential backoff; retry next sweep
      │    │
      │    ├── MISS ──► advance to next endpoint at same tier (Preference DESC);
      │    │            if tier exhausted, escalate to Tier K+1;
      │    │            retry IMMEDIATELY (no backoff on MISS)
      │    │
      │    └── THROTTLED ──► hold for the hinted backoff; retry the SAME endpoint;
      │                      do NOT escalate, do NOT count as a recovery failure
      │
      ├── ACK (no 0x02) ──► gap entry cancelled; done
      └── ACK (0x02 unicast sent) ──► await frame ──► FILLED
                                   └── timeout ──► advance endpoint

  Any state ──► FILLED (multicast repair arrived via data plane)
               gap entry cancelled; in-flight NACK socket times out harmlessly
```

* **ACK received, `Flags` without `0x02`:** Cancel gap entry. No further NACKs sent for this gap (the repair is trusted, not confirmed).
* **ACK received with `0x02` (unicast sent):** Do NOT cancel. Await the retransmitted frame; on arrival the gap is FILLED, and on timeout resume escalation from the next endpoint.
* **MISS received:** Advance to next endpoint at same tier by Preference; if tier exhausted, move to next tier; retry immediately.
* **Timeout:** Apply exponential backoff (capped at `nack-backoff-max`); retry on next sweeper tick.
* **THROTTLED received:** Hold the gap for the hinted backoff and retry the same endpoint. Do not escalate and do not count it as a recovery failure — it is congestion control (see [THROTTLED Response](#throttled-response-msgtype-0x13--16-bytes)).
* **Multicast fill:** The data-plane receive goroutine calls `Tracker.Fill()` independently; gap cancelled regardless of NACK state.

***

### Beacon Scopes

Three scope bytes are defined for the ADVERT wire format. The reference implementation's `-beacon-scope` flag accepts `site`, `org`, `global`, `both`, or `all` (`both` and `all` are equivalent: the ADVERT is sent to the site, org, and global beacon groups simultaneously):

| Scope           | Scope byte | Beacon group   | Use case                                            |
| --------------- | ---------- | -------------- | --------------------------------------------------- |
| Site (`0x05`)   | `0x05`     | `FF05::B:FFFD` | Intra-site discovery; all listeners join at startup |
| Org (`0x08`)    | `0x08`     | `FF08::B:FFFD` | Organisation-wide discovery for multi-site AS       |
| Global (`0x0E`) | `0x0E`     | `FF0E::B:FFFD` | Inter-AS discovery via MP-BGP MVPN / MSDP           |

Beacon group addresses are shown in ASM (`FF0x`) form. Under SSM, substitute the `FF3x` prefix per [BRC-129](/transactions/0129) (`FF35::B:FFFD` site, `FF3E::B:FFFD` global; inter-domain scope is SSM-only per RFC 8815). Because beacon groups are control-plane, their source — the emitting retry endpoint — cannot be discovered from within the group; SSM receivers `(S,G)`-join using the per-group bootstrap source lists defined in BRC-129, not via ADVERT. The ADVERT and NACK wire formats are unchanged across modes.

Scope byte `0xFF` is used in ADVERT datagrams when the sender intends to cover site, org, and global simultaneously (sends three ADVERTs). Listeners parse the scope byte from each individual datagram.

To cover multiple scopes, run separate endpoint instances each configured with a different `-beacon-scope` value. Each scope can use independent `Tier` and `Preference` tuning.

Listeners compute TTL as `3 × BeaconInterval`. An endpoint not heard for that duration is evicted from the registry. Static seeds (`-retry-endpoints`) are never evicted.

***

### Configurable Retransmit Modes

Retransmit behavior is controlled by flags on the retry endpoint:

| Flag                      | Default | Effect                                             |
| ------------------------- | ------- | -------------------------------------------------- |
| `-beacon-flags-multicast` | `true`  | Retransmit frame to original multicast shard group |
| `-beacon-flags-unicast`   | `false` | Retransmit frame unicast to NACK source            |
| `-suppress-miss`          | `false` | Do not send MISS response on cache miss            |
| `-suppress-ack`           | `false` | Do not send ACK response on cache hit              |

**Deployment profiles:**

* **On-fabric (default):** `-beacon-flags-multicast=true` — all listeners on the fabric receive the retransmit simultaneously.
* **Edge / unicast:** `-beacon-flags-unicast=true -beacon-flags-multicast=false` — for listeners not on the multicast fabric, and wherever repair must be *confirmed* rather than trusted (see *Repair confirmation* above). A multicast retransmit is also delivered to every listener in the group, including those that observed no loss.
* **High-volume:** `-suppress-ack=true` — reduces per-frame ACK traffic; MISS responses are preserved for escalation correctness.

***

### Flood Prevention

| Mechanism               | Component      | Effect                                                                                                                         |
| ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Cache TTL (60 s)        | Retry endpoint | Frames expire naturally; bounds retransmit window                                                                              |
| `Tracker.Fill()`        | Listener       | Multicast repair cancels all pending NACKs for a gap                                                                           |
| Jitter hold-off         | Listener       | Randomised delay before first NACK suppresses correlated duplicates                                                            |
| Exponential backoff     | Listener       | Reduces NACK rate on persistent or repeated gaps                                                                               |
| `MaxRetries` + `GapTTL` | Listener       | Gap entries evicted after retry exhaustion or absolute deadline                                                                |
| THROTTLED hint          | Retry endpoint | Tells throttled listeners to hold (vs. timeout + escalate), cutting NACK load on honest congestion; IP flood tier stays silent |

***

## Examples

### ADVERT Datagram (56 bytes)

A site-scope endpoint at `fd20::24`, port 9300, Tier 0, Preference 128, 60-second interval, multicast retransmit enabled:

```
E3E1F3E8                                  // Network Magic
02BF                                      // Protocol Version
20                                        // MsgType = ADVERT
05                                        // Scope = site-local
FD200000000000000000000000000024          // NACKAddr = fd20::24
2454                                      // NACKPort = 9300
00                                        // Tier = 0
80                                        // Preference = 128
003C                                      // BeaconInterval = 60 s
0010                                      // Flags = MulticastRetransmit
A1B2C3D4                                  // InstanceID (CRC32c of hostname)
00000000                                  // Reserved
00000000000000000000000000000000          // Reserved (16 bytes)
```

### NACK Datagram (64 bytes)

Single-frame retrieval: missing frame for flow `HashKey=0xA1B2C3D400000001`, `SeqNum=1235`:

```
E3E1F3E8                                  // Network Magic
02BF                                      // Protocol Version
10                                        // MsgType = NACK
00                                        // Flags (reserved)
A1B2C3D400000001                          // HashKey (XXH64 of sender+group+subtree)
00000000000004D3                          // StartSeq = 1235
00000000000004D3                          // EndSeq = 1235 (single frame)
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5  // SubtreeID
```

### MISS Response (16 bytes)

```
E3E1F3E8                                  // Network Magic
02BF                                      // Protocol Version
11                                        // MsgType = MISS
00                                        // Flags
0000000000000000                          // SeqNum = 0 (cache miss)
```

### ACK Response (16 bytes)

Frame found and retransmitted via multicast:

```
E3E1F3E8                                  // Network Magic
02BF                                      // Protocol Version
12                                        // MsgType = ACK
01                                        // Flags = multicast_sent
00000000000004D3                          // SeqNum of retransmitted frame (1235)
```

***

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — Payload format for transaction data within BRC-124 frames
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Data-plane frame format; defines `HashKey`, `SeqNum`, and `Subtree ID` stamped by the proxy
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery

***

## Constants Reference

| Name                    | Value      | Hex          | Description                            |
| ----------------------- | ---------- | ------------ | -------------------------------------- |
| `MagicBSV`              | 3823236072 | `0xE3E1F3E8` | BSV mainnet P2P magic                  |
| `ProtoVer`              | 703        | `0x02BF`     | Protocol version                       |
| `MsgTypeNACK`           | 16         | `0x10`       | NACK request                           |
| `MsgTypeMISS`           | 17         | `0x11`       | MISS response                          |
| `MsgTypeACK`            | 18         | `0x12`       | ACK response                           |
| `MsgTypeADVERT`         | 32         | `0x20`       | Endpoint advertisement beacon          |
| `ScopeSite`             | 5          | `0x05`       | Site-local beacon scope                |
| `ScopeOrg`              | 8          | `0x08`       | Organisation beacon scope              |
| `ScopeGlobal`           | 14         | `0x0E`       | Global beacon scope                    |
| `NACKSize`              | 64         | `0x40`       | NACK datagram size in bytes            |
| `FlagProxied`           | 1          | `0x01`       | NACK flag: proxied, one-hop downstream |
| `FlagMulticastSent`     | 1          | `0x01`       | ACK flag: multicast retransmit sent    |
| `FlagUnicastSent`       | 2          | `0x02`       | ACK flag: unicast retransmit sent      |
| `FlagHasParent`         | 2          | `0x02`       | ADVERT flag: upstream endpoint set     |
| `FlagDraining`          | 4          | `0x04`       | ADVERT flag: endpoint is draining      |
| `FlagUnicastRetransmit` | 8          | `0x08`       | ADVERT flag: unicast retransmit        |
| `FlagMcastRetransmit`   | 16         | `0x10`       | ADVERT flag: multicast retransmit      |
| `GroupBeacon`           | 65533      | `0xFFFD`     | Control-plane beacon group index       |
| `DefaultNACKPort`       | 9300       | `0x2454`     | Default NACK/ADVERT UDP port           |
| `DefaultBeaconInterval` | 60         | —            | Default ADVERT send interval (s)       |
| `DefaultCacheTTL`       | 60         | —            | Default frame cache TTL (s)            |
| `TierStaticSeed`        | 255        | `0xFF`       | Tier assigned to static seed endpoints |


# Multicast Subtree Group Announcement Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies a periodic multicast announcement protocol that maps BRC-124 `SubtreeID` values to stable 128-bit group identifiers. Senders broadcast compact 64-byte datagrams on a reserved control-plane multicast group, allowing listeners to dynamically learn which subtree IDs belong to which groups and filter incoming transaction traffic accordingly — without static configuration.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

BRC-124 frames carry a 32-byte `SubtreeID` that identifies the transaction subtree to which each frame belongs. Subtree IDs are derived deterministically by each block assembler and change with every new work candidate. Static subtree ID lists are impractical for listeners that want to subscribe to a logical class of transactions (a *subtree group*) across block boundaries.

This BRC introduces:

1. **Subtree groups** — stable 128-bit identifiers that logically group related subtree IDs. A group ID is an XXH3-128 hash of a canonical group name, or any operator-assigned 128-bit value.
2. **SubtreeGroupAnnounce** — a periodic 64-byte control datagram sent by block assemblers announcing that a given `SubtreeID` is a member of a given group.
3. **Dynamic listener filtering** — listeners join a dedicated control-plane multicast group, process SubtreeGroupAnnounce datagrams, maintain a time-bounded registry, and pass transactions based on group membership.

Subtrees are temporal: they are valid only for the duration of a work candidate. Announcements are sent periodically; entries not refreshed within their TTL are evicted automatically.

## Specification

### Control-Plane Group

SubtreeGroupAnnounce datagrams are sent to the reserved control-plane multicast group index `0xFFFC` (`GroupSubtreeGroupAnnounce`), one slot below the BRC-126 beacon group (`0xFFFD`).

Control-plane addresses use the IANA-aligned layout: bytes 0–1 carry the scope prefix, bytes 2–11 are zero (IANA 96-bit boundary), bytes 12–13 carry the IANA Bitcoin group-id (`0x000B`), and bytes 14–15 carry the group index.

| Scope           | Announce Group |
| --------------- | -------------- |
| Site (`0x05`)   | `FF05::B:FFFC` |
| Org (`0x08`)    | `FF08::B:FFFC` |
| Global (`0x0E`) | `FF0E::B:FFFC` |

Operators MAY override the IANA group-id (`0x000B`) via the `-mc-group-id` configuration flag for private deployments; the resulting addresses change accordingly.

Announce-group addresses are shown in ASM (`FF0x`) form; under SSM, substitute the `FF3x` prefix per [BRC-129](/transactions/0129) (`FF3E::B:FFFC` for inter-domain scope, which is SSM-only per RFC 8815). The datagram format is unchanged. This is a control-plane group whose source — the announcing block assembler — is not discoverable from within the group, so SSM listeners `(S,G)`-join using the bootstrap source lists defined in BRC-129; these are the same source addresses a listener may configure in the include filter (see [Sender Filtering](#sender-filtering)).

### SubtreeGroupAnnounce Wire Format (`MsgType 0x30`) — 64 bytes

| Offset | Size | Field         | Description                                                   |
| ------ | ---- | ------------- | ------------------------------------------------------------- |
| 0      | 4    | Network Magic | `0xE3E1F3E8` (BSV mainnet P2P magic)                          |
| 4      | 2    | Protocol Ver  | `0x02BF` (703, BSV large-block baseline)                      |
| 6      | 1    | MsgType       | `0x30` (SubtreeGroupAnnounce)                                 |
| 7      | 1    | Flags         | Reserved; must be `0x00`                                      |
| 8      | 32   | SubtreeID     | SHA-256 subtree root hash (from BRC-124 frame header)         |
| 40     | 16   | GroupID       | 128-bit group identifier, big-endian                          |
| 56     | 4    | Epoch         | Unix timestamp (uint32 BE) when this announcement was created |
| 60     | 2    | TTL           | Validity in seconds (uint16 BE); `0` = use listener default   |
| 62     | 2    | Reserved      | Must be `0x0000`                                              |

Total: **64 bytes**. Power-of-two message size; `GroupID` at offset 40 is 8-byte aligned.

#### Flags (byte 7)

All bits are reserved and must be zero. Future versions of this protocol may define flag bits.

#### GroupID

A GroupID is a 128-bit value uniquely identifying a logical subtree group. It is recommended to derive GroupIDs using XXH3-128 applied to a canonical group name string, as this provides hardware-accelerated, collision-resistant derivation. GroupIDs may also be assigned directly as opaque 128-bit values.

#### TTL and Expiry

A `TTL` value of `0` instructs the listener to apply its locally configured default TTL. Listeners SHOULD set the default TTL to at least 900 seconds (15 minutes) to accommodate block creation intervals. Senders SHOULD re-announce each (SubtreeID, GroupID) pair at an interval well below the effective TTL (recommended: every 10–30 seconds).

#### Multi-Group Membership

A single SubtreeID may belong to multiple groups. The sender emits one SubtreeGroupAnnounce datagram per (SubtreeID, GroupID) pair per announcement interval.

### Sender Behaviour

1. For each (SubtreeID, GroupID) pair, construct a 64-byte SubtreeGroupAnnounce datagram.
2. Send the datagram as a UDP packet to the SubtreeGroupAnnounce control group on the configured multicast scope(s).
3. Repeat at the configured announcement interval (recommended: 10–30 seconds).
4. On work reset (new block), stop announcing old SubtreeIDs. Listeners will evict them when the TTL expires.

No retransmission or sequencing is required. Periodic re-announcement provides eventual consistency.

### Listener Behaviour

1. Join the SubtreeGroupAnnounce control-plane multicast group on the configured scope(s).
2. For each received datagram, validate the magic, ProtoVer, MsgType, and length.
3. If a sender include/exclude filter is configured, check the UDP source address before processing.
4. If the GroupID matches a subscribed group, record `(SubtreeID → expiry)` in the group registry.
5. Periodically evict entries whose expiry has passed.
6. On data-plane frame receipt, allow the frame if its SubtreeID is present in any subscribed group's registry.

### Sender Filtering

Listeners MAY configure include and exclude filters on announcement source addresses (IPv6 addresses or CIDR prefixes). The evaluation order is: exclude first, then include. An empty include list accepts all non-excluded sources.

***

## Example

SubtreeGroupAnnounce for SubtreeID `A3F1...9247`, GroupID `A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6`, Epoch `1746800000`, TTL `0` (use listener default):

```
E3E1F3E8                                  // Network Magic
02BF                                      // Protocol Version
30                                        // MsgType = SubtreeGroupAnnounce
00                                        // Flags = 0
A3F1E8B04C7D2A9156E3F08B4D7C2E9A          // SubtreeID bytes 0–15
1B5F3D8E6C0A4279B1D5E8F3C6A09247          // SubtreeID bytes 16–31
A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6          // GroupID (128-bit)
67E27D80                                  // Epoch = 1746800000
0000                                      // TTL = 0 (use listener default)
0000                                      // Reserved
```

***

## Constants Reference

| Name                          | Value      | Hex          | Description                                 |
| ----------------------------- | ---------- | ------------ | ------------------------------------------- |
| `MagicBSV`                    | 3823236072 | `0xE3E1F3E8` | BSV mainnet P2P magic                       |
| `ProtoVer`                    | 703        | `0x02BF`     | Protocol version                            |
| `MsgTypeSubtreeGroupAnnounce` | 48         | `0x30`       | SubtreeGroupAnnounce datagram type          |
| `GroupSubtreeGroupAnnounce`   | 65532      | `0xFFFC`     | Control-plane subtree group announce group  |
| `GroupBeacon`                 | 65533      | `0xFFFD`     | Control-plane beacon group (BRC-126)        |
| `SubtreeGroupAnnounceSize`    | 64         | —            | Fixed datagram size in bytes                |
| `DefaultAnnounceTTL`          | 900        | —            | Recommended listener default TTL (s)        |
| `DefaultAnnounceInterval`     | 10         | —            | Recommended sender re-announce interval (s) |

***

## References

* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Data-plane frame format; defines `SubtreeID`
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery


# Multicast Extended Transaction Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies a payload variant for the BRC-124 multicast frame format in which the transaction payload uses the BRC-30 Extended Format (EF) instead of the BRC-12 raw transaction format. The 92-byte BRC-124 header is unchanged; the payload is self-identifying via the BRC-30 EF marker. This enables multicast receivers and broadcast services to fully validate transaction signatures and fee amounts without external UTXO lookups.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

BRC-124 frames carry BSV transactions in BRC-12 raw format, which omits the locking scripts and satoshi amounts of spent inputs. Validating signatures or computing fees from a BRC-12 payload requires an external UTXO lookup — a bottleneck that BRC-30 was specifically designed to eliminate.

By defining BRC-124 frames with BRC-30 Extended Format payloads, multicast infrastructure gains:

1. **Self-contained validation** — receivers can verify all input signatures and compute exact fee amounts from the frame payload alone, with no UTXO index dependency.
2. **Zero header changes** — the BRC-124 header (Frame Version `0x02`, 92 bytes) is reused as-is. The EF marker embedded in the payload provides unambiguous format detection. No frame version bump is required.
3. **Transparent infrastructure** — proxies, retry endpoints, and listeners treat the payload as opaque bytes. Existing multicast infrastructure forwards BRC-128 frames without modification.
4. **Coexistence** — BRC-124 frames with BRC-12 payloads and BRC-128 frames with BRC-30 EF payloads share the same multicast groups, frame version, and processing pipeline.

## Specification

### Frame Structure Overview

A BRC-128 frame consists of a standard 92-byte BRC-124 header followed by a variable-length payload containing a BRC-30 Extended Format transaction. The header is identical to BRC-124 in every respect — same field layout, same Frame Version byte (`0x02`), same alignment.

### Header Format (92 bytes)

The header is defined by [BRC-124](/transactions/0124) and reproduced here for reference:

| Offset | Size | Alignment | Field            | Description                                               |
| ------ | ---- | --------- | ---------------- | --------------------------------------------------------- |
| 0      | 4    | —         | Network Magic    | `0xE3E1F3E8` (BSV mainnet P2P magic)                      |
| 4      | 2    | —         | Protocol Version | `0x02BF` (703, BSV large-block baseline)                  |
| 6      | 1    | —         | Frame Version    | `0x02`                                                    |
| 7      | 1    | —         | Reserved         | Must be `0x00`                                            |
| 8      | 32   | 8-byte    | Transaction ID   | Raw 256-bit TXID (internal byte order)                    |
| 40     | 8    | 8-byte    | HashKey          | Stable per-flow XXH64 identifier; `0` = unstamped         |
| 48     | 8    | 8-byte    | SeqNum           | Monotonic per-flow counter (starts at 1); `0` = unstamped |
| 56     | 32   | 8-byte    | Subtree ID       | 32-byte batch identifier; zeros = unset                   |
| 88     | 4    | 8-byte    | Payload Length   | `uint32` BE                                               |
| 92     | \*   | —         | Payload          | BRC-30 Extended Format transaction bytes                  |

All header fields retain the semantics defined in BRC-124. The only difference from a BRC-124 frame is the payload format.

### Payload Format (BRC-30 Extended Format)

The payload is a BSV transaction serialized in the BRC-30 Extended Format. The format is summarized here; see [BRC-30](/transactions/0030) for the full specification.

#### EF Transaction Structure

| Field           | Description                      | Size                                     |
| --------------- | -------------------------------- | ---------------------------------------- |
| Version no      | Transaction version, currently 2 | 4 bytes (LE)                             |
| EF marker       | Extended Format marker           | 6 bytes: `0x00 0x00 0x00 0x00 0x00 0xEF` |
| In-counter      | Number of inputs (VarInt)        | 1–9 bytes                                |
| list of inputs  | Extended Format input structures | variable                                 |
| Out-counter     | Number of outputs (VarInt)       | 1–9 bytes                                |
| list of outputs | Standard output structures       | variable                                 |
| nLocktime       | Lock time                        | 4 bytes (LE)                             |

#### Extended Format Input Structure

Each input in an EF transaction appends the spent output's satoshi value and locking script after the standard input fields:

| Field                          | Description                                       | Size             |
| ------------------------------ | ------------------------------------------------- | ---------------- |
| Previous Transaction hash      | TXID of the transaction the output was created in | 32 bytes         |
| Previous Txout-index           | Index of the output (non-negative integer)        | 4 bytes          |
| Txin-script length             | VarInt                                            | 1–9 bytes        |
| Txin-script / scriptSig        | Unlocking script                                  | bytes            |
| Sequence\_no                   | Sequence number                                   | 4 bytes          |
| **Previous TX satoshi output** | **Satoshi value of the spent output**             | **8 bytes (LE)** |
| **Previous TX script length**  | **VarInt**                                        | **1–9 bytes**    |
| **Previous TX locking script** | **Locking script of the spent output**            | **bytes**        |

### Payload Format Detection

Receivers distinguish BRC-128 (EF) payloads from BRC-124 (BRC-12 raw) payloads by inspecting the first 10 bytes of the payload:

```
Payload bytes 0–3:   Transaction version (4 bytes LE) — typically 0x02000000
Payload bytes 4–9:   EF marker check
  - BRC-30 EF:       0x00 0x00 0x00 0x00 0x00 0xEF
  - BRC-12 raw:      VarInt input count (never matches the EF marker)
```

The 6-byte EF marker `0x000000000000EF` is placed immediately after the 4-byte version number. In a BRC-12 raw transaction, bytes 4–9 contain the input count VarInt followed by the first bytes of the first input — a sequence that cannot produce the EF marker pattern. This makes detection unambiguous without any header-level signaling.

### Why No Frame Version Bump

The Frame Version byte remains `0x02` for the following reasons:

1. **Self-identifying payload** — BRC-30 was explicitly designed with the EF marker for format detection. A header-level signal is redundant.
2. **Consistent BSV precedent** — BRC-62 (BEEF), BRC-95 (Atomic BEEF), and BRC-96 (BEEF V2) all use payload-level markers for format discrimination rather than outer envelope version numbers.
3. **Non-breaking** — existing infrastructure (`frame.Decode`, TCP reader, proxy forwarder, retry endpoint cache) treats the payload as opaque bytes. A new frame version would cause all deployed components to reject frames (`ErrBadVer`) until updated, with no structural benefit.
4. **Version semantics** — Frame Version `0x01` → `0x02` changed the header from 44 to 92 bytes. A version bump should signal a header structure change, not a payload format change.

### Compatibility

BRC-128 frames are fully compatible with the existing multicast infrastructure:

* **Proxy** — forwards BRC-128 frames verbatim, stamps HashKey and SeqNum as usual. The proxy does not inspect the payload.
* **Listener** — decodes the BRC-124 header, applies shard and subtree filters, tracks gaps via SeqNum per flow. Payload format is irrelevant to these operations.
* **Retry endpoint** — caches and retransmits BRC-128 frames identically to BRC-124 frames. The cache indexes by `(HashKey, SeqNum)`, not payload content.
* **Downstream consumers** — applications that receive forwarded frames inspect payload bytes 4–9 to determine whether to parse as BRC-12 raw or BRC-30 EF.
* **Transport** — BRC-128 frames are byte-identical on the wire under ASM and SSM. Multicast addressing, scopes, and the SSM `(S,G)` join and source-discovery model are inherited from [BRC-124](/transactions/0124) and specified in [BRC-129](/transactions/0129); the source is the emitting proxy (`senderIPv6`).

BRC-124 (BRC-12 payload) and BRC-128 (BRC-30 EF payload) frames can be intermixed on the same multicast group without conflict.

### TCP Transport

TCP transport is unchanged from BRC-124. The reader:

1. Reads 44 bytes (sufficient for legacy header or BRC-124 header start)
2. Inspects byte 6 (Frame Version)
   * **Version 0x01 (legacy):** header complete; read `PayLen` at bytes 40–43
   * **Version 0x02 (BRC-124/BRC-128):** read 48 more bytes; read `PayLen` at bytes 88–91
3. Reads exactly `PayLen` bytes
4. Processes the complete frame

The receiver determines the payload format (BRC-12 vs BRC-30 EF) after reading the payload, not from the header.

## Examples

### BRC-128 Frame Hex Dump

A frame carrying a BRC-30 EF transaction. The payload begins with the 4-byte version and 6-byte EF marker:

```
// Header (92 bytes) — identical to BRC-124
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
02                                                                // Frame Version (0x02 — same as BRC-124)
00                                                                // Reserved
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471  // TXID
A1B2C3D4E5F6A7B8                                                  // HashKey (XXH64 of flow)
0000000000000001                                                  // SeqNum (1)
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5  // Subtree ID
00000130                                                          // Payload Length (304)

// Payload (304 bytes of BRC-30 Extended Format transaction)
02000000                                                          // TX version (2, LE)
0000000000EF                                                      // EF marker
01                                                                // Input count (1)
// Extended Format input:
aabb...0000                                                       // Previous TX hash (32 bytes)
00000000                                                          // Previous Txout-index
6a47...3e76                                                       // scriptSig (VarInt length + script)
FFFFFFFF                                                          // Sequence number
3E66000000000000                                                  // Previous TX satoshi output (LE, 26174 sats)
1976...c288                                                       // Previous TX locking script (VarInt + script)
// Outputs:
01                                                                // Output count (1)
3C66000000000000                                                  // Output value (LE, 26172 sats)
1976...c288                                                       // Locking script (VarInt + script)
00000000                                                          // nLocktime
```

### BRC-124 Frame Hex Dump (for comparison)

The same transaction in BRC-12 raw format (without extended input data):

```
// Header (92 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
02                                                                // Frame Version
00                                                                // Reserved
11c6900eee6e68d191cd25034a5f872ed29e3b69273906a10e021f39ed866471  // TXID
A1B2C3D4E5F6A7B8                                                  // HashKey (XXH64 of flow)
0000000000000001                                                  // SeqNum (1)
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5  // Subtree ID
000000C8                                                          // Payload Length (200)

// Payload (200 bytes of BRC-12 raw transaction)
0200000001...00000000
```

Note that the headers are byte-identical; only the payload differs.

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — Standard transaction payload format
* [BRC-30: Transaction Extended Format (EF)](/transactions/0030) — Extended Format payload used by this specification
* [BRC-74: BSV Unified Merkle Path (BUMP) Format](/transactions/0074) — Merkle proof format for transaction verification
* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — Defines the Subtree ID concept referenced in the frame header
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Header format reused by this specification
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery


# IPv6 Multicast Group Address Assignments

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the IPv6 multicast group address scheme for the BSV transaction sharding pipeline. It defines how the 16-bit shard index space is allocated across data-plane shard groups and control-plane service groups, how addresses are derived from the IANA Bitcoin multicast allocation, and the canonical addresses for beacon discovery, subtree announcement, and the block control channel. The scheme defines addressing for both Any-Source Multicast (ASM, the default) and Source-Specific Multicast (SSM) transport modes, selected per deployment without any change to the frame, NACK, or sharding semantics.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

[BRC-82](/peer-to-peer/0082) details a design for a scalable IPv6 multicast protocol for BSV transaction distribution. [BRC-124](/transactions/0124) defines a multicast transmission wire frame format. Both reference a set of multicast group addresses without specifying a canonical address scheme. As the multicast infrastructure is deployed across multiple scopes (site-local, organisation-local, and global) and across independent operator networks, a shared addressing convention is needed so that:

1. Any deployment with `shardBits ≤ 12` produces data-plane group indices that are orthogonal to control-plane groups.
2. Control-plane services — beacon discovery, subtree announcements, and block control — use stable, well-known addresses operators can pre-configure in firewalls and routing policy.
3. Operators running private or test networks can use an alternative group-id to isolate their multicast address space without modifying any other aspect of the protocol.
4. Deployments can run the data plane under Source-Specific Multicast for inter-domain scale — [RFC 8815](https://www.rfc-editor.org/rfc/rfc8815) deprecates inter-domain ASM — using the same group-id and shard-index conventions, so addresses differ only in their high-order prefix.

## Specification

### IANA Allocation

IANA allocates IPv6 multicast group addresses on the **96-bit boundary**: the top 96 bits (bytes `[0:12]`) identify the IANA-assigned group, leaving the bottom 32 bits (bytes `[12:16]`) for sub-allocation by the assignee.

The IANA Bitcoin multicast allocation is `FF0X::B` (group-id `0x000B`). This BRC splits the bottom 32 bits of that allocation into two 16-bit subfields:

* **Bytes `[12:14]`** — IANA group-id (default `0x000B`)
* **Bytes `[14:16]`** — shard index (16-bit, application-assigned)

```
Byte:   0  1    2  3  4  5  6  7  8  9 10 11   12 13   14 15
        [scope] [-------- IANA boundary zero -------]  [GID] [IDX]
        FF 05    00 00 00 00 00 00 00 00 00 00         00 0B  XX XX
```

Conformant deployments MUST use group-id `0x000B`. Operators MAY override the group-id (see [Group-ID Override](#group-id-override)) for testing or private deployments. Deployments using different group-ids operate in entirely disjoint multicast address spaces.

This bottom-32-bit split (group-id in bytes `[12:14]`, shard index in bytes `[14:16]`) is identical under both source modes defined in [Source Mode and Address Range](#source-mode-and-address-range); only the high-order prefix bytes `[0:2]` differ.

### Source Mode and Address Range

The fabric runs in one of two source modes. **Any-Source Multicast (ASM)** is the default; **Source-Specific Multicast (SSM)** is an opt-in transport mode for deployments that need inter-domain distribution (see [Source-Specific Multicast (SSM)](#source-specific-multicast-ssm)). The source mode selects the address range via the multicast prefix byte `[1]`, which encodes `flags(4 bits) | scope(4 bits)`:

* **ASM** uses the well-known permanent prefix (`flags = 0`) → `FF0x`.
* **SSM** uses the RFC 4607 SSM range `FF3x::/32` (`flags = 3`) → `FF3x`.

The scope nibble (`5` site-local, `8` organisation-local, `E` global) is unchanged between modes. Because [RFC 8815](https://www.rfc-editor.org/rfc/rfc8815) deprecates inter-domain ASM, global scope is SSM-only.

| Mode | Site scope (intra-domain) | Global scope (inter-domain) |
| ---- | ------------------------- | --------------------------- |
| ASM  | `FF05::B:idx`             | not supported (RFC 8815)    |
| SSM  | `FF35::B:idx`             | `FF3E::B:idx`               |

The group-id (`0x000B`) and the shard-index field are preserved across modes — only the high 32 bits change. All addresses elsewhere in this BRC are written in their ASM (`FF0x`) form; under SSM, substitute the corresponding `FF3x` prefix.

### Address Derivation

Every multicast group address in the BSV shard pipeline is derived from three components:

1. **Multicast prefix** (`MCPrefix`, 2 bytes) — the first two bytes of the IPv6 address, encoding `flags(4) | scope(4)`. Under ASM: `FF05` (site-local), `FF08` (organisation-local), `FF0E` (global). Under SSM: `FF35`, `FF38`, `FF3E` for the same scopes (see [Source Mode and Address Range](#source-mode-and-address-range)).
2. **IANA group-id** (`MCGroupID`, 2 bytes) — occupies bytes `[12:14]`. Default: `0x000B`.
3. **Shard group index** (`IDX`, 2 bytes) — occupies bytes `[14:16]`.

The full 128-bit address is assembled as:

```
[MCPrefix][0x00 × 10][MCGroupID][IDX]
```

For example, with prefix `FF05` (ASM site-local), group-id `0x000B`, and shard index `0x0003`:

```
FF05:0000:0000:0000:0000:0000:000B:0003
```

Compressed form: `FF05::B:3`. The same group under SSM site-local is `FF35::B:3` and under SSM global is `FF3E::B:3` — only the prefix changes.

### Data-Plane Shard Groups

Shard group indices MUST occupy the range `0x0000`–`0x0FFF` (4,096 indices). The maximum permitted value is `shardBits = 12` (4,096 groups), which keeps all shard indices within this range. Indices at or above `0x1000` MUST NOT be used as shard group indices.

The group index for a transaction is derived deterministically from its TxID:

```
groupIndex = binary.BigEndian.Uint32(txid[0:4]) >> (32 - shardBits)
```

Implementations MUST use only the low 16 bits of `groupIndex` when assembling the multicast address.

### Free Space and Specialty Transmission Domains

Indices `0x1000`–`0xF7FF` (59,392 indices) are unassigned and reserved for future use. This range accommodates future expansion of the shard group space, as well as specialty transmission domains for purpose-specific multicast services that are not general-purpose transaction sharding. No current protocol assigns addresses in this range. Implementations MUST NOT join or transmit to addresses in this range unless explicitly specified by a future BRC.

### Control-Plane Reserved Indices

Network service groups occupy `0xF800`–`0xFFFF` (2,048 indices). Current protocol assignments are allocated from the top of this range; the remainder is reserved for future network services. Implementations MUST NOT use unassigned indices within this range. The defined assignments MUST be supported at the scopes indicated; additional scopes are permitted where operationally necessary.

| Index    | Purpose                              | Scope  | Full address (default group-id)                   | Compressed                |
| -------- | ------------------------------------ | ------ | ------------------------------------------------- | ------------------------- |
| `0xFFFA` | Block Header egress (BRC-135)        | varies | `FF05:0000:0000:0000:0000:0000:<egress-gid>:FFFA` | `FF05::<egress-gid>:FFFA` |
| `0xFFFB` | Subtree Announcements (site)         | `FF05` | `FF05:0000:0000:0000:0000:0000:000B:FFFB`         | `FF05::B:FFFB`            |
| `0xFFFB` | Subtree Announcements (org)          | `FF08` | `FF08:0000:0000:0000:0000:0000:000B:FFFB`         | `FF08::B:FFFB`            |
| `0xFFFB` | Subtree Announcements (global)       | `FF0E` | `FF0E:0000:0000:0000:0000:0000:000B:FFFB`         | `FF0E::B:FFFB`            |
| `0xFFFC` | Subtree Group Announcements (site)   | `FF05` | `FF05:0000:0000:0000:0000:0000:000B:FFFC`         | `FF05::B:FFFC`            |
| `0xFFFC` | Subtree Group Announcements (org)    | `FF08` | `FF08:0000:0000:0000:0000:0000:000B:FFFC`         | `FF08::B:FFFC`            |
| `0xFFFC` | Subtree Group Announcements (global) | `FF0E` | `FF0E:0000:0000:0000:0000:0000:000B:FFFC`         | `FF0E::B:FFFC`            |
| `0xFFFD` | Beacon (site)                        | `FF05` | `FF05:0000:0000:0000:0000:0000:000B:FFFD`         | `FF05::B:FFFD`            |
| `0xFFFD` | Beacon (org)                         | `FF08` | `FF08:0000:0000:0000:0000:0000:000B:FFFD`         | `FF08::B:FFFD`            |
| `0xFFFD` | Beacon (global)                      | `FF0E` | `FF0E:0000:0000:0000:0000:0000:000B:FFFD`         | `FF0E::B:FFFD`            |
| `0xFFFE` | Block Broadcast channel              | `FF0E` | `FF0E:0000:0000:0000:0000:0000:000B:FFFE`         | `FF0E::B:FFFE`            |
| `0xFFFF` | *(reserved)*                         | —      | reserved                                          | do not use                |

The addresses above are shown in their ASM (`FF0x`) form. Under SSM, substitute the `FF3x` prefix for the same scope (`FF35` site, `FF3E` global) — the group-id and index are unchanged. For example, the beacon group becomes `FF35::B:FFFD` (site) or `FF3E::B:FFFD` (global). See [Source-Specific Multicast (SSM)](#source-specific-multicast-ssm).

### Virtual HashKey Ingredient Indices

Several control-plane frame types share `GroupBlockBroadcast` (`0xFFFE`) as their egress multicast destination but must form independent per-sender flows so each carries its own monotonic `SeqNum` counter. The proxy's flow key is `(senderIPv6, groupIdx, subtreeID)`; to keep these flows separate while still emitting to the same multicast group, the proxy substitutes a distinct virtual `groupIdx` into the `HashKey` computation defined in [BRC-124](/transactions/0124). These virtual indices are **never** assembled into an actual IPv6 multicast address and MUST NOT be joined or transmitted to; they exist only as inputs to the XXH64 `HashKey` derivation.

| Virtual index | Constant            | Used by             | Egress group |
| ------------- | ------------------- | ------------------- | ------------ |
| `0xFFF8`      | `GroupCoinbaseFlow` | BRC-133 coinbase tx | `0xFFFE`     |
| `0xFFF9`      | `GroupAnchorFlow`   | BRC-134 anchor tx   | `0xFFFE`     |

BRC-131 block announcements continue to use `0xFFFE` itself as the `HashKey` ingredient, so coinbase ([BRC-133](/transactions/0133)) and anchor ([BRC-134](/transactions/0134)) frames remain distinct flows from block announcements even though all three egress to `FF0E::B:FFFE`.

### Group-ID Override

The 16-bit IANA group-id field (bytes `[12:14]`) is configurable via the `-mc-group-id` flag (environment variable `MC_GROUP_ID`). The default is `0x000B` (IANA Bitcoin allocation).

```
-mc-group-id 0x000B    # default (IANA Bitcoin)
-mc-group-id 0xCAFE    # private deployment / lab
```

All group addresses — both shard and network-service — inherit the same group-id. Deployments using different group-ids are entirely isolated at the multicast layer with no protocol changes required.

### Source-Specific Multicast (SSM)

SSM is a transport mode only: the frame format ([BRC-124](/transactions/0124)), the NACK protocol ([BRC-126](/transactions/0126)), the `HashKey` computation, and the shard derivation are all unchanged. SSM affects only the address range (the high 32 bits, per [Source Mode and Address Range](#source-mode-and-address-range)) and how receivers join a group:

* **Joins.** Under ASM, receivers perform an any-source `(*,G)` join. Under SSM, receivers perform a source-specific `(S,G)` join per [RFC 3678](https://www.rfc-editor.org/rfc/rfc3678) (`MCAST_JOIN_SOURCE_GROUP`), one join per `(source, group)` pair.
* **Distinct source per publisher.** Each publisher MUST emit from a distinct, stable unicast source address (`bindSource`). This is required by PIM-SSM reverse-path forwarding and preserves the per-publisher `HashKey` flow identity. Anycast or shared-source emission is not supported under SSM; a deployment needing a single stable identity SHOULD use VRRP active-standby (failover, not load distribution).
* **Source discovery.** Receivers must learn the set of publisher sources for each group before issuing `(S,G)` joins:
  * **Data-plane sources** are distributed via the shard manifest protocol (BRC-139), whose announcements carry the active publisher source set; a receiver unions the sources across currently-valid manifests.
  * **Control-plane groups** (beacon, manifest, subtree announcement) are joined against per-group bootstrap source lists configured out of band (IPv6 literals or DNS names re-resolved on refresh), since their sources cannot be discovered from within the group itself.

### Beacon Groups

Beacon groups (`0xFFFD`) carry ADVERT messages that enable listeners to discover retry endpoint addresses without manual configuration. Each beacon-enabled service instance advertises to exactly one scope group, selected via `-beacon-scope`. Deployments requiring coverage across multiple scopes run separate service instances per scope. The beacon protocol is specified in [BRC-126](/transactions/0126).

### Subtree Announcements

Subtree Announcement groups (`0xFFFB`) carry signed announcements of a subtree's transaction IDs and their ordering within the subtree's Merkle structure. The subtree ID is the Merkle root hash of the announced set. Listeners subscribe to this group to discover transaction batches for downstream filtering. The subtree data frame format carried on this group is specified in [BRC-132](/transactions/0132); the SubtreeID-to-group mapping announcements on `0xFFFC` are specified in [BRC-127](/transactions/0127).

### Subtree Group Announcements

Subtree Group Announcement groups (`0xFFFC`) carry batched announcements of new subtree inclusions in a logical group of subtrees. This enables listeners to discover related transaction groupings and configure automated filtering for special-interest downstream networks. Like beacon and subtree announcement groups, multiple scopes are supported.

### Block Header Egress Channel

`FF05::<egress-gid>:FFFA` (index `0xFFFA`) is used by listener nodes to re-emit standalone BRC-135 block header frames to downstream consumers such as SPV wallets and header-chain validators. The scope and group-id are configured independently of the ingress fabric via the listener's egress flags, allowing isolation of the egress domain. BRC-135 frames MUST NOT be re-injected onto `FF0E::B:FFFE` to avoid feedback loops. The block header frame format is specified in [BRC-135](/transactions/0135).

### Block Broadcast Channel

Index `0xFFFE` (global scope) is a mandatory channel distributing block headers, block templates, coinbase transactions, chained-transaction anchors, and other producer data of interest to all network participants. All conformant network participants MUST join this group. The concrete address depends on the source mode: under ASM the channel is `FF0E::B:FFFE` and is intra-domain only (inter- domain ASM is deprecated by [RFC 8815](https://www.rfc-editor.org/rfc/rfc8815)); inter-domain distribution uses the SSM address `FF3E::B:FFFE`.

## References

* [BRC-82: Defining a Scalable IPv6 Multicast Protocol for Blockchain Transaction Broadcast and Update Delivery](/peer-to-peer/0082) — Overall multicast protocol architecture
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Wire format for data-plane frames
* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — Subtree ID concept referenced by the subtree announcement groups
* [BRC-126: NACK Retransmission Protocol](/transactions/0126) — ADVERT beacon wire format and endpoint discovery
* [BRC-127: Subtree Group Announcement Frame Format](/transactions/0127) — SubtreeID-to-group mapping announcements on `0xFFFC`
* [BRC-132: Multicast Subtree Data Frame Format](/transactions/0132) — subtree data frames carried on the `0xFFFB` GroupSubtreeDataAnnounce group
* BRC-139: Multicast Shard Manifest Announcement Protocol — distributes the active publisher source set used for SSM data-plane source discovery
* [RFC 4607: Source-Specific Multicast for IP](https://www.rfc-editor.org/rfc/rfc4607) — defines the `FF3x::/32` SSM address range
* [RFC 3678: Socket Interface Extensions for Multicast Source Filters](https://www.rfc-editor.org/rfc/rfc3678) — `MCAST_JOIN_SOURCE_GROUP` `(S,G)` join API
* [RFC 8815: Deprecating Any-Source Multicast (ASM) for Interdomain Multicast](https://www.rfc-editor.org/rfc/rfc8815) — rationale for SSM-only global scope


# Multicast Transaction Frame Fragmentation

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies a fragmentation extension to the BRC-124 Multicast Transaction Frame Format. When a BSV transaction payload exceeds the path MTU available on a multicast fabric, the proxy decomposes the payload into a sequence of fixed-size fragment datagrams. Listeners reassemble the fragments and verify the reconstructed payload against the transaction ID before forwarding. The fragment header is a strict superset of the BRC-124 header, enabling mixed-version infrastructure to identify and discard fragment frames it cannot process.

Fragmentation is transport-agnostic: fragment datagrams are identical on the wire under ASM and SSM, and every fragment of a transaction is emitted from the same proxy source (`senderIPv6`), so a receiver joins a single `(S,G)` for all of them. Multicast addressing and the SSM join and source-discovery model are specified in [BRC-129](/transactions/0129).

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

BSV transactions can be arbitrarily large. The 1500-byte Ethernet MTU imposes a hard limit on single-datagram UDP delivery; transactions exceeding \~1300 bytes of payload after IPv6 and UDP overhead cannot be transported in a single BRC-124 frame without IP fragmentation, which is unreliable on multicast paths and disabled by default on many fabrics.

Rather than rely on IP-layer fragmentation (which requires receiver kernel reassembly and breaks on many switch configurations), BRC-130 performs fragmentation at the application layer:

1. The proxy slices the payload into *k* equal-sized chunks and emits *k* independent UDP datagrams.
2. Each datagram carries a complete BRC-130 header that is layout-compatible with BRC-124, allowing existing firewall rules, packet classifiers, and traffic monitors to inspect the frame header fields.
3. Listeners reassemble fragments keyed on the Transaction ID and verify the result with SHA256d before forwarding.

## Specification

### Fragment Header Format (104 bytes)

A BRC-130 fragment datagram consists of a 104-byte header followed by the fragment data. All multi-byte integers are big-endian.

| Offset | Size | Align | Field          | Description                                               |
| ------ | ---- | ----- | -------------- | --------------------------------------------------------- |
| 0      | 4    | —     | Network Magic  | 0xE3E1F3E8 (BSV mainnet P2P magic)                        |
| 4      | 2    | —     | Protocol Ver   | 0x02BF (703, BSV large-block baseline)                    |
| 6      | 1    | —     | Frame Version  | 0x03 (BRC-130 fragment)                                   |
| 7      | 1    | —     | MsgType        | Carried-through MsgType (byte 7); 0x00 for BRC-124/128    |
| 8      | 32   | 8B    | Transaction ID | SHA256d(reassembled payload); same on every fragment      |
| 40     | 8    | 8B    | HashKey        | XXH64(senderIPv6 ∥ groupIdx ∥ subtreeID); per-flow        |
| 48     | 8    | 8B    | SeqNum         | Per-flow monotonic counter; independent per fragment      |
| 56     | 32   | 8B    | Subtree ID     | 32-byte batch identifier; zeros = unset                   |
| 88     | 4    | 8B    | PayloadLen     | Size of this fragment's data bytes (uint32 BE)            |
| 92     | 4    | 4B    | OrigPayloadLen | Total unfragmented payload length (uint32 BE)             |
| 96     | 2    | 2B    | FragIndex      | 0-based index of this fragment (uint16 BE)                |
| 98     | 2    | 2B    | FragTotal      | Total number of fragments in this transaction (uint16 BE) |
| 100    | 1    | —     | OrigFrameVer   | Original FrameVer before fragmentation; 0x00 ⇒ 0x02       |
| 101    | 3    | —     | Reserved       | Must be 0x000000                                          |
| 104    | \*   | —     | Fragment data  | Slice of the original payload (PayloadLen bytes)          |

Bytes 0–91 are **layout-identical** to a BRC-124 frame header (`FrameVer=0x03` and the byte-7 `MsgType` excepted). Existing infrastructure that inspects the Transaction ID, HashKey, SeqNum, or Subtree ID fields in a BRC-124 header will read correct values from a BRC-130 datagram at the same offsets.

### Field Definitions

#### Network Magic (bytes 0–3)

`0xE3E1F3E8` — identical to BRC-124. Enables BSV firewall rules and monitoring tools to classify fragment datagrams without modification.

#### Protocol Version (bytes 4–5)

`0x02BF` — identical to BRC-124. Informational; receivers do not validate it.

#### Frame Version (byte 6)

`0x03` — identifies this datagram as a BRC-130 fragment. Infrastructure that does not implement BRC-130 reassembly must silently discard datagrams with this version byte.

#### MsgType (byte 7)

The frame-type `MsgType` carried through from byte 7 of the original (pre-fragmentation) frame, so reassembly can reconstruct it. For fragmented BRC-124/128 transaction frames this byte is `0x00` (their byte 7 is reserved); for fragmented BRC-131 block frames it is `0x01`/`0x02`, for BRC-132 subtree frames `0x01`/`0x02`, and for BRC-149 BEEF object frames `0x00` (their byte 7 is reserved). Together with `OrigFrameVer` (byte 100) it fully restores the original header bytes 6–7 on reassembly.

#### Transaction ID (bytes 8–39)

The 32-byte double-SHA256 hash of the complete unfragmented payload:

```
TxID = SHA256(SHA256(reassembled_payload))
```

The same TxID appears on every fragment of a transaction. It serves as both the reassembly key and the integrity proof: after reassembly, the listener must verify `SHA256(SHA256(payload)) == TxID`.

#### HashKey (bytes 40–47)

An 8-byte stable per-flow identifier, computed by the proxy as:

```
HashKey = XXH64(senderIPv6[16B] ∥ groupIdx[4B BE] ∥ subtreeID[32B])
```

`XXH64` is the 64-bit xxHash algorithm (`github.com/cespare/xxhash/v2`). HashKey is the same for every BRC-124 and BRC-130 frame emitted by the same (sender, multicast-group, subtree-batch) flow. Senders set this field to 0; the proxy stamps it in-place.

**Note:** For fragment datagrams, the proxy stamps an **independent** HashKey and SeqNum per fragment (i.e., each fragment is treated as a separate frame for gap-tracking purposes). This allows retransmission of individual lost fragments via the standard BRC-126 NACK mechanism without changes to the retry endpoint.

#### SeqNum (bytes 48–55)

An 8-byte per-flow monotonic counter, starting at 1. The proxy increments the per-flow counter independently for each fragment datagram. Listeners detect gaps at the fragment granularity, enabling single-fragment retransmission.

Senders set SeqNum to 0; the proxy stamps it before forwarding.

#### Subtree ID (bytes 56–87)

Identical to BRC-124. A 32-byte batch identifier shared by all transactions belonging to the same subtree (BRC-119 STUMP root). All-zero bytes indicate the field is unset.

#### PayloadLen (bytes 88–91)

The number of fragment data bytes carried by **this datagram** — not the total original payload length. Equals `min(fragDataSize, origPayloadLen - fragIndex * fragDataSize)` for the last fragment, and `fragDataSize` for all prior fragments.

#### OrigPayloadLen (bytes 92–95)

The total length of the unfragmented payload in bytes. All fragments of the same transaction carry the same `OrigPayloadLen` value. Receivers use this to allocate a reassembly buffer of exactly the correct size.

#### FragIndex (bytes 96–97)

The 0-based index of this fragment within the transaction. Values range from `0` to `FragTotal - 1`.

#### FragTotal (bytes 98–99)

The total number of fragments into which the payload was divided. All fragments of the same transaction carry the same `FragTotal` value. Must be ≥ 1. `FragTotal = 1` is valid and means the payload was not split (degenerate case, used only for testing).

#### OrigFrameVer (byte 100)

The `FrameVer` of the original frame before fragmentation (`0x02` BRC-124/128, `0x04` BRC-131, `0x05` BRC-132, `0x09` BRC-149 BEEF object). A value of `0x00` defaults to `0x02` (BRC-124). Listeners use it — together with the byte-7 `MsgType` — to reconstruct the correct synthetic frame on reassembly.

#### Reserved (bytes 101–103)

Must be `0x000000`. Reserved for future protocol extensions.

### Fragment Data (byte 104 onward)

The slice of the original payload bytes corresponding to this fragment:

```
fragment[i].data = payload[i * fragDataSize : (i+1) * fragDataSize]
```

The last fragment carries the remaining bytes, which may be shorter than `fragDataSize`.

### Fragment Data Size

The fragment data size (`fragDataSize`) is derived from the configured path MTU:

```
fragDataSize = pathMTU - IPv6HeaderSize - UDPHeaderSize - BRC130HeaderSize
             = pathMTU - 40 - 8 - 104
             = pathMTU - 152
```

For standard Ethernet (pathMTU = 1500): `fragDataSize = 1348 bytes`.\
For jumbo frames (pathMTU = 9000): `fragDataSize = 8848 bytes`.

### Reassembly

A listener reassembles a fragmented transaction as follows:

1. **Slot allocation.** On receipt of the first fragment for a reassembly key, allocate a slot containing a `OrigPayloadLen`-byte buffer, a received-fragment bitmask of `FragTotal` bits, and a TTL timer.

   The reassembly key is the Transaction ID (bytes 8–39), **except** where the carried plane derives that hash from payload content that several concurrent frames legitimately share. For `OrigFrameVer = 0x09` (BRC-149 BEEF object) the key MUST be the pair (Transaction ID, Subtree ID) — bytes 8–39 carry the ContentID and bytes 56–87 the TopicID, and one object submitted to several topics is emitted once per topic with a shared ContentID and a distinct TopicID. Keying such fragments on bytes 8–39 alone merges the sibling emissions into a single slot, so all but one topic silently lose the object.
2. **Fragment placement.** Copy fragment data into the buffer at `offset = fragIndex * fragDataSize`. Mark the fragment index as received.
3. **Completion check.** When all `FragTotal` fragment indices are marked received, proceed to verification.
4. **Hash verification.** Where bytes 8–39 are a double-SHA256 of the payload — `OrigFrameVer` `0x00`/`0x02` (transaction, TxID) and `0x09` (BRC-149 BEEF object, ContentID) — compute `SHA256(SHA256(buffer))` and compare. If they differ, drop the slot and increment `bsl_reassembly_hash_mismatch_total`.

   This check does not apply to planes whose bytes 8–39 carry a different commitment: `0x04` (BRC-131 block control) and `0x05` (BRC-132 subtree data, whose Subtree ID is a Merkle root rather than a hash of the fragmented bytes). Those planes complete on the fragment bitmask alone; a subtree implementation MAY additionally verify the reassembled data against the Merkle root it commits to.
5. **Delivery.** Construct a synthetic frame of the original type — `FrameVer = OrigFrameVer` (byte 100, `0x00` ⇒ `0x02`) with byte 7 restored from the carried `MsgType` — carrying the reassembled payload and the Transaction ID, HashKey, SeqNum, and Subtree ID from the first fragment received. A BEEF object (`0x09`) is therefore rebuilt as a BRC-149 frame with its ContentID and TopicID in place, not as a BRC-124 transaction frame; reconstructing every plane as `0x02` would strip the routing identity the downstream filter needs. Route the frame through the normal filter → egress → gap-tracking path for its plane.
6. **TTL eviction.** If a slot's TTL expires before all fragments arrive, discard the partial buffer and increment `bsl_reassembly_abandoned_total`. The default TTL is 10 seconds.
7. **Slot limits.** A reassembly buffer implementation may enforce a maximum number of concurrent slots (default: 4096). When the limit is reached, the oldest incomplete slot is evicted to make room for the new TxID.
8. **Duplicate fragments.** Duplicate fragments (same TxID + FragIndex) are silently ignored.

### Reassembly Metrics

Listeners implementing BRC-130 reassembly expose the following Prometheus counters:

| Metric                               | Description                                           |
| ------------------------------------ | ----------------------------------------------------- |
| `bsl_reassembly_started_total`       | New reassembly slots opened (first fragment received) |
| `bsl_reassembly_completed_total`     | Reassemblies completed and delivered downstream       |
| `bsl_reassembly_abandoned_total`     | Slots evicted due to TTL expiry or max-slots eviction |
| `bsl_reassembly_hash_mismatch_total` | Reassembled payloads that failed SHA256d verification |

### Error Handling

| Condition                      | Action                                      |
| ------------------------------ | ------------------------------------------- |
| Fragment version byte ≠ 0x03   | Not a BRC-130 datagram; decode as BRC-124   |
| Bad magic                      | Silent drop                                 |
| FragIndex ≥ FragTotal          | Silent drop (malformed fragment)            |
| FragTotal = 0                  | Silent drop (malformed)                     |
| OrigPayloadLen = 0             | Silent drop                                 |
| PayloadLen > fragDataSize      | Silent drop                                 |
| Datagram shorter than header   | Silent drop                                 |
| Hash mismatch after reassembly | Drop slot; increment hash\_mismatch counter |
| TTL expiry                     | Drop slot; increment abandoned counter      |

## Examples

### Two-Fragment Transaction (1500-byte path MTU)

A 2000-byte transaction payload is split into two fragments under a 1500-byte path MTU (`fragDataSize = 1348`):

**Fragment 0 (1452 bytes on wire):**

```
// BRC-130 header (104 bytes)
E3E1F3E8          // Network Magic
02BF              // Protocol Version
03                // Frame Version (BRC-130)
00                // Reserved
<32B TxID>        // SHA256d of full payload
<8B HashKey>      // XXH64(senderIPv6 ∥ groupIdx ∥ subtreeID)
<8B SeqNum>       // e.g. 0x0000000000000001
<32B SubtreeID>
00000544          // PayloadLen = 1348 (0x544)
000007D0          // OrigPayloadLen = 2000 (0x7D0)
0000              // FragIndex = 0
0002              // FragTotal = 2
00                // OrigFrameVer (0x00 = BRC-124)
000000            // Reserved

// Fragment data (1348 bytes: payload[0:1348])
```

**Fragment 1 (756 bytes on wire):**

```
// BRC-130 header (104 bytes)
E3E1F3E8
02BF
03
00
<32B TxID>        // Same TxID as fragment 0
<8B HashKey>      // Same flow HashKey
<8B SeqNum>       // e.g. 0x0000000000000002
<32B SubtreeID>
0000028C          // PayloadLen = 652 (0x28C; last fragment: 2000 - 1348 = 652)
000007D0          // OrigPayloadLen = 2000
0001              // FragIndex = 1
0002              // FragTotal = 2
00                // OrigFrameVer (0x00 = BRC-124)
000000            // Reserved

// Fragment data (652 bytes: payload[1348:2000])
```

## Compatibility

* **BRC-12 (FrameVer 0x01) receivers:** Must discard datagrams with FrameVer 0x03.
* **BRC-124 (FrameVer 0x02) receivers:** Must discard datagrams with FrameVer 0x03. BRC-130 fragments will not parse as valid BRC-124 frames because the `PayloadLen` field carries the fragment data size, not the full transaction length.
* **BRC-130 receivers:** Implement the reassembly procedure above and deliver a synthetic BRC-124 frame to downstream consumers.

The BRC-130 header is intentionally layout-compatible with BRC-124 at bytes 0–91 so that packet classifiers, firewall rules, and monitoring tools that inspect common header fields (magic, version, TxID, HashKey, SeqNum, SubtreeID) work correctly on fragment datagrams without modification.

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — Payload format for BSV transactions
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Base frame format extended by this BRC
* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — Defines the Subtree ID concept
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery

## Constants Reference

| Name                | Value | Hex    | Description                        |
| ------------------- | ----- | ------ | ---------------------------------- |
| FrameVerV3          | 3     | 0x03   | BRC-130 fragment frame version     |
| HeaderSizeV3        | 104   | 0x68   | BRC-130 header size in bytes       |
| IPv6HeaderSize      | 40    | 0x28   | IPv6 header overhead               |
| UDPHeaderSize       | 8     | 0x08   | UDP header overhead                |
| MinPathMTU          | 1280  | 0x500  | IPv6 minimum MTU (RFC 8200)        |
| EthernetMTU         | 1500  | 0x5DC  | Standard Ethernet MTU              |
| JumboMTU            | 9000  | 0x2328 | Jumbo frame MTU                    |
| DefaultFragDataSize | 1348  | 0x544  | fragDataSize at 1500-byte path MTU |


# Multicast Block Announcement Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the wire format for distributing block-level metadata over the IPv6 multicast fabric. A new frame version (`0x04`) reuses the BRC-124 92-byte header layout and carries either a block announcement (block header + coinbase TxID + subtree hashes) or a coinbase transaction, delivered to all subscribers via a dedicated control-plane multicast group (`FF0E::B:FFFE`).

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

The multicast fabric defined in BRC-124 shards transaction delivery across deterministic IPv6 multicast groups. Block events require a complementary distribution path:

1. **Block announcements** must reach every subscriber — miners, exchanges, and overlay services — so they can update block templates, advance chain state, and validate received transactions against the new tip.
2. **Coinbase transactions** must be delivered to all subscribers regardless of shard assignment, since every node needs the coinbase to reconstruct the block Merkle root.
3. Both events must carry the same sequencing and reliability guarantees (HashKey/SeqNum stamping and NACK-based retransmission) as ordinary transaction frames, so existing retry infrastructure works without modification.

Defining a new `FrameVer` byte satisfies these requirements with minimal protocol surface: the BRC-124 header layout is fully preserved, the same proxy/listener/retry machinery applies, and the only routing change is substituting the shard-derived multicast group with the dedicated control group.

## Specification

### Frame Header Format (92 bytes)

The BRC-131 header is layout-identical to the BRC-124 header. All multi-byte integers are big-endian.

| Offset | Size | Alignment | Field          | Description                                        |
| ------ | ---- | --------- | -------------- | -------------------------------------------------- |
| 0      | 4    | —         | Network Magic  | 0xE3E1F3E8 (BSV mainnet P2P magic)                 |
| 4      | 2    | —         | Protocol Ver   | 0x02BF (703, BSV large-block baseline)             |
| 6      | 1    | —         | Frame Version  | 0x04 — BRC-131 block control                       |
| 7      | 1    | —         | MsgType        | 0x01 = BlockAnnounce; 0x02 = CoinbaseTx            |
| 8      | 32   | 8-byte    | ContentID      | BlockHash (announce) or CoinbaseTxID (coinbase)    |
| 40     | 8    | 8-byte    | HashKey        | XXH64(senderIPv6 ∥ flowIdx ∥ zeros); proxy-stamped |
| 48     | 8    | 8-byte    | SeqNum         | Per-sender monotonic counter; stamped by proxy     |
| 56     | 32   | 8-byte    | Reserved32     | All zeros                                          |
| 88     | 4    | 8-byte    | Payload Length | uint32 BE                                          |
| 92     | \*   | —         | Payload        | MsgType-specific (see below)                       |

**Distinction from BRC-124:** Byte 7 carries `MsgType` rather than `Reserved=0x00`. The 32 bytes at 56–87 are always zeros (no subtree ID; block frames address all subscribers).

### Field Definitions

#### Network Magic (bytes 0–3)

The value `0xE3E1F3E8` (BSV mainnet P2P magic). Frames with incorrect magic are rejected.

#### Protocol Version (bytes 4–5)

`0x02BF` (703). Informational; receivers do not validate.

#### Frame Version (byte 6)

`0x04` for BRC-131 frames. Any other value causes the frame to be handled by a different decoder.

#### MsgType (byte 7)

Identifies the payload type:

| Value  | Constant           | Payload                                           |
| ------ | ------------------ | ------------------------------------------------- |
| `0x01` | `BlockMsgAnnounce` | Block header + CoinbaseTxID + subtree root hashes |
| `0x02` | `BlockMsgCoinbase` | Raw serialized coinbase transaction bytes         |

Any other value is rejected.

#### ContentID (bytes 8–39)

A 32-byte identifier:

* **BlockAnnounce:** the block hash in internal byte order (not reversed display order).
* **CoinbaseTx:** SHA256d of the raw coinbase transaction bytes (the CoinbaseTxID).

#### HashKey (bytes 40–47)

`uint64` big-endian. XXH64 of `(senderIPv6[16] ∥ flowIdx[4] ∥ zeroSubtreeID[32])` where `flowIdx` is a 4-byte big-endian value selected by `MsgType`, so the two payload types form independent flows on the shared egress group:

* **BlockAnnounce** (`MsgType 0x01`): `flowIdx = 0x0000FFFE` (`GroupBlockBroadcast`).
* **CoinbaseTx** (`MsgType 0x02`): `flowIdx = 0x0000FFF8` (`GroupCoinbaseFlow`, a virtual HashKey index — see [BRC-129](/transactions/0129) and [BRC-133](/transactions/0133)).

Stamped in-place by the proxy if zero on arrival.

#### SeqNum (bytes 48–55)

`uint64` big-endian. Monotonic per-sender counter for the `(HashKey, flowIdx)` flow. Stamped in-place by the proxy if zero on arrival. Used by listeners and retry endpoints as the secondary cache key.

#### Reserved32 (bytes 56–87)

All zero bytes. Block control frames have no subtree ID; the field is reserved for future use.

#### Payload Length (bytes 88–91)

`uint32` big-endian. Number of payload bytes immediately following the header.

#### Payload (byte 92 onward)

MsgType-specific (see below).

### BlockAnnounce Payload (MsgType 0x01)

| Offset | Size   | Field         | Description                                       |
| ------ | ------ | ------------- | ------------------------------------------------- |
| 0      | 80     | BlockHeader   | Standard 80-byte BSV block header                 |
| 80     | 32     | CoinbaseTxID  | SHA256d of the coinbase transaction (internal BO) |
| 112    | 4      | SubtreeCount  | Number of subtree root hashes (uint32 BE); ≥ 0    |
| 116    | 32 × N | SubtreeHashes | Ordered list of N subtree Merkle root hashes      |

**Minimum payload size:** 116 bytes (N = 0). **Total payload size:** 116 + 32 × N bytes.

The `ContentID` in the frame header is the 32-byte block hash. The `SubtreeHashes` are the ordered Merkle roots of the sharded transaction subtrees included in this block, matching the producer's subtree enumeration. The count may be zero for empty blocks or blocks with no sharded subtrees.

### CoinbaseTx Payload (MsgType 0x02)

The payload is the raw serialized coinbase transaction — the same encoding as a BRC-12 transaction payload (version LE32 + inputs + outputs + locktime LE32), with no additional envelope.

The `ContentID` in the frame header is the SHA256d of these raw bytes (the CoinbaseTxID), identical to the `CoinbaseTxID` field in the corresponding `BlockAnnounce` frame.

### Control-Plane Multicast Group

BRC-131 frames are distributed exclusively on the **GroupBlockBroadcast** group:

| Index  | Scope  | IPv6 Address | Constant            |
| ------ | ------ | ------------ | ------------------- |
| 0xFFFE | global | FF0E::B:FFFE | GroupBlockBroadcast |

The global scope (`FF0E`) ensures block announcements cross site boundaries. The group index `0xFFFE` is in the reserved control-plane range and is never used as a shard group for any `shard_bits` ≤ 12.

The address is shown in ASM form (`FF0E::B:FFFE`), which under RFC 8815 is intra-domain only; inter-domain delivery across site boundaries uses the SSM address `FF3E::B:FFFE` (see [BRC-129](/transactions/0129)). The frame format, `HashKey`, `SeqNum`, and NACK path are unchanged across modes. Under SSM, receivers `(S,G)`-join using the block-announce source (the emitting proxy, `senderIPv6`); block-broadcast sources are distributed per BRC-129.

### Sequence Tracking and Retransmission

BRC-131 frames use the same XXH64 hash-chain sequencing as BRC-124:

* **HashKey** is computed as `XXH64(senderIPv6 ∥ flowIdx ∥ zeroSubtreeID)` where `flowIdx = 0x0000FFFE` for BlockAnnounce and `0x0000FFF8` for CoinbaseTx (see the HashKey field definition).
* **SeqNum** is a monotonic per-sender counter for the `(HashKey, flowIdx)` flow.
* The proxy stamps both fields in-place if `SeqNum == 0` on arrival.
* Listeners detect gaps by comparing consecutive `SeqNum` values on the control flow and dispatch BRC-126 NACKs to retry endpoints.
* Retry endpoints cache BRC-131 frames by `HashKey ∥ SeqNum` and retransmit to `FF0E::B:FFFE` on NACK. **The retransmit destination is the control group, not the shard group derived from ContentID.**

### TCP Transport

The read sequence is identical to BRC-124:

1. Read 44 bytes (sufficient for legacy header or BRC-131 start).
2. Inspect `FrameVer` at byte 6.
   * **Version 0x04 (BRC-131):** read 48 more bytes to complete the 92-byte header; `PayLen` is at bytes 88–91.
3. Read exactly `PayLen` bytes.
4. Process the complete frame.

### Fragmentation (BRC-130 Extension)

When `len(Payload) > fragDataSize`, the proxy fragments the frame using BRC-130. The `OrigFrameVer` field at byte 100 of the BRC-130 fragment header is set to `0x04`. The `MsgType` byte is preserved in the BRC-130 fragment's byte 7. Fragment gap tracking uses the `(HashKey, flowIdx, zeroSubtreeID)` flow identically to BRC-124 fragments.

Block announcements for typical blocks fit well within a 9000-byte jumbo frame (116 + 32 × 128 = 4212 bytes for 128 subtrees). Fragmentation is primarily relevant for large `CoinbaseTx` payloads.

### Error Handling

| Condition                | UDP Behavior | TCP Behavior      |
| ------------------------ | ------------ | ----------------- |
| Bad magic                | Silent drop  | Connection closed |
| FrameVer ≠ 0x04          | Not BRC-131  | Not BRC-131       |
| Unknown MsgType          | Silent drop  | Connection closed |
| Payload length too large | Silent drop  | Connection closed |
| Truncated datagram       | Silent drop  | Connection closed |
| SeqNum == 0 at listener  | Discard      | Discard           |

## Examples

### BlockAnnounce Frame (N=2 subtrees)

```
// Header (92 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
04                                                                // Frame Version (BRC-131)
01                                                                // MsgType (BlockAnnounce)
<32-byte block hash>                                              // ContentID
<8-byte HashKey>                                                  // Proxy-stamped XXH64
<8-byte SeqNum>                                                   // Proxy-stamped monotonic
0000000000000000000000000000000000000000000000000000000000000000  // Reserved32
000000B4                                                          // PayloadLen = 180 (116 + 2×32)

// Payload (148 bytes)
<80-byte block header>                                            // BlockHeader
<32-byte coinbase TxID>                                           // CoinbaseTxID
00000002                                                          // SubtreeCount = 2
<32-byte subtree hash 0>                                          // SubtreeHashes[0]
<32-byte subtree hash 1>                                          // SubtreeHashes[1]
```

### CoinbaseTx Frame

```
// Header (92 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
04                                                                // Frame Version (BRC-131)
02                                                                // MsgType (CoinbaseTx)
<32-byte CoinbaseTxID>                                            // ContentID = SHA256d(payload)
<8-byte HashKey>                                                  // Proxy-stamped XXH64
<8-byte SeqNum>                                                   // Proxy-stamped monotonic
0000000000000000000000000000000000000000000000000000000000000000  // Reserved32
<4-byte PayloadLen>                                               // Coinbase tx byte count

// Payload
<raw coinbase transaction bytes>
```

## Alignment Verification

| Field          | Offset | Offset % 8 |
| -------------- | ------ | ---------- |
| ContentID      | 8      | 0 ✓        |
| HashKey        | 40     | 0 ✓        |
| SeqNum         | 48     | 0 ✓        |
| Reserved32     | 56     | 0 ✓        |
| Payload Length | 88     | 0 ✓        |
| Payload        | 92     | 4          |

## Constants Reference

| Name                    | Value | Hex    | Description                                  |
| ----------------------- | ----- | ------ | -------------------------------------------- |
| FrameVerV4              | 4     | 0x04   | BRC-131 block control frame version          |
| BlockMsgAnnounce        | 1     | 0x01   | MsgType: block announcement                  |
| BlockMsgCoinbase        | 2     | 0x02   | MsgType: coinbase transaction                |
| GroupBlockBroadcast     | 65534 | 0xFFFE | Control-plane group index for block frames   |
| BlockHeaderSize         | 80    | 0x50   | Standard BSV block header size (bytes)       |
| BlockAnnounceMinPayload | 116   | 0x74   | Minimum BlockAnnounce payload (N=0 subtrees) |
| HeaderSize              | 92    | 0x5C   | BRC-131 header size (identical to BRC-124)   |

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — Coinbase payload encoding
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Header layout reused by BRC-131
* [BRC-126: Multicast Retransmission Protocol](/transactions/0126) — NACK/ACK/MISS used for block frame retransmission
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — GroupBlockBroadcast index allocation
* [BRC-130: Multicast Transaction Frame Fragmentation](/transactions/0130) — BRC-130 fragmentation with OrigFrameVer=0x04
* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — Defines the subtree concept referenced in BlockAnnounce


# Multicast Subtree Data Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC defines frame version `0x05` for distributing complete Merkle subtree contents — transaction hashes and optional per-transaction metadata — over the IPv6 multicast fabric. Subtree data frames are delivered to all subscribers via the dedicated `GroupSubtreeDataAnnounce` multicast group (`FF0X::B:FFFB`), independently of the per-shard groups used for individual transaction distribution.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

The multicast fabric distributes individual transactions shard-by-shard (BRC-124) and block-level metadata (BRC-131). BRC-132 fills the remaining gap: delivering the contents of each Merkle subtree so that subscribers can:

1. Reconstruct the subtree Merkle tree locally.
2. Verify block inclusion without fetching individual transactions.
3. Power block-assembly tooling and downstream analytics without per-transaction retrieval.

BRC-132 complements BRC-127, which maps SubtreeIDs to GroupIDs for shard-level filtering. BRC-127 announces *where* a subtree lives; BRC-132 delivers *what* it contains.

## Specification

### Multicast Group

Subtree data frames are sent to the `GroupSubtreeDataAnnounce` group (index `0xFFFB`):

| Index  | Scope  | Compressed Address | Constant                 |
| ------ | ------ | ------------------ | ------------------------ |
| 0xFFFB | site   | FF05::B:FFFB       | GroupSubtreeDataAnnounce |
| 0xFFFB | org    | FF08::B:FFFB       | GroupSubtreeDataAnnounce |
| 0xFFFB | global | FF0E::B:FFFB       | GroupSubtreeDataAnnounce |

Scope selection follows the `GroupBeacon` pattern defined in BRC-129. Operators select one or more scopes via the `-announce-scope` flag on listening components.

Addresses are shown in ASM (`FF0x`) form; under SSM, substitute the `FF3x` prefix per [BRC-129](/transactions/0129) (`FF3E::B:FFFB` for inter-domain scope, SSM-only per RFC 8815). The frame format and NACK/fragmentation paths are unchanged. Under SSM, receivers `(S,G)`-join using the subtree-data source (the emitting proxy, `senderIPv6`); the source set is distributed per BRC-129 (data-plane sources via the shard manifest).

### Frame Header Format (92 bytes)

The BRC-132 header is layout-identical to the BRC-124 header. All infrastructure components that inspect Magic, HashKey, or SeqNum read correct values at the same offsets. The header is read using the same 44+48 two-step protocol as BRC-124 and BRC-131.

| Offset | Size | Align | Field         | Value / Notes                                            |
| ------ | ---- | ----- | ------------- | -------------------------------------------------------- |
| 0      | 4    | —     | Network Magic | `0xE3E1F3E8` (BSV mainnet P2P magic)                     |
| 4      | 2    | —     | Protocol Ver  | `0x02BF` (703, BSV large-block baseline)                 |
| 6      | 1    | —     | Frame Version | `0x05` — BRC-132 subtree data                            |
| 7      | 1    | —     | MsgType       | `0x01` = HashesOnly, `0x02` = FullNodes                  |
| 8      | 32   | 8B    | SubtreeID     | SHA-256 Merkle root of the subtree (content ID)          |
| 40     | 8    | 8B    | HashKey       | `XXH64(senderIPv6 ∥ 0xFFFB ∥ SubtreeID)`; proxy-stamped  |
| 48     | 8    | 8B    | SeqNum        | Monotonic counter per (sender, SubtreeID); proxy-stamped |
| 56     | 32   | 8B    | LayoutPad32   | All zeros; retained for uniform `HeaderSize = 92`        |
| 88     | 4    | 8B    | PayloadLen    | Payload size in bytes (uint32 BE)                        |
| 92     | \*   | —     | Payload       | MsgType-specific subtree data (see §Payload Format)      |

**Key distinctions from BRC-124:**

* Byte 7 carries `MsgType` rather than `Reserved = 0x00`.
* Bytes 8–39 carry `SubtreeID` (the Merkle root), not a `TxID`.
* `LayoutPad32` (bytes 56–87) is always zero. SubtreeID already serves as content identifier and flow scope; no secondary field is needed. The field is retained so that `HeaderSize = 92` remains uniform across V2/V4/V5.
* `HashKey` is computed as `XXH64(senderIPv6 ∥ 0xFFFB ∥ SubtreeID)`. Each distinct subtree from the same sender has an independent sequence stream, so loss in one subtree does not create false gaps in another.

### MsgType Values

| MsgType | Constant               | Node Size | Description                             |
| ------- | ---------------------- | --------- | --------------------------------------- |
| `0x01`  | `SubtreeMsgHashesOnly` | 32 bytes  | TxHashes only (network transfer format) |
| `0x02`  | `SubtreeMsgFullNodes`  | 48 bytes  | TxHash + Fee + Size per node            |

Any other MsgType value causes the frame to be rejected with `ErrBadSubtreeMsg`.

### Payload Format

Both MsgType variants share a 24-byte metadata prefix followed by N node entries and a conflict set.

#### Common Prefix (24 bytes)

| Offset | Size | Field          | Description                                     |
| ------ | ---- | -------------- | ----------------------------------------------- |
| 0      | 8    | TotalFees      | Aggregate fee sum for the subtree (uint64 BE)   |
| 8      | 8    | TotalSizeBytes | Aggregate serialised tx size (uint64 BE, bytes) |
| 16     | 8    | NodeCount      | Number of transaction nodes (uint64 BE)         |

#### MsgType 0x01 — HashesOnly

| Offset       | Size   | Field                     |
| ------------ | ------ | ------------------------- |
| 24           | 32 × N | TxHashes                  |
| 24 + 32N     | 8      | ConflictCount (uint64 BE) |
| 24 + 32N + 8 | 32 × M | ConflictHashes            |

Maximum size at 1M nodes, 0 conflicts: 24 + 32 × 1,048,576 + 8 ≈ **32 MB**.

#### MsgType 0x02 — FullNodes

| Offset       | Size   | Field                                            |
| ------------ | ------ | ------------------------------------------------ |
| 24           | 48 × N | Nodes: TxHash (32B) ∥ Fee (8B BE) ∥ Size (8B BE) |
| 24 + 48N     | 8      | ConflictCount (uint64 BE)                        |
| 24 + 48N + 8 | 32 × M | ConflictHashes                                   |

Maximum size at 1M nodes, 0 conflicts: 24 + 48 × 1,048,576 + 8 ≈ **48 MB**.

### Fragmentation

Payloads of 32–48 MB exceed any path MTU. The proxy fragments each BRC-132 frame using BRC-130:

* `OrigFrameVer = 0x05` in each BRC-130 fragment header (byte 100).
* `MsgType` is preserved in fragment header byte 7 (same pattern as BRC-131 `fragmentBlock`).
* Fragment reassembly is keyed by SubtreeID (bytes 8–39), matching the `TxID` slot used by BRC-124 fragments.
* SHA256d hash verification does **not** apply — SubtreeID is a Merkle root, not a payload double-hash. The `verifyHash` flag must be `false` for V5 reassembly slots.
* Optional post-reassembly Merkle-root verification is available (see §Merkle Verification).

Fragment counts at MTU 9000 (fragDataSize = 8848 bytes):

| Subtree size                   | Fragments |
| ------------------------------ | --------- |
| \~32 MB (HashesOnly, 1M nodes) | \~3,793   |
| \~48 MB (FullNodes, 1M nodes)  | \~5,689   |

Both fit within the `uint16` `FragTotal` limit of 65,535.

### Sequence Tracking and Retransmission

BRC-132 frames participate in the same NACK-based reliability mechanism as BRC-124 and BRC-131:

* The proxy stamps `HashKey` and `SeqNum` in-place before forwarding. Each `(senderIPv6, SubtreeID)` pair owns an independent monotonic sequence stream.
* If `SeqNum` is already non-zero on arrival, the frame is forwarded verbatim (pre-stamped path).
* Listeners detect gaps on the `(HashKey, 0xFFFB, SubtreeID)` flow and dispatch BRC-126 NACKs to retry endpoints.
* Retry endpoints join `FF0X::B:FFFB` and cache BRC-132 frames and BRC-130 fragments (`OrigFrameVer = 0x05`) by `HashKey ∥ SeqNum`. On NACK, the frame is retransmitted to `FF0X::B:FFFB`. The retry endpoint TTL for BRC-132 frames defaults to 120 s (compared to 60 s for transaction frames) to accommodate large reassembly windows.

### Merkle Verification

After reassembly, optional Merkle-root recomputation verifies the SubtreeID:

* Enabled by `-subtree-data-verify-merkle` / `SUBTREE_DATA_VERIFY_MERKLE=true` on the listener.
* The listener decodes the payload into nodes and computes SHA256d pairwise up the binary tree, then compares the root to SubtreeID.
* Computationally significant at 1M nodes (\~1M double-SHA256 operations); **disabled by default**.
* On mismatch: drop the reassembly slot; increment `bsl_reassembly_merkle_mismatch_total`.

### Error Handling

| Condition                      | Action                                         |
| ------------------------------ | ---------------------------------------------- |
| `raw[6] != 0x05`               | Not BRC-132; handled by other decoders         |
| Bad magic                      | Silent drop                                    |
| Unknown MsgType                | Drop; `ErrBadSubtreeMsg`                       |
| PayloadLen exceeds buffer      | Drop; `io.ErrUnexpectedEOF`                    |
| Datagram shorter than 92 bytes | Drop; `ErrTooShort`                            |
| `SeqNum == 0`                  | Frame not proxy-stamped; listener discards     |
| Merkle mismatch (optional)     | Drop; `bsl_reassembly_merkle_mismatch_total++` |

## Constants Reference

| Name                           | Value | Hex      | Description                                 |
| ------------------------------ | ----- | -------- | ------------------------------------------- |
| `FrameVerV5`                   | 5     | `0x05`   | BRC-132 subtree data frame version          |
| `SubtreeMsgHashesOnly`         | 1     | `0x01`   | MsgType: TxHashes only (32B per node)       |
| `SubtreeMsgFullNodes`          | 2     | `0x02`   | MsgType: TxHash + Fee + Size (48B per node) |
| `GroupSubtreeDataAnnounce`     | 65531 | `0xFFFB` | Subtree data multicast group index          |
| `HeaderSize`                   | 92    | `0x5C`   | BRC-132 header size (identical to BRC-124)  |
| `SubtreeDataPayloadHeaderSize` | 24    | `0x18`   | Fixed metadata prefix size                  |
| `SubtreeNodeHashSize`          | 32    | `0x20`   | Node size in HashesOnly payload             |
| `SubtreeNodeFullSize`          | 48    | `0x30`   | Node size in FullNodes payload              |

## References

* [BRC-119: SubTree Unified Merkle Path (STUMP) Format](/transactions/0119) — SubtreeID definition and Merkle subtree structure
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Base header layout reused by BRC-132
* [BRC-126: BSV Multicast NACK Retransmission Protocol](/transactions/0126) — NACK-based reliability used for subtree frame retransmission
* [BRC-127: Multicast Subtree Group Announcement Frame Format](/transactions/0127) — SubtreeID→GroupID metadata; distinct from BRC-132 data delivery
* [BRC-129: IPv6 Multicast Group Address Assignments](/transactions/0129) — Group index allocations; `0xFFFB` = GroupSubtreeDataAnnounce
* [BRC-130: Multicast Frame Fragmentation](/transactions/0130) — Fragmentation for large subtree payloads; `OrigFrameVer = 0x05`
* [BRC-131: Block Announcement Protocol](/transactions/0131) — `FrameVerV4` pattern followed by BRC-132


# Multicast Coinbase Transaction Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC defines the policy and wire mechanism for distributing coinbase transactions over the IPv6 multicast fabric. Coinbase transactions are carried as a dedicated message type within BRC-131 block control frames and delivered to all subscribers via the control-plane multicast group, independently of the shard groups used for ordinary transaction distribution.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

The multicast fabric shards ordinary transactions across per-shard multicast groups based on the top bits of each transaction's TxID. Coinbase transactions cannot be sharded this way because:

1. **Universal relevance** — the coinbase TxID is included in every block announcement and is required to verify the block's Merkle root.
2. **Fee aggregation** — coinbase outputs pay the block subsidy and miner fee aggregations that downstream systems need to validate template construction.
3. **Unpredictable placement** — no subscriber can predict which shard a coinbase TxID would hash to before the block is found.

BRC-133 addresses this by routing coinbase frames to the **GroupBlockBroadcast** group (`FF0E::B:FFFE`), a global control channel that all subscribers join unconditionally.

## Specification

### Control-Plane Multicast Group

Coinbase frames are delivered on the **GroupBlockBroadcast** group:

| Index    | Scope  | Compressed Address | Constant              |
| -------- | ------ | ------------------ | --------------------- |
| `0xFFFE` | global | `FF0E::B:FFFE`     | `GroupBlockBroadcast` |

The global scope (`FF0E`) ensures coinbase transactions cross site boundaries. The group index `0xFFFE` is in the reserved control-plane range and never overlaps with data-plane shard groups (maximum shard group index is `0x0FFF` for `shard_bits=12`).

The address is shown in ASM form (`FF0E::B:FFFE`), intra-domain only under RFC 8815; inter-domain delivery uses the SSM address `FF3E::B:FFFE` (see [BRC-129](/transactions/0129)). The frame format and NACK path are unchanged. Coinbase and block-announce frames are separate flows but share the same egress source (the emitting proxy, `senderIPv6`), so SSM receivers `(S,G)`-join `0xFFFE` once; block-broadcast sources are distributed per BRC-129.

### Wire Format

Coinbase transactions are carried as BRC-131 frames (FrameVer `0x04`) with `MsgType = 0x02` (`BlockMsgCoinbase`).

| Offset | Size | Field        | Value                                                 |
| ------ | ---- | ------------ | ----------------------------------------------------- |
| 6      | 1    | FrameVersion | `0x04` (BRC-131)                                      |
| 7      | 1    | MsgType      | `0x02` = `BlockMsgCoinbase`                           |
| 8      | 32   | ContentID    | CoinbaseTxID — SHA256d of the raw coinbase tx bytes   |
| 40     | 8    | HashKey      | XXH64(senderIPv6 ∥ 0xFFF8 ∥ zeros); stamped by proxy  |
| 48     | 8    | SeqNum       | Monotonic per-sender counter; stamped by proxy        |
| 56     | 32   | LayoutPad32  | All zeros                                             |
| 88     | 4    | PayloadLen   | Length of the raw coinbase transaction bytes          |
| 92     | \*   | Payload      | Raw serialised coinbase transaction (no P2P envelope) |

**Payload encoding:** The payload is a raw BSV serialised transaction — version (4 bytes LE), input vector, output vector, locktime (4 bytes LE) — identical to the encoding used in BRC-12/BRC-124 frames.

### Sequencing and Retransmission

Coinbase frames participate in the same NACK-based reliability mechanism as BRC-124 shard frames:

* The proxy stamps `HashKey = XXH64(senderIPv6 ∥ 0xFFF8 ∥ zeros)` and `SeqNum` (monotonic per sender on the coinbase flow) in-place before forwarding. The virtual index `0xFFF8` (`GroupCoinbaseFlow`) is never used as an actual multicast destination — it appears only in the HashKey computation to give coinbase frames an independent flow identity from BRC-131 block announces, which share the same egress multicast group. If the frame arrives pre-stamped (`SeqNum != 0`), it is forwarded verbatim.
* Listeners observe `(coinbaseFlowIdx=0xFFF8, zeroSubtreeID, HashKey, SeqNum, ContentID)` for gap detection and dispatch NACKs to retry endpoints on gap.
* Retry endpoints join `FF0E::B:FFFE` and cache coinbase frames by `HashKey ∥ SeqNum`. On NACK, the frame is retransmitted to `FF0E::B:FFFE`.

### Relationship to BRC-131 Block Announcements

A `BlockAnnounce` frame (BRC-131, MsgType `0x01`) is sent first and carries the `CoinbaseTxID` in its payload. The separate `BlockMsgCoinbase` frame then carries the full raw coinbase bytes. Subscribers that only need to verify the Merkle root may use the `CoinbaseTxID` from the announce frame without waiting for the coinbase frame itself.

`BlockAnnounce` and `BlockMsgCoinbase` form **separate flows** on the proxy. BlockAnnounce frames use a `HashKey` derived from `(sender, 0xFFFE, zeros)`; Coinbase frames use a `HashKey` derived from `(sender, 0xFFF8, zeros)`. Each flow therefore has its own monotonic `SeqNum` counter, its own `HashKey` value, and is gap-tracked independently by listeners — even though both egress to the same `FF0E::B:FFFE` multicast destination.

## Constants Reference

| Name                  | Value    | Description                                                                    |
| --------------------- | -------- | ------------------------------------------------------------------------------ |
| `FrameVerV4`          | `0x04`   | BRC-131 block control frame version                                            |
| `BlockMsgCoinbase`    | `0x02`   | MsgType: raw coinbase transaction                                              |
| `GroupBlockBroadcast` | `0xFFFE` | Block control multicast group index (egress)                                   |
| `GroupCoinbaseFlow`   | `0xFFF8` | Virtual HashKey ingredient; coinbase flow identity (never a multicast address) |

## References

* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — base header layout reused by BRC-131
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery
* [BRC-134: Chained Anchor Transaction Frames](/transactions/0134) — another control-group transaction type


# Multicast Anchor Transaction Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC defines a new frame version (`0x06`) for distributing chained anchor transactions over the IPv6 multicast fabric. An anchor transaction is the root (first) transaction in a chain of dependent transactions. Because all subsequent transactions in the chain reference the anchor as an input, every subscriber must receive it regardless of which shard its TxID would otherwise hash to.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

The multicast fabric shards ordinary transactions across per-shard multicast groups based on the top bits of each TxID. Chained anchor transactions cannot be sharded this way because:

1. **Dependency root** — all dependent (chained) transactions in the set reference the anchor's TxID as an input. A subscriber that misses the anchor cannot validate any dependent transaction in the chain.
2. **Unpredictable placement** — the anchor's TxID is not known in advance by subscribers; they cannot pre-join the correct shard group.
3. **Cross-shard span** — the chain may span many shards. The anchor itself might hash to shard 0 while dependents are spread across shards 1–15. Every subscriber needs the anchor irrespective of which shards they follow.
4. **Visibility over confirmation** — anchor transactions do not need to be confirmed before use; they need only to be visible and valid. Wide visibility is the primary requirement.

BRC-134 addresses this by routing anchor frames to the **GroupBlockBroadcast** group (`FF0E::B:FFFE`), the same global control channel used for block headers and coinbase transactions (BRC-131, BRC-133). All subscribers join this group unconditionally.

## Specification

### Control-Plane Multicast Group

Anchor frames are delivered on the **GroupBlockBroadcast** group:

| Index    | Scope  | Compressed Address | Constant              |
| -------- | ------ | ------------------ | --------------------- |
| `0xFFFE` | global | `FF0E::B:FFFE`     | `GroupBlockBroadcast` |

The global scope (`FF0E`) ensures anchor transactions cross site boundaries and reach all geographically distributed subscribers. The group index `0xFFFE` is in the reserved control-plane range and never overlaps with data-plane shard groups (maximum shard group index is `0x0FFF` for `shard_bits=12`).

The address is shown in ASM form (`FF0E::B:FFFE`), intra-domain only under RFC 8815; inter-domain and geographically distributed delivery uses the SSM address `FF3E::B:FFFE` (see [BRC-129](/transactions/0129)). The frame format and NACK path are unchanged. Anchors are user-submitted, so under SSM their source set (the emitting proxies, `senderIPv6`) is broader than the miner-only block and coinbase sources; receivers `(S,G)`-join `0xFFFE` per the source set distributed per BRC-129.

### Frame Header Format (92 bytes)

The BRC-134 header is **layout-identical** to BRC-124. Infrastructure components that inspect the Magic, HashKey, or SeqNum fields read correct values at the same offsets.

| Offset | Size | Align | Field         | Value / Notes                                             |
| ------ | ---- | ----- | ------------- | --------------------------------------------------------- |
| 0      | 4    | —     | Network Magic | `0xE3E1F3E8` (BSV mainnet P2P magic)                      |
| 4      | 2    | —     | Protocol Ver  | `0x02BF` (703, BSV large-block baseline)                  |
| 6      | 1    | —     | Frame Version | **`0x06`** — BRC-134 anchor transaction                   |
| 7      | 1    | —     | Reserved      | `0x00`                                                    |
| 8      | 32   | 8B    | TxID          | SHA256d of the anchor transaction (internal byte order)   |
| 40     | 8    | 8B    | HashKey       | XXH64(senderIPv6 ∥ 0xFFF9 ∥ zeros); stamped by proxy      |
| 48     | 8    | 8B    | SeqNum        | Monotonic per-sender counter; stamped by proxy; 0 = unset |
| 56     | 32   | 8B    | LayoutPad32   | All zeros (no subtree scope for anchor frames)            |
| 88     | 4    | —     | PayloadLen    | Size of anchor transaction payload in bytes (uint32 BE)   |
| 92     | \*   | —     | Payload       | Raw serialised anchor transaction (no P2P envelope)       |

**Key distinction from BRC-124:** the version byte at offset 6 is `0x06` rather than `0x02`. All other field offsets and sizes are identical. The `LayoutPad32` field at bytes 56–87 is always zeros — anchor frames have no subtree scope. The field is kept for layout uniformity: the proxy TCP reader, stamping path, listener, and retry endpoint all share the single `HeaderSize` constant.

**Payload encoding:** The payload is a raw BSV serialised transaction — version (4 bytes LE), input vector, output vector, locktime (4 bytes LE) — identical to the encoding used in BRC-12/BRC-124 frames.

### Sequencing and Retransmission

BRC-134 frames participate in the same NACK-based reliability mechanism as BRC-124 shard frames:

* The proxy stamps `HashKey = XXH64(senderIPv6 ∥ 0xFFF9 ∥ zeros)` and `SeqNum` (monotonic per sender) in-place before forwarding. The virtual index `0xFFF9` gives anchor frames an independent flow identity from BRC-131 block frames, which both travel on the same `GroupBlockBroadcast` multicast address. If the frame arrives pre-stamped (`SeqNum != 0`), it is forwarded verbatim.
* Listeners observe `(anchorGroupIdx=0xFFF9, zeroSubtreeID, HashKey, SeqNum, TxID)` for gap detection and dispatch NACKs to retry endpoints on gap.
* Retry endpoints join `FF0E::B:FFFE` and cache BRC-134 frames by `HashKey ∥ SeqNum`. On NACK, the frame is retransmitted to `FF0E::B:FFFE`.

## Constants Reference

| Name                  | Value    | Description                                                                  |
| --------------------- | -------- | ---------------------------------------------------------------------------- |
| `FrameVerV6`          | `0x06`   | BRC-134 chained anchor transaction frame version                             |
| `GroupBlockBroadcast` | `0xFFFE` | Control-plane multicast group index (shared with BRC-131/BRC-133)            |
| `GroupAnchorFlow`     | `0xFFF9` | Virtual HashKey ingredient; anchor flow identity (never a multicast address) |

## References

* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — base header layout reused by BRC-134
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — SSM/ASM addressing, scopes, and `(S,G)` source discovery
* [BRC-133: Coinbase Transaction Delivery](/transactions/0133) — another control-group transaction type using BRC-131


# Multicast Block Header Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies a lightweight wire format for distributing standalone 80-byte BSV block headers over IPv6 multicast and unicast transport. BRC-135 frames are produced by splitting the block header from a BRC-131 `BlockAnnounce` payload and wrapping it in a minimal 172-byte frame. The format reuses the 92-byte BRC-124 header layout, ensuring compatibility with existing infrastructure classifiers and firewall rules.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

BRC-131 `BlockAnnounce` frames distribute block headers alongside CoinbaseTxID and a variable-length list of subtree root hashes. Many downstream consumers need only the 80-byte block header:

1. **SPV clients** require only the header chain to verify proof-of-work and compute chain state; subtree hashes and CoinbaseTxID are irrelevant.
2. **Mining coordinators** need the previous-block hash and timestamp to build new templates; the remaining announce payload adds unnecessary latency on constrained links.
3. **Header-chain archival services** store and index raw 80-byte headers and discard everything else.

Delivering the full `BlockAnnounce` payload to these consumers wastes bandwidth and increases processing overhead. BRC-135 defines a fixed-size, minimal frame that any node receiving a `BlockAnnounce` can produce by extracting the 80-byte header and re-emitting it to downstream consumers. A node that produces BRC-135 frames is referred to as an **emitter** throughout this specification.

## Specification

### Terminology

| Term          | Definition                                                                                                |
| ------------- | --------------------------------------------------------------------------------------------------------- |
| **Emitter**   | Any node that receives a BRC-131 `BlockAnnounce` frame and produces a BRC-135 block header frame from it. |
| **Consumer**  | A downstream node that receives and processes BRC-135 frames.                                             |
| **BlockHash** | The SHA256d hash of the 80-byte block header, in internal byte order.                                     |

### Multicast Group

BRC-135 frames are emitted to the emitter's configured multicast egress group or unicast egress address. They MUST NOT be re-injected onto the primary fabric group `FF0E::B:FFFE` — doing so would create a feedback loop as other fabric subscribers would receive the split frame on the control channel they already subscribe to.

When multicast egress is enabled, the emitter sends BRC-135 frames to the `GroupBlockHeader` index (`0xFFFA`) on the egress scope, typically a different scope or group-id from the ingress fabric:

| Index  | Scope           | Compressed Address | Notes                            |
| ------ | --------------- | ------------------ | -------------------------------- |
| 0xFFFA | egress (varies) | FF05:::FFFA        | Emitter multicast egress channel |

The egress group-id is set independently of the fabric group-id. This ensures BRC-135 frames reach only downstream consumers, not peer fabric subscribers or retry endpoints on the ingress fabric.

The egress address is shown in ASM (`FF05`) form; under SSM, substitute the `FF3x` prefix per [BRC-129](/transactions/0129). The frame format is unchanged. The frame's source is the emitter (`emitterIPv6`), which stamps its own identity, so downstream consumers `(S,G)`-join per emitter. Under SSM the multi-emitter reliability pattern uses distinct per-emitter sources (or VRRP active-standby); anycast or shared-source emission is not supported (BRC-129).

### Frame Header Format (92 bytes)

The BRC-135 header is layout-identical to BRC-124. All infrastructure components that inspect Magic, HashKey, or SeqNum read correct values at the same offsets. All multi-byte integers are big-endian.

| Offset | Size | Alignment | Field         | Description                                               |
| ------ | ---- | --------- | ------------- | --------------------------------------------------------- |
| 0      | 4    | —         | Network Magic | 0xE3E1F3E8 (BSV mainnet P2P magic)                        |
| 4      | 2    | —         | Protocol Ver  | 0x02BF (703, BSV large-block baseline)                    |
| 6      | 1    | —         | Frame Version | 0x07 — BRC-135 block header                               |
| 7      | 1    | —         | Reserved      | 0x00                                                      |
| 8      | 32   | 8-byte    | BlockHash     | SHA256d of the 80-byte block header (internal byte order) |
| 40     | 8    | 8-byte    | HashKey       | XXH64(emitterIPv6 ∥ 0xFFFA ∥ zeros); stamped by emitter   |
| 48     | 8    | 8-byte    | SeqNum        | Monotonic per-emitter counter; stamped by emitter         |
| 56     | 32   | 8-byte    | LayoutPad32   | All zeros (no subtree scope for block headers)            |
| 88     | 4    | —         | PayloadLen    | 0x00000050 (80 = fixed block header size, uint32 BE)      |
| 92     | 80   | —         | Payload       | Raw 80-byte BSV block header                              |

**Total frame size:** 172 bytes (92 header + 80 payload). Always fits in a single UDP datagram — no fragmentation is required.

### Field Definitions

#### Network Magic (bytes 0–3)

The value `0xE3E1F3E8` (BSV mainnet P2P network magic). This enables standard BSV firewall rules and network monitoring tools to correctly classify BRC-135 frames. Frames with incorrect magic MUST be rejected.

#### Protocol Version (bytes 4–5)

The value `0x02BF` (703 in decimal). Informational; receivers do not validate it.

#### Frame Version (byte 6)

The value `0x07` identifies a BRC-135 block header frame. Any other version is handled by other decoders.

#### Reserved (byte 7)

Must be `0x00`. Unlike BRC-131 which uses this byte for MsgType, BRC-135 has no message subtypes — the frame always carries exactly one block header.

#### BlockHash (bytes 8–39)

The SHA256d of the 80-byte block header in internal byte order. This value is identical to the `ContentID` field in a BRC-131 `BlockAnnounce` frame for the same block.

#### HashKey (bytes 40–47)

A stable per-emitter flow identifier computed as:

```
HashKey = XXH64(emitterIPv6 [16 bytes] ∥ 0x0000FFFA [4 bytes BE] ∥ zeros [32 bytes])
```

The `0xFFFA` group index is `GroupBlockHeader`, the emitter multicast egress channel (see the group table above). The 32 zero bytes correspond to the absent SubtreeID. The HashKey is constant for the lifetime of the emitter process.

A value of `0` indicates the field is unset.

#### SeqNum (bytes 48–55)

A 64-bit unsigned integer (big-endian) containing a monotonic counter starting at 1, incremented for each BRC-135 frame emitted. Each emitter maintains a single counter for all block header frames.

A value of `0` indicates the field is unset.

#### LayoutPad32 (bytes 56–87)

All zeros. This field is retained to keep the header at 92 bytes, maintaining layout uniformity with BRC-124 and other frame versions. Block header frames have no subtree scope.

#### PayloadLen (bytes 88–91)

Always `0x00000050` (80 in decimal). A frame with any other PayloadLen MUST be rejected.

#### Payload (bytes 92–171)

The raw 80-byte BSV block header, byte-for-byte identical to the first 80 bytes of a BRC-131 `BlockAnnounce` payload:

| Offset | Size | Encoding  | Field         | Description                             |
| ------ | ---- | --------- | ------------- | --------------------------------------- |
| 0      | 4    | int32 LE  | Version       | Block version                           |
| 4      | 32   | bytes     | PrevBlockHash | Hash of previous block (internal order) |
| 36     | 32   | bytes     | MerkleRoot    | Merkle root of the block's tx tree      |
| 68     | 4    | uint32 LE | Timestamp     | Block timestamp (Unix epoch seconds)    |
| 72     | 4    | uint32 LE | Bits          | Compact difficulty target               |
| 76     | 4    | uint32 LE | Nonce         | Proof-of-work nonce                     |

No additional framing or envelope is applied. The block header is copied verbatim from the BRC-131 `BlockAnnounce` payload bytes `[0:80]`.

### Frame Production

Any node that receives a BRC-131 `BlockAnnounce` frame (`FrameVer=0x04`, `MsgType=0x01`) MAY produce a BRC-135 frame:

1. Extract the 80-byte block header from `BlockAnnounce` payload bytes `[0:80]`.
2. Copy the `BlockHash` from the BRC-131 frame's `ContentID` field (bytes 8–39).
3. Stamp `HashKey` using the emitter's own IPv6 address per the formula above.
4. Increment and stamp `SeqNum`.
5. Emit the 172-byte frame to the configured egress.

The emitter stamps `HashKey` and `SeqNum` using its own identity — not the proxy's. This reflects that the emitter is the originator of the split frame. Each emitter produces an independent sequence stream.

### Sequencing

BRC-135 frames carry their own independent `HashKey`/`SeqNum` flow:

* Each emitter maintains a single SeqNum counter for all block headers.
* Downstream consumers that track gaps identify the emitter via HashKey.
* If a consumer receives BRC-135 frames from multiple emitters (e.g., via anycast or failover), each emitter produces an independent sequence stream.

### Retransmission

BRC-135 frames are **not retransmitted** via the standard BRC-126 NACK path on the primary fabric. They are a derived product — if a downstream consumer misses a BRC-135 frame, it can recover from a different source:

1. **Redundant emitters** — multiple emitters produce the same block header; downstream consumers subscribe to more than one for reliability.
2. **Re-request from upstream** — the consumer re-requests the full BRC-131 `BlockAnnounce` via BRC-126 NACK to a retry endpoint, then extracts the header locally.
3. **Application-level retry** — the downstream consumer requests the block header by hash from any BSV peer using the standard `getheaders` protocol.

If a deployment requires NACK-based retransmission for BRC-135 frames on the egress network, a secondary retry endpoint can be deployed on the egress segment that joins the egress multicast group and caches BRC-135 frames by `HashKey ∥ SeqNum`. This is an optional deployment topology, not a protocol-level requirement.

### Error Handling

| Condition                       | Action                                 |
| ------------------------------- | -------------------------------------- |
| raw\[6] != 0x07                 | Not BRC-135; handled by other decoders |
| Bad magic                       | Silent drop                            |
| PayloadLen != 80                | Drop; ErrBadBlockHeaderLen             |
| Datagram shorter than 172 bytes | Drop; ErrTooShort                      |
| BlockHash mismatch (optional)   | Drop; integrity verification failed    |

### Consumer Processing

A consumer receiving BRC-135 frames:

1. **Validate** — Check `raw[0:4] == 0xE3E1F3E8`, `raw[6] == 0x07`, `PayloadLen == 80`.
2. **Extract** — Read the 80-byte block header from `raw[92:172]`.
3. **Verify** — Optionally compute `SHA256d(raw[92:172])` and compare against `BlockHash` (bytes 8–39) to confirm integrity.
4. **Gap track** — If consuming from a single emitter, monitor `SeqNum` continuity on the `HashKey` flow to detect missed headers.

## Examples

### BRC-135 Frame Hex Dump

A BRC-135 frame carrying the block header for block hash `00000000000000000...`:

```
// Header (92 bytes)
E3E1F3E8                                                          // Network Magic
02BF                                                              // Protocol Version
07                                                                // Frame Version (BRC-135)
00                                                                // Reserved
00000000000000000001a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3  // BlockHash
00112233AABBCCDD                                                  // HashKey (XXH64 of emitter flow)
0000000000000001                                                  // SeqNum (1)
0000000000000000000000000000000000000000000000000000000000000000  // LayoutPad32 (zeros)
00000050                                                          // PayloadLen (80)

// Payload (80 bytes — raw BSV block header)
20000000                                                          // Version (LE)
00000000000000000...                                              // PrevBlockHash (32 bytes)
aabbccdd00112233...                                               // MerkleRoot (32 bytes)
665B1A00                                                          // Timestamp (LE)
1D00FFFF                                                          // Bits (LE)
12345678                                                          // Nonce (LE)
```

## References

* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Base header layout reused by BRC-135
* [BRC-131: Block Announcement Frame Format](/transactions/0131) — Source of the 80-byte block header extracted by the emitter
* [BRC-133: Coinbase Transaction Frame Format](/transactions/0133) — Companion control-plane frame type
* [BRC-134: Anchor Transaction Frame Format](/transactions/0134) — Companion control-plane frame type
* [BRC-126: Retransmission Protocol](/transactions/0126) — NACK/ACK/MISS used for upstream block frame retransmission
* [BRC-129: Multicast Group Address Assignments](/transactions/0129) — Control-plane group index allocations

## Constants Reference

| Name                 | Value | Hex    | Description                                       |
| -------------------- | ----- | ------ | ------------------------------------------------- |
| FrameVerV7           | 7     | 0x07   | BRC-135 block header frame version                |
| BlockHeaderPayload   | 80    | 0x50   | Fixed payload size (standard BSV block header)    |
| BlockHeaderFrameSize | 172   | 0xAC   | Total frame size (92 + 80)                        |
| GroupBlockHeader     | 65530 | 0xFFFA | Block header egress channel (BRC-135 mc-egress)   |
| GroupBlockBroadcast  | 65534 | 0xFFFE | Control group index (shared with BRC-131/133/134) |
| HeaderSize           | 92    | 0x5C   | BRC-135 header size (identical to BRC-124)        |


# Multicast Shard Manifest Announcement Protocol

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the shard manifest announcement protocol for the BSV multicast transaction distribution pipeline. It defines a single UDP datagram format — ShardManifest (`MsgType 0x40`) — by which any participant periodically declares its `shard_bits` configuration and the set of shard groups it has joined. Manifests are emitted directly to the control-plane beacon group (`0xFFFD`, see [BRC-129](/transactions/0129)), the same group used by [BRC-126](/transactions/0126) ADVERT beacons, and are distinguished by their `MsgType` byte. The protocol provides operator visibility into network-wide sharding configuration, enables cross-peer divergence detection, and defines a normative consumer profile for automated, rate-limited shard-bit coordination. It is observation-only by default: there is no retransmission and no acknowledgment.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

Every component of the multicast pipeline — ingress proxy, listener, retry endpoint, and transaction producer — must agree on a single `shard_bits` value to interoperate, because the shard group index for a transaction is derived as `groupIndex = binary.BigEndian.Uint32(txid[0:4]) >> (32 − shardBits)` ([BRC-129](/transactions/0129)). Today this value is configured manually on each component and there is no on-network signal to verify agreement. A silent mismatch causes senders and receivers to use disjoint group spaces, dropping traffic without an obvious cause.

BRC-139 introduces a small, dedicated announcement that lets every participant:

1. Periodically declare its current `shard_bits` and the set of shard group indices it claims to have joined.
2. Let observers detect inconsistent configuration across peers.
3. Carry identity, timestamp, TTL, and a `GenerationID` so a normative consumer profile can implement automated, rate-limited `shard_bits` shifts safely.

The protocol reuses the existing beacon group rather than allocating a new control-plane index, and requires no proxy involvement: manifests do not transit the BRC-124 ingress path.

## Specification

### Common Message Preamble

ShardManifest datagrams share the [BRC-126](/transactions/0126) control-message preamble: the `MsgType` byte at offset 6 occupies the same position as `FrameVersion` in BRC-124 data frames. Value `0x40` identifies a ShardManifest and is distinct from both the data-frame version codes (`0x01`–`0x07`) and the BRC-126 control messages (`0x10`–`0x2F`). All multi-byte integers are big-endian.

### ShardManifest Wire Format (`MsgType 0x40`) — 64-byte header + variable payload

| Offset | Size | Field            | Description                                                                    |
| ------ | ---- | ---------------- | ------------------------------------------------------------------------------ |
| 0      | 4    | Magic            | `0xE3E1F3E8` (BSV mainnet P2P magic)                                           |
| 4      | 2    | ProtoVer         | `0x02BF`                                                                       |
| 6      | 1    | MsgType          | `0x40` (ShardManifest)                                                         |
| 7      | 1    | Flags            | See [Flags](#flags)                                                            |
| 8      | 16   | SrcIPv6          | Announcer's primary IPv6 (informational; datagram source is authoritative)     |
| 24     | 4    | InstanceID       | CRC32c of hostname; stable across restarts                                     |
| 28     | 4    | Epoch            | Unix seconds when the announcement was generated                               |
| 32     | 2    | TTL              | Validity in seconds; `0` = use consumer default                                |
| 34     | 2    | AnnounceInterval | Seconds between sends; consumers compute TTL hint = `3 × this`                 |
| 36     | 1    | ShardBits        | `0`–`12` (MUST be ≤ 12 per [BRC-129](/transactions/0129))                      |
| 37     | 1    | RoleHint         | Informational role (see [RoleHint](#rolehint))                                 |
| 38     | 2    | GroupCount       | `N`: number of `groupIndex` entries in list form (`0` if bitmap or no claim)   |
| 40     | 2    | BitmapBytes      | `M`: length of trailing bitmap; `0` ⇒ list form                                |
| 42     | 2    | SourceCount      | `K`: number of trailing 16-byte source-IPv6 entries; `0` unless `SourcesValid` |
| 44     | 4    | ManifestCRC      | CRC32c over the whole datagram with these 4 bytes treated as zero              |
| 48     | 16   | GenerationID     | Operator-supplied 128-bit value; bumped whenever `ShardBits` changes           |
| 64     | \*   | Payload          | Groups, then Sources, then Successor block (see below)                         |

The payload sections appear in this order after the 64-byte header:

1. **Groups** — when `GroupsValid=1`: either `GroupCount × 2` bytes of big-endian `groupIndex` (sorted ascending, no duplicates), or exactly `BitmapBytes` bytes of bitmap (LSB-first, bit `i` = group index `i`). Empty when `GroupsValid=0`.
2. **Sources** — when `SourcesValid=1`: `SourceCount × 16` bytes of source IPv6 addresses in network byte order.
3. **Successor** — when `SuccessorValid=1`: a 24-byte Successor block (see [Successor block](#successor-block-when-successorvalid1)).

Total datagram size = `64 + max(N×2, M) + K×16 + (24 if SuccessorValid else 0)`. Implementations SHOULD keep the total size ≤ 1232 B to avoid IPv6 fragmentation on typical paths. With `ShardBits=12` (4,096 groups) the bitmap form is exactly 512 B; the list form is 2 B per joined group. Operators with large source lists SHOULD spread them across multiple announcers (each advertising its own source as the lone entry) so individual datagrams stay within the recommended MTU.

### Encoding-form rules

| `GroupsValid` | `BitmapBytes` | `GroupCount` | Groups payload form                                                       |
| ------------- | ------------- | ------------ | ------------------------------------------------------------------------- |
| `0`           | `0`           | `0`          | identity-only (no group claim)                                            |
| `1`           | `> 0`         | `0`          | bitmap, exactly `BitmapBytes` bytes; bit positions `0..(BitmapBytes×8)-1` |
| `1`           | `0`           | `> 0`        | list, exactly `GroupCount × 2` bytes, sorted ascending, no duplicates     |

A datagram with `GroupsValid=1` and both `BitmapBytes=0` and `GroupCount=0` is malformed and MUST be rejected. A datagram with `GroupsValid=1` and both `BitmapBytes>0` and `GroupCount>0` is also malformed. For bitmap form, bits at positions ≥ `2^ShardBits` MUST be zero and MUST be ignored by consumers.

### Sources payload (when `SourcesValid=1`)

Each 16-byte entry is a publisher source IPv6 in network byte order, contributed by this announcer. Consumers MUST reject the datagram when `SourcesValid=1 && SourceCount=0` or when `SourcesValid=0 && SourceCount>0`, treat entries as set-valued (order not significant), and deduplicate across the union of all currently-valid manifests. When `SourceModeSSM=1`, consumers feed the union into the Source-Specific Multicast `(S,G)` join calls for data-plane groups derived from the announced parameters. Each sender SHOULD announce only its own data-plane source (typically a single entry) rather than the operator-curated full fleet list, so per-datagram size stays small and the source set tracks publisher lifecycle.

### Successor block (when `SuccessorValid=1`)

The Successor block signals an in-flight generation transition: the announcer commits to a future `ShardBits` (and optionally `SourceModeSSM`) value that becomes the sole active generation at `TransitionEpoch`. It enables live re-sharding consumers to enter a bridging window before cutover. When present, a 24-byte block is appended immediately after the Sources payload:

| Offset (in block) | Size | Field                 | Description                                                     |
| ----------------- | ---- | --------------------- | --------------------------------------------------------------- |
| 0                 | 16   | SuccessorGenerationID | The incoming generation's 128-bit ID                            |
| 16                | 1    | SuccessorShardBits    | `1`–`12`; MUST satisfy `\|Successor − ShardBits\| ≤ 1`          |
| 17                | 1    | SuccessorFlags        | Bit 0 = `SuccessorSourceModeSSM`; bits 1–7 reserved (`0`)       |
| 18                | 2    | Reserved              | MUST be `0`                                                     |
| 20                | 4    | TransitionEpoch       | Unix seconds at which the successor becomes the sole generation |

Consumer rules (normative when auto-configuration is enabled):

* Reject the datagram when `|SuccessorShardBits − ShardBits| > 1`.
* Reject the datagram when `SuccessorValid=1 && Authoritative=0` (live re-sharding signals require operator authority).
* Apply the existing adoption gates (quorum, hysteresis) to the Successor block as a unit; the tuple `(SuccessorGenerationID, SuccessorShardBits, SuccessorFlags, TransitionEpoch)` is the candidate value.
* Consumers that implement live re-sharding MAY enter a bridging window between the moment the Successor block first satisfies quorum and `local_clock ≥ TransitionEpoch`. Consumers that do not implement live re-sharding MUST treat Successor-block adoption as a divergence event and wait for the pilot to roll `GenerationID` (promoting the successor to the active generation) before reacting on the new value.

Pilots MUST choose `TransitionEpoch ≥ now + 2 × AnnounceInterval` (RECOMMENDED `≥ now + 4 × AnnounceInterval`) and MUST reject configurations below that floor. Clock skew between pilot and consumers MUST stay below `AnnounceInterval / 2`; operators SHOULD run NTP.

### Flags

| Bit | Name           | Meaning                                                                                                |
| --- | -------------- | ------------------------------------------------------------------------------------------------------ |
| 0   | GroupsValid    | The trailing payload carries a valid joined-groups encoding.                                           |
| 1   | Authoritative  | Operator-curated authoritative announcer; counts toward adoption quorum.                               |
| 2   | Shutdown       | Final announcement before graceful shutdown; consumers MAY evict immediately.                          |
| 3   | SourceModeSSM  | Data plane uses Source-Specific Multicast (`FF3x::/32`, RFC 4607); consumers MUST use the SSM prefix.  |
| 4   | SourcesValid   | The trailing payload includes `SourceCount × 16` bytes of publisher source IPv6 addresses. `0` ⇒ none. |
| 5   | PilotOnly      | Groups describe desired fleet state, not the announcer's own joins; implies `Authoritative=1`.         |
| 6   | SuccessorValid | The trailing payload includes a 24-byte Successor block; requires `Authoritative=1`.                   |

Bit 7 is reserved and MUST be `0`. Consumers MUST reject `PilotOnly=1 && Authoritative=0` and `SuccessorValid=1 && Authoritative=0` as malformed.

### RoleHint

`RoleHint` is informational; consumers SHOULD NOT make filtering decisions on it.

| Value | Role           |
| ----- | -------------- |
| `0`   | generic        |
| `1`   | proxy          |
| `2`   | listener       |
| `3`   | retry-endpoint |
| `4`   | producer       |
| `5`   | manifest-only  |

Values ≥ `6` are reserved.

### ManifestCRC

`ManifestCRC` is a CRC32c (Castagnoli polynomial) computed over the entire datagram with the four CRC bytes themselves treated as zero. Consumers MUST verify the CRC before acting on the manifest.

### Multicast Group and Scope

Manifests are sent **directly** to the beacon group defined in [BRC-129](/transactions/0129) (index `0xFFFD`), the same group used by BRC-126 ADVERT:

| Index    | Scope  | Compressed Address |
| -------- | ------ | ------------------ |
| `0xFFFD` | `FF05` | `FF05::B:FFFD`     |
| `0xFFFD` | `FF08` | `FF08::B:FFFD`     |
| `0xFFFD` | `FF0E` | `FF0E::B:FFFD`     |

The announcer chooses one or more scopes; when multiple are configured the same datagram is sent to each. The proxy is **not** involved. Because BRC-126 ADVERT (`MsgType 0x20`) shares this group, listeners MUST dispatch on the `MsgType` byte (offset 6) before parsing: `0x40` ⇒ ShardManifest; `0x20` ⇒ BRC-126 ADVERT.

### Cadence and Freshness

| Parameter        | Default | Notes                                        |
| ---------------- | ------- | -------------------------------------------- |
| AnnounceInterval | `300 s` | Every 5 minutes                              |
| Default TTL      | `900 s` | `3 × AnnounceInterval` (used when `TTL = 0`) |
| Send jitter      | ±10 %   | RECOMMENDED, to avoid global synchronisation |

Consumers MUST treat entries older than `Epoch + TTL` (or `Epoch + 3 × AnnounceInterval` when `TTL=0`) as expired. There is no retransmission and no NACK semantics; loss is tolerated by re-announcement, and an empty registry is valid.

### Identity and State

Consumers SHOULD key registry entries on `(SrcIPv6, InstanceID)`. The authoritative `SrcIPv6` is taken from the IPv6 datagram header; the in-frame `SrcIPv6` field is informational only. `InstanceID` is the CRC32c of the announcer's hostname, matching BRC-126 ADVERT semantics, and is stable across restarts. `GenerationID` is an operator-supplied 128-bit value (typically a UUID) that operators MUST bump whenever `ShardBits` changes; it is opaque to the protocol and consumers compare it for change detection.

### Consumer Behaviour — Observation (informative)

A consumer MAY join the beacon group(s) of interest and:

1. Dispatch incoming datagrams on the `MsgType` byte. `0x40` ⇒ ShardManifest.
2. Verify `ManifestCRC`; reject on mismatch.
3. Upsert into a registry keyed on `(SrcIPv6, InstanceID)`.
4. Evict on `Epoch + TTL` (or `Epoch + 3 × AnnounceInterval` when `TTL=0`).
5. Surface metrics: per-peer `ShardBits`, joined-group count, last-seen, and the count of distinct `ShardBits` values currently observed (divergence).

### Consumer Behaviour — Auto-configuration (normative when opted in)

A consumer that opts in to automatic configuration MUST implement the observation requirements above and additionally MUST satisfy the following. Components that do not opt in are unaffected.

1. **Authoritative-only adoption.** Manifests with `Authoritative=0` MUST NOT contribute to any adopted value (they MAY still be indexed for visibility and contribute to the source-set union below).
2. **Quorum.** A candidate value is eligible for adoption only when reported by at least `pilot-quorum` distinct authoritative announcers (keyed on `(SrcIPv6, InstanceID)`) within their TTL window. `pilot-quorum` MUST be configurable; default `2`.
3. **Hysteresis.** A candidate that satisfies quorum MUST hold quorum continuously for `≥ 2 × AnnounceInterval` before adoption. A change in adopted value resets the timer.
4. **`ShardBits` shift bound.** A consumer MUST NOT adopt a `ShardBits` value differing from the currently adopted value by more than ±1 within any rolling `AnnounceInterval` window.
5. **Manual pin precedence.** An operator-pinned value is the local authority and MUST NOT be overridden by adoption; the consumer MUST still evaluate quorum and emit divergence telemetry when the adopted candidate differs from the pin.

Fields subject to adoption: `ShardBits`, `SourceModeSSM`, and the deduplicated union of `SourcesValid` payloads (the source set is **not** gated by quorum — it is the union of every currently-valid manifest's sources, irrespective of `Authoritative`). `MCGroupID` is not carried in the payload; consumers MUST derive it from the destination address of the beacon socket on which the manifest was received. Consumers SHOULD rate-limit source-set additions and removals before feeding them into kernel join calls to avoid thrashing the multicast forwarding information base, and MUST NOT label any metric with raw source IPv6 addresses.

### Safety Guidance

1. Bump `GenerationID` whenever `ShardBits` changes.
2. Keep authoritative announcers to a small operator-curated set (RECOMMENDED: three instances across failure domains).
3. Deploy authoritative announcers with `PilotOnly=1` so consumers can distinguish operator intent from an announcer's own joins.
4. Treat non-authoritative manifests as observational (and, when `SourcesValid=1`, as per-publisher source contributions).
5. Warn — do not auto-shift — on observed `ShardBits` divergence; the adoption gates already prevent unsafe shifts, but operator visibility is where misconfiguration is caught.

## Interactions With Other BRCs

* [**BRC-126**](/transactions/0126) (Retransmission / ADVERT) — shares the beacon group `0xFFFD` and listen port. Distinguished by the `MsgType` byte (`0x20` ADVERT vs `0x40` ShardManifest). BRC-139 is neither retransmitted nor a retransmitter.
* [**BRC-127**](/transactions/0127) (Subtree group announcements) — orthogonal: BRC-127 announces SubtreeID→GroupID bindings on `0xFFFC` via the proxy; BRC-139 announces participant configuration directly on `0xFFFD`.
* [**BRC-129**](/transactions/0129) (Multicast addressing) — no new index allocated; manifests reuse the existing beacon group, and `ShardBits ≤ 12` per BRC-129.

## References

* [BRC-82: Defining a Scalable IPv6 Multicast Protocol for Blockchain Transaction Broadcast and Update Delivery](/peer-to-peer/0082) — Overall multicast protocol architecture
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — Data-plane frame format and `HashKey`/`SeqNum` derivation
* [BRC-126: Multicast Transaction NACK Retransmission Protocol](/transactions/0126) — ADVERT beacon sharing the same beacon group
* [BRC-129: IPv6 Multicast Group Address Assignments](/transactions/0129) — Beacon group and `shard_bits` bound

## Constants Reference

| Name                      | Value      | Hex          | Description                                      |
| ------------------------- | ---------- | ------------ | ------------------------------------------------ |
| `MagicBSV`                | 3823236072 | `0xE3E1F3E8` | BSV mainnet P2P magic                            |
| `ProtoVer`                | 703        | `0x02BF`     | Protocol version                                 |
| `MsgTypeShardManifest`    | 64         | `0x40`       | ShardManifest datagram type                      |
| `ShardManifestHeaderSize` | 64         | `0x40`       | Fixed header size in bytes                       |
| `GroupBeacon`             | 65533      | `0xFFFD`     | Control-plane beacon group index                 |
| `FlagGroupsValid`         | 1          | `0x01`       | Flags bit 0: groups payload present              |
| `FlagAuthoritative`       | 2          | `0x02`       | Flags bit 1: operator-curated announcer          |
| `FlagShutdown`            | 4          | `0x04`       | Flags bit 2: final pre-shutdown announcement     |
| `FlagSourceModeSSM`       | 8          | `0x08`       | Flags bit 3: data plane uses SSM addressing      |
| `FlagSourcesValid`        | 16         | `0x10`       | Flags bit 4: sources payload present             |
| `FlagPilotOnly`           | 32         | `0x20`       | Flags bit 5: groups describe desired fleet state |
| `FlagSuccessorValid`      | 64         | `0x40`       | Flags bit 6: Successor block present             |
| `DefaultAnnounceInterval` | 300        | —            | Default re-announce period (s)                   |
| `DefaultTTLMultiplier`    | 3          | —            | TTL = 3 × AnnounceInterval when `TTL=0`          |


# Multicast Transaction Bundle Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies a coalescing extension to the BRC-124 and BRC-128 Multicast Transaction Frame Formats. A *bundle* packs many small BSV transactions — standard (BRC-12 raw) or Extended Format (BRC-30 EF) — that share one shard group and subtree into a single datagram, so a payload that would otherwise cross a multicast fabric as N small packets crosses it as one. It is the inverse of BRC-130 fragmentation, which splits one oversized transaction across many datagrams. Bundling reduces packets-per-second — the dominant per-packet forwarding cost of multicast fan-out — without changing transaction semantics.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

Basic BSV payments are small — a typical pay-to-public-key-hash (P2PKH) transaction is only \~200 bytes — even though Bitcoin script imposes no such limit and many transactions are far larger. A large share of fabric traffic is therefore small-packet, and multicast fan-out cost is dominated by packets-per-second, not bandwidth: each replicated packet carries a per-packet forwarding cost largely independent of its size. A node that already forwards individual BRC-124/128 frames can pack many same-destination transactions into one datagram, cutting the packet rate it imposes on every downstream hop while carrying the same transactions. Bundling is an operator-side optimization: senders need not participate, and a node may bundle, or not, without coordination.

## Specification

### Bundle Header Format (66 bytes)

A bundle is identified by Frame Version `0x08`. Unlike BRC-130, a bundle header is **not** layout-compatible with BRC-124 — a bundle has no single Transaction ID. All multi-byte integers are big-endian.

| Offset | Size | Field          | Description                                                                                   |
| ------ | ---- | -------------- | --------------------------------------------------------------------------------------------- |
| 0      | 4    | Network Magic  | 0xE3E1F3E8 (BSV mainnet P2P magic)                                                            |
| 4      | 2    | Protocol Ver   | 0x02BF (703)                                                                                  |
| 6      | 1    | Frame Version  | 0x08 (BRC-142 bundle)                                                                         |
| 7      | 1    | Flags          | bit0 = `TxIDsPresent` (per-member Transaction ID present, all-or-none); bits 1–7 reserved (0) |
| 8      | 32   | Subtree ID     | The single 32-byte subtree shared by all members; zeros = unset                               |
| 40     | 8    | HashKey        | XXH64(senderIPv6 ∥ groupIdx ∥ subtreeID); the bundle flow identity                            |
| 48     | 8    | SeqNum         | Per-flow monotonic counter, starts at 1; 0 = unset                                            |
| 56     | 2    | GroupIdx       | Shard group index this bundle was built for (uint16 BE)                                       |
| 58     | 1    | ShardBits      | Shard-bit width `GroupIdx` was computed at (1–12); pins the generation                        |
| 59     | 1    | Reserved       | 0x00                                                                                          |
| 60     | 2    | TxCount        | Number of members (uint16 BE)                                                                 |
| 62     | 4    | PayloadLen     | Total byte length of the member section that follows (uint32 BE)                              |
| 66     | \*   | Member section | `TxCount` members (below)                                                                     |

### Member Format

The header is followed by `TxCount` length-prefixed members:

| Offset (relative) | Size  | Field | Description                                                       |
| ----------------- | ----- | ----- | ----------------------------------------------------------------- |
| 0                 | 2     | TxLen | Member transaction byte length (uint16 BE)                        |
| 2                 | 32    | TxID  | Present **only** when `Flags.TxIDsPresent`; raw 256-bit TxID      |
| 2 or 34           | TxLen | Tx    | Serialised BSV transaction — BRC-12 raw or BRC-30 Extended Format |

* `TxLen` is a fixed uint16: a member must fit one datagram, so 16 bits suffices; a transaction that would exceed the path MTU is ineligible and uses BRC-130.
* The per-member Transaction ID is **optional** (the `TxIDsPresent` flag) because it is recomputable from the transaction, so carrying it is redundant wire data — and at 32 bytes per member it is a real density cost on small payments (\~14% fewer members for a \~200-byte P2PKH tx), working against the packing this format exists for. Omit it by default and let receivers recompute `SHA256d(Tx)`. Carry it (set the flag) when a receiver needs the id without recompute — line-rate dedup or accounting — or for Extended Format members, whose canonical id is the hash of the de-extended base transaction (**not** of the EF bytes) and so cannot be cheaply recomputed. It is **not** a retransmission aid.
* A member is a transaction in **BRC-12** (raw) or **BRC-30** (Extended Format) serialization; an EF member self-identifies by its 6-byte marker (payload bytes 4–9), so the two may be mixed in one bundle with no per-member type flag. The bundle carries only the transaction bytes — the **egress format is the downstream consumer's contract, not the bundle's**. On decoalesce the edge re-emits each member in the consumer's format: as a multicast frame — **BRC-124** (raw) or **BRC-128** (Extended Format), both Frame Version 0x02 — or in the transaction's own **BRC-12** / **BRC-30** format.
* Parsers read members until `PayloadLen` bytes are consumed; `TxCount` is a cross-check.

### Group, Subtree, and Flow Identity

Every member of a bundle satisfies `GroupIndex(member TxID) == GroupIdx` at `ShardBits` (BRC-129) and carries the bundle's Subtree ID. A bundle is therefore a single `(sender, group, subtree)` flow with one HashKey and one monotonic SeqNum, exactly as a BRC-124 frame. This lets a bundle reuse the BRC-126 retransmission machinery unchanged: gap detection and NACK operate on the bundle SeqNum stream.

A bundle is transport-agnostic: the on-wire bytes are identical under Any-Source (ASM) and Source-Specific (SSM) multicast. Addressing, scopes, and the SSM `(S,G)` join and source-discovery model are those of the underlying shard group in [BRC-129](/transactions/0129); the bundle's source is the coalescing node (`senderIPv6`, the first HashKey ingredient), so a receiver joins the same `(S,G)` it would for the group's BRC-124 frames.

`GroupIdx` and `ShardBits` are carried explicitly, rather than derived from member Transaction IDs, so any node can classify and re-bucket a bundle deterministically during a BRC-139 shard-bit transition — when the active `ShardBits`, and therefore the group a Transaction ID maps to, is changing.

### Coalescing and Decoalescing

A coalescing node buckets input frames by `(group, subtree)` and packs each bucket into bundles, starting a new bundle when the next member would exceed the path MTU or `TxCount = 65535`. A bundle never exceeds the path MTU and never fragments: BRC-142 and BRC-130 are mutually exclusive per datagram. Coalescing is bounded so a partial bundle is not held indefinitely — the bound (a maximum delay or a batch window) is implementation-defined — and is opt-in, at an implementation-defined granularity (per node or per flow; latency-sensitive relay opts out).

Decoalescing splits a bundle back into individual transactions in the downstream consumer's format — each re-emitted as a multicast frame (BRC-124 for a raw transaction or BRC-128 for an Extended Format one, both Frame Version 0x02) or in the transaction's own BRC-12 / BRC-30 format. The default is **edge-decoalesce**: the fabric carries bundles and the edge node splits them before per-consumer delivery, leaving the consumer contract unchanged. Each emitted frame inherits the bundle's Subtree ID, carries its own (carried or recomputed) Transaction ID, and is assigned a per-transaction SeqNum on the egress flow. A consumer MAY instead opt to receive bundles directly.

### Retransmission

The retransmission unit is the whole bundle. A lost bundle is a datagram the receiver never saw, so it can only request the bundle by its SeqNum, not a member within it. A retry endpoint caches and retransmits a bundle keyed on `(HashKey, SeqNum)` exactly as a BRC-124 frame.

### Re-bucketing

A relay forwarding a bundle into a domain or generation running a different `ShardBits` MUST re-bucket it: decoalesce and re-coalesce at the target `ShardBits` (learned from the BRC-139 manifest), routing each member to its correct group, preserving the Subtree ID, and re-stamping the flow HashKey and SeqNum. A relay MUST NOT deliver a bundle to a subscriber whose interest is finer than the bundle's `ShardBits` without re-bucketing, since that subscriber would receive — and, on loss, re-request — transactions outside its interest.

### Error Handling

| Condition                             | Action                                                                              |
| ------------------------------------- | ----------------------------------------------------------------------------------- |
| Frame Version ≠ 0x08                  | Not a bundle; decode per its version                                                |
| Bad magic                             | Silent drop                                                                         |
| Datagram shorter than 66-byte header  | Silent drop                                                                         |
| PayloadLen exceeds datagram remainder | Silent drop (truncated)                                                             |
| Member runs past PayloadLen           | Silent drop (malformed)                                                             |
| Member group ≠ GroupIdx at ShardBits  | Encoder invariant — origin MUST pack only same-group members (not decoder-enforced) |

## Compatibility

* BRC-12 (0x01) and BRC-124/128 (0x02) receivers MUST discard datagrams with Frame Version 0x08; a bundle does not parse as a single-transaction frame.
* BRC-142 receivers implement decoalescing (or re-bucketing, at a relay) and deliver individual transactions downstream — as multicast frames (BRC-124/128, 0x02) or in the transaction's base BRC-12 / BRC-30 format, per the consumer's contract — preserving existing consumer behaviour.
* Network Magic and Protocol Version are at the BRC-124 offsets, so firewall rules and classifiers matching those still recognise the datagram. The Transaction ID, HashKey, and SeqNum offsets differ from BRC-124, so a classifier reading those fields must branch on Frame Version 0x08.

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — raw member payload and base egress format
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — base frame coalesced by this BRC
* [BRC-30: Transaction Extended Format (EF)](/transactions/0030) — EF member payload and base egress format
* [BRC-128: Multicast Extended Transaction Frame Format](/transactions/0128) — Extended Format members
* [BRC-126: Multicast Transaction NACK Retransmission Protocol](/transactions/0126) — bundle-unit retransmission
* [BRC-129: IPv6 Multicast Group Address Assignments](/transactions/0129) — group/subtree addressing
* [BRC-130: Multicast Transaction Frame Fragmentation](/transactions/0130) — the inverse; mutually exclusive per datagram
* [BRC-139: Multicast Shard Manifest Announcement Protocol](/transactions/0139) — ShardBits/generation coordination

## Constants Reference

| Name             | Value | Hex    | Description                           |
| ---------------- | ----- | ------ | ------------------------------------- |
| FrameVerBundle   | 8     | 0x08   | BRC-142 bundle frame version          |
| BundleHeaderSize | 66    | 0x42   | Bundle header size in bytes           |
| FlagTxIDsPresent | 1     | 0x01   | Flags bit 0: per-member TxIDs present |
| MaxMembers       | 65535 | 0xFFFF | TxCount ceiling (uint16)              |
| MaxMemberTxLen   | 65535 | 0xFFFF | Largest member transaction (uint16)   |


# Subtree Data Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the push wire form of a **subtree** — an ordered list of transaction node hashes plus the merkle root that identifies it — for delivery and ingest over a byte stream. A subtree names its member transactions by hash; a receiver reconstructs the subtree and verifies it against the carried root. All frame integers are fixed-width big-endian; there is no length prefix beyond the node count.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

A subtree is a batch of transactions committed by a merkle root, and a block references its subtrees in order. In an announce/pull system a receiver fetches a subtree **by its root hash** — the root is the request key, carried out-of-band in the request path — and the transferred bytes are a bare list of 32-byte node hashes.

A **push** delivery has no request path: the subtree arrives unsolicited, so the identifying root must travel **in-band**. BRC-143 is that frame — the merkle root followed by the ordered node hashes — and nothing a receiver recomputes locally. Per-node fee and size, the aggregate totals, and the conflict set are deliberately omitted: a receiver's transaction-meta store supplies fee and size, and validation does not consume the others on the ingest path, so carrying them would only inflate an already large object.

## Specification

### Frame layout

All frame integers are big-endian. Node hashes are carried in internal (`chainhash`) byte order — not display order.

```
| Offset | Size   | Field             | Notes                                                                    |
|--------|--------|-------------------|--------------------------------------------------------------------------|
| 0      | 32     | SubtreeMerkleRoot | Merkle root of the node hashes: the subtree identity and verify target.  |
| 32     | 8      | NodeCount (N)     | uint64 BE. Number of node hashes that follow. Delimits the body.         |
| 40     | 32 × N | NodeHashes        | Ordered node hashes, each 32 bytes. Includes the coinbase placeholder.   |
```

Total header: **40 bytes**. `NodeCount` is the sole delimiter — a reader consumes the 40-byte header, then exactly `NodeCount × 32` bytes.

### Coinbase placeholder

A block's **first** subtree begins with a coinbase placeholder: **32 bytes of `0xFF`** at `NodeHashes[0]`. This is *not* zeros — a zero hash is the merkle padding value, and placing zeros where the coinbase leaf belongs corrupts the root. The placeholder is self-describing and detected **by value**; there is no flag. A non-first subtree carries no placeholder.

### Reconstruction and verification

A receiver rebuilds the subtree through the subtree node API, then verifies:

* For each hash in order: if it equals `0xFF × 32`, insert it as the **coinbase node**; otherwise insert it as an ordinary node, sourcing fee and size from the local transaction-meta store (or zero when only the root is being checked).
* Assert the recomputed merkle root equals `SubtreeMerkleRoot`.

Tree height is derived as `ceil(log2(N))`; the merkle computation pads to the next power of two with zero-hash leaves, and an odd node is hashed with itself. No height or capacity field is carried — it would fight that derivation.

**Do not byte-splice.** This wire frame is not a storage serialization; a receiver rebuilds via the node API and never copies these bytes into a deserializer.

### Member transactions

A subtree references its members by hash and does **not** carry them. A receiver validating the subtree also needs the member transaction bytes (for script/UTXO/parent checks); those are supplied from the receiver's own transaction feed or fetched out of band. BRC-143 proper is hashes-only.

## Identity

Subtree identity is `SubtreeMerkleRoot` — the merkle root over `NodeHashes`, with the coinbase placeholder participating as the first leaf. A receiver recomputes it from the hashes to verify the frame; there is no separate identifier field.

## References

* [BRC-144: Block Frame Format](/transactions/0144) — carries the ordered subtree list (the block↔subtree association) that this frame deliberately omits
* [BRC-12: Raw Transaction Format](/transactions/0012) / [BRC-30: Transaction Extended Format (EF)](/transactions/0030) — the member transactions a subtree references by hash

## Constants Reference

| Name                | Value       | Description                               |
| ------------------- | ----------- | ----------------------------------------- |
| SubtreeHeaderSize   | 40          | Root + NodeCount, in bytes                |
| CoinbasePlaceholder | `0xFF` × 32 | Node-hash value marking the coinbase slot |


# Block Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the push wire form of a **block** — the 80-byte block header, block-level counts, the ordered list of subtree roots the block references, the full coinbase transaction, the block height, and the coinbase merkle path — for delivery and ingest over a byte stream. It carries everything a receiver needs to assemble and validate the block without a follow-up fetch. All frame integers are fixed-width big-endian; consensus-defined payloads (the header, the coinbase transaction, the BUMP) keep their native serialisation.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

A block commits to its transactions through a header and names the subtrees it references in order — the block↔subtree association that binds independently delivered subtrees ([BRC-143](/transactions/0143)) into a block and fixes their order for merkle assembly.

In an announce/pull system a node is *notified* of a block (hash, height, a fetch URL) and then pulls the full block on demand. A **push** delivery has no fetch URL: the block arrives whole and unsolicited, so the frame carries the complete block body inline. Its field sequence mirrors the block serialisation a node already ingests — header, counts, subtree roots, the full coinbase, height, and the coinbase BUMP — with each variable-length integer replaced by a fixed-width big-endian field. The coinbase is carried **in-band** because it cannot be delivered as a loose transaction (a node rejects a standalone coinbase on its transaction path) and the block assembly needs its bytes.

## Specification

### Frame layout

All frame integers are big-endian. Hashes are in internal byte order. The embedded block header, coinbase transaction, and coinbase BUMP keep their native serialisations.

```
| Size   | Field             | Notes                                                              |
|--------|-------------------|--------------------------------------------------------------------|
| 80     | BlockHeader       | Standard 80-byte block header (below).                             |
| 8      | TransactionCount  | uint64 BE. Total transactions committed by the block.             |
| 8      | SizeInBytes       | uint64 BE. Total serialized block size.                           |
| 8      | SubtreeCount (M)  | uint64 BE. Number of subtree roots that follow.                   |
| 32 × M | SubtreeHashes     | Ordered subtree merkle roots, each 32 bytes.                      |
| *      | Coinbase          | Full coinbase transaction (BRC-12), self-delimiting by structure. |
| 8      | Height            | uint64 BE. Block height.                                          |
| 8      | CoinbaseBUMPLen   | uint64 BE. Byte length of the coinbase BUMP that follows.         |
| *      | CoinbaseBUMP      | BRC-74 merkle path of the coinbase; present only when `CoinbaseBUMPLen > 0`. |
```

Fixed prefix through `SubtreeCount` is **104 bytes**; `SubtreeHashes`, `Coinbase`, and `CoinbaseBUMP` are variable. The `Coinbase` has no length prefix — it is self-delimiting by transaction structure (version, input/output vectors, locktime), so a reader parses it and resumes at `Height`.

The field order and semantics mirror the block body a node serialises natively; this frame is that body with fixed-width big-endian counts in place of variable integers.

### Block header (80 bytes)

The standard block header, consensus byte layout:

| Offset | Size | Field               | Encoding                                         |
| ------ | ---- | ------------------- | ------------------------------------------------ |
| 0      | 4    | Version             | uint32 little-endian                             |
| 4      | 32   | Previous block hash | SHA256d, internal byte order                     |
| 36     | 32   | Merkle root         | SHA256d of the tx tree, internal byte order      |
| 68     | 4    | Timestamp           | uint32 little-endian (Unix seconds)              |
| 72     | 4    | nBits               | uint32 little-endian (compact difficulty target) |
| 76     | 4    | Nonce               | uint32 little-endian                             |

`BlockHash = SHA256d(BlockHeader)` — the receiver computes it from the 80 bytes; there is no separate block-hash field. The previous-block hash (bytes 4–35) gives chain context intrinsically. Block height is not present in a block header (it appears in the coinbase per BIP-34) and is carried explicitly as the `Height` field so the receiver need not extract it.

### Coinbase and subtree association

`SubtreeHashes` is ordered to match the producer's subtree enumeration — the order used to assemble the block merkle root from the subtree roots. A block's **first** subtree ([BRC-143](/transactions/0143)) carries the `0xFF × 32` coinbase placeholder at its first node; the receiver substitutes the coinbase transaction carried here into that slot when it reconstructs the block.

The coinbase is the full transaction, not just its identifier: a node rejects a loose coinbase on its transaction-ingest path, so it is delivered only here, and block assembly consumes its bytes directly.

## Identity

Block identity is `BlockHash = SHA256d(BlockHeader)`. The block's committed merkle root (header bytes 36–67) is the value a receiver reproduces from the ordered subtree roots and the coinbase; there is no separate identifier field.

## References

* [BRC-143: Subtree Data Frame Format](/transactions/0143) — the subtrees this block orders; the block↔subtree association lives here, not in the subtree frame
* [BRC-74: BSV Unified Merkle Path (BUMP) Format](/transactions/0074) — the `CoinbaseBUMP` payload
* [BRC-12: Raw Transaction Format](/transactions/0012) — the coinbase transaction body

## Constants Reference

| Name             | Value | Description                                                      |
| ---------------- | ----- | ---------------------------------------------------------------- |
| BlockFramePrefix | 104   | Header + TransactionCount + SizeInBytes + SubtreeCount, in bytes |
| BlockHeaderSize  | 80    | Block header, in bytes                                           |


# Multicast Shard Domain Partitioning and the BEEF Object Plane

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC extends the IPv6 multicast group address scheme of [BRC-129](/transactions/0129) by partitioning the 16-bit shard-index space into independent **object planes**, each identified by a 4-bit domain selector in the high nibble of the index. The existing transaction plane (raw and Extended Format transactions) is retained unchanged as domain `0x0`; a new **BEEF object plane** is allocated as domain `0x1` to carry [BRC-62](/transactions/0062), [BRC-95](/transactions/0095), and [BRC-96](/transactions/0096) BEEF-family transaction objects for peer-to-peer and overlay synchronisation. The BEEF plane shards by **overlay topic**: each object carries a topic identifier from which its multicast group is derived by the BRC-129 top-bits rule, and overlay-tier subscribers filter delivery by elected topic and by BEEF version (encoding). Each plane subscribes and publishes independently and may run its own shard-bit width. This BRC also forward-extends the [BRC-139](/transactions/0139) shard-manifest protocol with a per-domain descriptor section so each plane can advertise and coordinate its own `shard_bits` and generation transitions without disturbing the others. The transaction plane's on-wire addresses, frames, and manifests are byte-identical to their pre-BRC-148 form; this is a strictly additive extension.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

[BRC-129](/transactions/0129) allocates the bottom 32 bits of the IANA Bitcoin multicast allocation (`FF0X::B`) as a 16-bit group-id (default `0x000B`) followed by a 16-bit shard index. It caps `shard_bits` at 12 so that all transaction shard groups occupy `0x0000`–`0x0FFF`, and it reserves `0x1000`–`0xF7FF` as free space "for future expansion ... as well as specialty transmission domains for purpose-specific multicast services that are not general-purpose transaction sharding."

BEEF-family transactions are exactly such a purpose-specific service. Unlike the transaction plane — whose consumers are miners and settlement infrastructure requiring raw or Extended Format transactions — BEEF objects carry ancestry and Merkle-path proofs ([BRC-74](/transactions/0074)) for **peer-to-peer and overlay synchronisation**: SPV wallets, overlay services, and application-layer indexers that validate without a UTXO set. The two use cases do not overlap. A BEEF object is never a substitute for the settlement transaction on the transaction plane, and the transaction plane never carries proof ancestry. They are distinct planes of operation with distinct classes of both publishers and subscribers.

Overlay networks identify themselves by **topic**. A [BRC-22](/overlays/0022) submission is a BEEF object plus a list of topic names; per-topic managers decide admittance; [BRC-87](/overlays/0087) names topics (`tm_*`); and under [BRC-88](/overlays/0088) a broadcaster discovers the hosts interested in a topic via SHIP advertisements and then propagates the object to each host point-to-point over HTTPS. That per-host fan-out is the step that does not scale as topics and hosts multiply. This BRC gives that propagation step a multicast substrate: a publisher emits a topical BEEF object once, and every subscribed host of that topic receives it, globally, with multicast-native retransmission. History bootstrap remains the province of the existing synchronisation protocols (see Operational Considerations).

Carrying BEEF objects on their own multicast plane — rather than translating them into the transaction plane — keeps the fabric stateless and lossless: no proof data is stripped, no per-input UTXO resolution is required, and subscribers receive exactly the format they requested. It also establishes the addressing and coordination pattern for the broader application/overlay layer, which is expected to scale independently of, and potentially beyond, the transaction plane's shard count.

This BRC specifies:

1. A backward-compatible **domain partition** of the 16-bit shard-index space so that multiple independent object planes coexist without colliding with each other, with the transaction plane, or with the control plane.
2. The allocation of the **BEEF object plane** (domain `0x1`) with its topical sharding, publication, and filtered-delivery rules.
3. A **plane growth policy** that reserves each plane a contiguous runway so it can widen its shard-bit space over time without re-basing its addresses.
4. A **per-domain extension to the BRC-139 shard manifest** so each plane advertises and live-reshards its own `shard_bits` and generation independently.

## Specification

### Relationship to BRC-129

This BRC supersedes the [BRC-129](/transactions/0129) section *"Free Space and Specialty Transmission Domains"* and refines the *"Data-Plane Shard Groups"* derivation. All other BRC-129 provisions — the IANA allocation, the group-id field (bytes `[12:14]`, default `0x000B`), the source-mode / address-range rules (ASM `FF0x` vs SSM `FF3x`), the group-id override, and the control-plane assignments `0xF800`–`0xFFFF` — remain in force unchanged. Domain `0x0` under this BRC is bit-for-bit identical to a BRC-129 deployment; a participant that implements only BRC-129 interoperates fully with the transaction plane of a BRC-148 deployment.

### Domain-Partitioned Shard Index

The 16-bit shard-index field (address bytes `[14:16]`, "IDX") is partitioned into a 4-bit **domain selector** in the high nibble and a shard index in the low bits:

```
IDX[15:0] =  D D D D | S S S S S S S S S S S S
             └domain┘   └──── shard index ────┘   (shard_bits ≤ 12 ⇒ within low 12 bits)
```

Because the transaction plane bounds `shard_bits ≤ 12`, its shard index never occupies the high nibble, which is therefore always `0x0` for domain `0x0` — i.e. the pre-existing `0x0000`–`0x0FFF` range. A non-zero domain selects a distinct plane whose base address is offset by the domain value.

#### Plane base and address derivation

Each plane is assigned a **plane base** aligned to a `0x1000` boundary:

```
planeBase(domain) = uint16(domain) << 12          // 0x0000, 0x1000, 0x2000, …
```

Each plane defines a 32-byte **shard key** for its objects: the transaction plane keys on the transaction ID; the BEEF plane keys on the topic identifier (see *Topical sharding* below). The group index is the unmodified BRC-129 top-bits derivation applied to the plane's shard key at the plane's own shard-bit width, and the plane base is added to form the 16-bit IDX:

```
shardIndex = (binary.BigEndian.Uint32(shardKey[0:4]) >> (32 - shardBits)) & (2^shardBits - 1)
IDX        = planeBase(domain) + shardIndex
address    = [MCPrefix][0x00 × 10][MCGroupID][IDX]
```

For domain `0x0` (shard key = TxID, `shard_bits ≤ 12`), `planeBase` is `0x0000` and the formula reduces exactly to BRC-129. The addition (rather than a bitwise OR) is significant only for **wide planes** (see below); for any plane with `shard_bits ≤ 12` and a `0x1000`-aligned base, the low-12-bit shard index and the high-nibble base do not overlap and the result is identical to an OR.

**`shardBits = 0` — the single-group plane.** A width of zero is valid and means the plane is not sharded: `shardIndex` is `0` for every object and the plane occupies exactly its base slot, `IDX = planeBase(domain)`. Implementers MUST special-case it rather than evaluate the expression above, whose `>> (32 - shardBits)` term becomes a 32-bit shift of a 32-bit value — zero in some languages, undefined behaviour in others. This is the expected configuration for a plane whose delivery selectivity comes from elsewhere: on the BEEF plane a subscriber's topics are filtered per-consumer at the delivery operator, so a single group carrying every topic is a deliberate operating point, not a degenerate one, and it removes generation transitions entirely. A deployment SHOULD widen `shard_bits` only when per-group delivery capacity, not topic count, becomes the binding constraint.

The consistent-hashing split property of BRC-129 is preserved per plane: increasing a plane's `shard_bits` by one splits each of its groups into exactly two children, so no subscription is ever re-hashed to an unrelated group. A subscriber holding a shard *slice* adds both children; a subscriber following a single shard key (e.g. one topic) follows it into the one child that inherits it, joining the child before leaving the parent during the transition window.

#### Domain registry

| Domain      | Base              | Plane                                                      | Specification             |
| ----------- | ----------------- | ---------------------------------------------------------- | ------------------------- |
| `0x0`       | `0x0000`          | Transaction plane (raw / EF)                               | BRC-124, BRC-128, BRC-129 |
| `0x1`       | `0x1000`          | **BEEF object plane**                                      | this BRC                  |
| `0x2`–`0xE` | `0x2000`–`0xE000` | Reserved for future object planes                          | future BRC                |
| `0xF`       | `0xF000`          | **Forbidden** — overlaps control plane (`0xF800`–`0xFFFF`) | —                         |

Domain `0xF` MUST NOT be assigned as a plane base: its slot spans `0xF000`–`0xFFFF`, of which `0xF800`–`0xFFFF` is the BRC-129 control plane. Planes therefore occupy domains `0x1`–`0xE`. The residual band `0xF000`–`0xF7FF` (2,048 indices) between the top plane slot and the control plane remains reserved; implementations MUST NOT join or transmit to it.

Implementations MUST NOT join or transmit to a plane's addresses unless that plane is defined by this BRC or a future BRC.

### Plane Allocation and Growth Policy

Planes are allocated from opposite ends of the object-plane band so that the BEEF plane retains a contiguous runway to widen its shard space:

* The **BEEF object plane grows upward** from base `0x1000`. Additional `shard_bits` consume successive `0x1000` slots immediately above it.
* **Future specialty planes are allocated from the top downward**, beginning at domain `0xE` (base `0xE000`) and descending, so their bases never intrude on the BEEF plane's upward runway.

Under this policy the BEEF plane may widen to as many as `shard_bits = 15` (32,768 groups, occupying `0x1000`–`0x8FFF`) before meeting descending specialty allocations, without ever re-basing its `0x1000` origin. A plane MUST NOT be widened into slots already reserved to another plane.

### Wide Planes (`shard_bits > 12`)

A plane MAY operate with `shard_bits` greater than 12, in which case its shard index exceeds 12 bits and its address range spans multiple contiguous `0x1000` slots. Such a plane reserves **SlotSpan** = `ceil(2^shardBits / 4096)` consecutive slots starting at its base. Constraints:

1. `planeBase + 2^shardBits ≤ 0xF800` (the range MUST NOT reach the control plane).
2. A plane's reserved slots MUST NOT overlap any other plane's reserved slots.
3. The plane base MUST remain `0x1000`-aligned; widening reserves additional slots upward but does not move the base.

Domain `0x0` (the transaction plane) retains the BRC-129 cap of `shard_bits ≤ 12` and MUST NOT be widened, preserving interoperability with BRC-129-only participants.

### The BEEF Object Plane (Domain `0x1`)

#### Payload

The BEEF object plane carries BEEF-family transaction objects **verbatim**. All three BEEF encodings are permitted and are self-identifying by their leading marker, so a single plane carries them without a per-format sub-allocation:

| Object                        | Leading marker            | Reference                    |
| ----------------------------- | ------------------------- | ---------------------------- |
| BEEF                          | `0100BEEF`                | [BRC-62](/transactions/0062) |
| Atomic BEEF                   | `01010101` + 32-byte TXID | [BRC-95](/transactions/0095) |
| BEEF V2 (TXID-only extension) | `0200BEEF`                | [BRC-96](/transactions/0096) |

The fabric never parses BEEF structure. Sharding, identity, and filtering key on envelope fields fixed at ingress — the submitted topic (TopicID), a hash of the whole object bytes (ContentID), and the fixed-offset version word. It performs no UTXO resolution and strips no proof data.

#### Topical sharding

The BEEF plane's shard key is the **overlay topic**, not the transaction ID. Overlay subscribers want topical slices; transaction IDs are uniformly distributed, so TxID sharding would spread every topic across every group and force each topical subscriber to receive the entire plane. Keying the group on the topic makes the multicast group itself the coarse topical filter.

The **topic identifier** of an object is the hash of its topic name:

```
TopicID = SHA-256(UTF-8 topic name)        // e.g. SHA-256("tm_uhrp_files")
```

and the object's group is the plane derivation applied to it:

```
shardIndex = (binary.BigEndian.Uint32(TopicID[0:4]) >> (32 - shardBits)) & (2^shardBits - 1)
IDX        = 0x1000 + shardIndex
```

Properties:

* **One topic, one group.** A subscriber to topic *T* joins exactly `0x1000 + shardIndex(T)` and receives *T* plus only the topics that share its group by hash: at one million topics, ≈244 co-resident topics per group at `shard_bits = 12`, ≈31 at the reserved width of 15.
* **Zero fabric topic state.** Any topic string hashes to a group without registration, matching the permissionless BRC-22 topic space. Per-topic state exists only where topics are *elected* — at delivery operators, for their own subscribers. The fabric's routing state scales with groups and sources, never with topic count.
* **Consistent-hash splits.** Widening `shard_bits` by one sends each topic to one of its group's two children; subscribers move each elected topic to its child group during the generation-transition window.
* **Multi-topic objects.** A BRC-22 submission may name several topics; the object is emitted once per topic, each frame carrying that topic's TopicID. Sibling emissions share a ContentID (see *Frame carriage*).

**Per-topic throughput bound.** Because a topic occupies one group at a time, a single topic's sustained rate is bounded by per-group delivery capacity, and widening `shard_bits` splits topic *sets*, not one hot topic. A future descriptor revision (`Version > 0`) may define a declared-spread mechanism (a hot topic spread across `2^w` sibling groups by ContentID bits); this revision reserves the problem and does not define it.

**Subject transaction.** The subject TxID remains consumer-level semantics — Atomic BEEF (BRC-95) carries it explicitly in the 32 bytes after its prefix; for BRC-62/BRC-96 it is the last transaction in topological order. The fabric does not use it for sharding or identity.

#### Publication (ingress)

BEEF-plane publishers — overlay hosts, application services, and end users — form an **overlay ingress class**, distinct from transaction-plane submitters. Admission is operator policy, conditioned on topic count. Each emitted frame reaches a single topical group, so a **single-topic** submission has the bounded, election-scoped footprint of a transaction submission and is admitted as an **open class**. A **multi-topic** submission fans one object out to one frame per topic — up to a 15× amplification — so open/anonymous ingress MUST admit `TopicCount == 1` and reject `TopicCount > 1`, while multi-topic (`TopicCount > 1`) is an authenticated-ingress capability (BRC-149 §Fan-out admission). This split is an interoperability requirement; an operator MAY further restrict the class as local policy. A submission is the pair *(topic list, BEEF object)*, mirroring the BRC-22 submit shape. For each submitted topic the ingress derives the TopicID, computes the object's ContentID, and emits one frame to the topic's group. Publishers submit to operator ingress; the plane's multicast sources remain the operator's proxies, as on the transaction plane.

Re-submission of the same subject transaction with an updated proof (a BRC-62 BUMP refreshed after the transaction mines) is a legitimate, distinct object, and sibling emissions of a multi-topic submission share a ContentID. Ingress duplicate suppression MUST therefore key on the (ContentID, TopicID) pair, never on the subject TxID or the ContentID alone.

#### Independent-plane semantics

The BEEF plane and the transaction plane are independent planes of operation:

1. **No cross-plane translation.** An object submitted to the BEEF plane is delivered only on the BEEF plane; a transaction submitted to the transaction plane is delivered only there. The fabric never bridges the two. A publisher that requires a transaction to reach both miners and overlay subscribers submits to both planes.
2. **Distinct publisher and subscriber classes (tiers).** The transaction plane serves the *settlement* and *miner* consumer tiers; the BEEF plane serves a distinct **overlay** consumer tier with its own ingress and delivery paths. These are separate participant classes; a single node MAY participate in more than one.
3. **Independent shard width.** The BEEF plane MAY run a `shard_bits` different from the transaction plane (coordinated per the manifest extension below).

#### Delivery identifiers and filtering

Fan-out filtering reads exactly two fixed-offset envelope fields; neither requires a payload parse:

1. **TopicID** (header offset 56) — the **selectivity axis**. Cardinality is unbounded: the plane accommodates millions of concurrent topics because the fabric holds no per-topic state and filters resolve by hash lookup.
2. **BEEF version** (payload bytes 0–3, immediately after the header) — the **encoding-capability axis**. Cardinality is small and closed: the three markers in the Payload table above. [BRC-62](/transactions/0062) fixes the version word as a Uint32LE sequence beginning at `4022206465` (`0100BEEF`) whose marker remains `BEEF` only through `4022271999` — at most 65,535 format versions. The version identifies an *encoding*, never an overlay: it MUST NOT be repurposed as an overlay namespace, both because it collides with future BEEF format revisions and because its space is orders of magnitude smaller than the topic population.

An **overlay-tier** subscriber's election is the pair *(topics, versions)*. A subscriber's group set follows from its election: a topical subscriber's groups are derived from its elected topics (one group per topic hash); an aggregator elects groups directly — up to the whole plane — and takes every topic they carry.

* **Topic filter** — the set of elected TopicIDs. An object is delivered only when its TopicID is a member. An absent topic filter admits every topic on the subscriber's elected groups (aggregator mode).
* **Version filter** — the set of accepted BEEF versions; absent admits all. This is a capability gate: for example, a host that cannot resolve BRC-96 TXID-only ancestors excludes `0200BEEF`.

Filters compose in order: **group membership (network) → topic filter → version filter → delivery**. Filtering narrows what a joined subscriber receives; it never alters shard-group derivation, join sets, or subscriber-to-node placement.

#### Conforming listener profile

A listener that serves the BEEF plane MUST:

1. Join the plane's domain-tagged groups (per source mode) and receive frames using the same [BRC-124](/transactions/0124) / [BRC-126](/transactions/0126) machinery as the transaction plane, with retransmission keying and sequencing per *Frame carriage* below: flows are tracked per (sender, group), gap-detected on `SeqNum`, and recovered by NACK independently of transaction-plane flows.
2. Read each object's TopicID and BEEF version from their fixed offsets.
3. Apply each subscriber's topic filter, then version filter, at fan-out. The topic filter MUST resolve in time independent of the number of elected topics (e.g. a TopicID-keyed hash lookup), so per-frame cost does not grow with topic count.
4. Suppress retransmit duplicates per flow and `SeqNum` exactly as on the transaction plane.

These obligations are identical for every conforming listener implementation.

#### Frame carriage

Objects on the BEEF plane are carried in multicast frames that reuse the [BRC-124](/transactions/0124) 92-byte header layout with a distinct `FrameVersion`, preserving HashKey/SeqNum stamping and [BRC-126](/transactions/0126) NACK retransmission; objects that exceed the path MTU are fragmented per [BRC-130](/transactions/0130). The concrete frame format — and the unicast submission/delivery record grammars used at ingress and delivery — are specified in [BRC-149](/transactions/0149), which assigns `FrameVersion 0x09`. This BRC constrains the header fields that addressing, retransmission, and filtering depend on:

* **ContentID (offset 8, 32 bytes)** — SHA-256d over the complete object bytes. This is the same hash BRC-130 already requires for reassembly verification of fragmented payloads, so fragmentation needs no special-casing. It MUST NOT be the subject TxID: a proof update re-emits the same subject with different bytes. BRC-130 reassembly keys on this field **paired with TopicID** — sibling emissions of one object to different topics share a ContentID, so a ContentID-only slot key would collapse them and deliver only one — hence two in-flight objects for one subject must never share it.
* **TopicID (offset 56, 32 bytes)** — the field that carries the SubtreeID in transaction frames.
* **HashKey** = `XXH64(senderIPv6 ∥ domain-tagged groupIdx ∥ zeros)`. Unlike transaction frames, the 32-byte field (TopicID) is **excluded** from the flow key: including it would create one flow — and one gap tracker — per (sender, topic). Flows are per (sender, group), as in [BRC-131](/transactions/0131), so retransmission and sequencing state is bounded by groups × multicast sources regardless of topic count. The domain-tagged IDX (`0x1nnn`) in the HashKey keeps BEEF flows distinct from transaction flows for the same shard number.
* **SeqNum** — per-sender monotonic within the (sender, group) flow; gap detection and NACK recovery operate on it unchanged.
* **NACK fields** — object-plane NACKs carry zero in the 32-byte SubtreeID field and responders MUST ignore it; lookup is HashKey ∥ SeqNum alone (a flow interleaves topics).
* **BEEF version** — the first four payload bytes; not duplicated in the header.

Fragmentation composes cleanly with filtering: BRC-130 fragment headers are layout-identical to BRC-124 for bytes 0–91, so ContentID and TopicID appear in **every** fragment, while the version word appears only in the first fragment's data. Listeners reassemble before fan-out (BRC-130 delivers the reassembled object as a synthetic frame), so both filters evaluate on whole objects; a listener MAY additionally drop fragments early by TopicID when no subscriber has elected the topic.

### Per-Domain Shard Coordination (BRC-139 Extension)

[BRC-139](/transactions/0139) advertises a single `shard_bits` and generation for the fabric. This BRC adds an optional, backward-compatible **Domains** payload section so each plane advertises and coordinates its own parameters.

#### Flags

One new flag bit is defined in the BRC-139 `Flags` byte (offset 7):

| Bit    | Name           | Meaning                                                        |
| ------ | -------------- | -------------------------------------------------------------- |
| `1<<7` | `DomainsValid` | The datagram carries a Domains descriptor section (see below). |

`1<<7` is the final unallocated bit in the BRC-139 `Flags` byte. To avoid exhausting the flag space, the Domain Descriptor block carries its own `Version` field (below), which is the forward extension point for all subsequent per-domain additions; no further top-level flag bit is to be spent on plane coordination.

#### Backward compatibility

When `DomainsValid = 0`, the datagram is a standard BRC-139 manifest describing the transaction plane only; this is the default and requires no change from BRC-139 producers or consumers.

When `DomainsValid = 1`, the top-level `ShardBits` (offset 36), `GenerationID` (offset 48), and any top-level Successor block continue to describe **domain `0x0`** exactly as in BRC-139. A BRC-139-only consumer ignores the trailing Domains section and remains correct for the transaction plane. A BRC-148 consumer additionally parses the Domains section for planes `≥ 0x1`. A domain-0 descriptor MAY appear in the Domains section; if present it MUST agree with the top-level fields, which remain authoritative for domain `0x0`.

#### Domains payload section

When `DomainsValid = 1`, a Domains section is appended **after** the BRC-139 Groups, Sources, and (top-level) Successor payloads, in this order. It begins with a one-byte `DomainCount` followed by `DomainCount` fixed-layout descriptors:

```
DomainCount (1 byte)  — number of descriptors, 1..15 (domains 0x00–0x0E)
followed by DomainCount × Domain Descriptor
```

**Domain Descriptor** — 24-byte core, optionally followed by a 24-byte Successor block:

| Offset | Size | Field        | Description                                                                  |
| ------ | ---- | ------------ | ---------------------------------------------------------------------------- |
| 0      | 1    | DomainID     | Plane domain selector (`0x00`–`0x0E`)                                        |
| 1      | 1    | ShardBits    | This plane's shard-bit width (`0`–`15`; `0x00` MUST be `≤ 12`)               |
| 2      | 1    | SlotSpan     | Contiguous `0x1000` slots reserved to this plane; `≥ ceil(2^ShardBits/4096)` |
| 3      | 1    | DomainFlags  | See below                                                                    |
| 4      | 1    | Version      | Descriptor format version; `0x00` in this revision                           |
| 5      | 3    | Reserved     | MUST be zero on send; ignored on receive                                     |
| 8      | 16   | GenerationID | This plane's 128-bit generation identifier; bumped on `ShardBits` change     |
| 24     | 24   | Successor    | Present iff `DomainFlags.SuccessorValid`; layout per BRC-139 Successor block |

**DomainFlags**:

| Bit    | Name             | Meaning                                                                                           |
| ------ | ---------------- | ------------------------------------------------------------------------------------------------- |
| `1<<0` | `SourceModeSSM`  | This plane's data plane uses SSM (`FF3x`).                                                        |
| `1<<1` | `SuccessorValid` | A 24-byte Successor block follows this descriptor's core.                                         |
| `1<<2` | `Active`         | The announcer publishes and/or serves this plane (authoritative per-domain participation signal). |

The overall datagram size becomes:

```
64 + groups + sources + topSuccessor
   + (DomainsValid ? 1 + Σ(24 + (SuccessorValid_i ? 24 : 0)) : 0)
```

The `ManifestCRC` (offset 44) is computed over the whole datagram including the Domains section, as in BRC-139. Implementations SHOULD keep the total datagram `≤ 1232` bytes to avoid IPv6 fragmentation; a handful of descriptors fits comfortably within that budget.

#### Validation

* `DomainsValid = 1` with `DomainCount = 0` is malformed and MUST be rejected.
* `DomainID` values MUST be in `0x00`–`0x0E`, unique within a datagram, and MUST NOT be `0x0F`.
* For each descriptor, `planeBase(DomainID) + 2^ShardBits ≤ 0xF800`, and the reserved slot ranges of distinct descriptors MUST NOT overlap.
* A descriptor with `DomainID = 0x00` MUST carry `ShardBits ≤ 12` and MUST agree with the top-level `ShardBits`/`GenerationID`.
* `SuccessorValid = 1` requires the announcer to be `Authoritative` (BRC-139 `Flags` `1<<1`) and requires the successor's `ShardBits` to differ from the descriptor's `ShardBits` by at most ±1. The slot-range constraint (`planeBase(DomainID) + 2^ShardBits ≤ 0xF800`, no overlap with other planes' reserved slots) applies to the successor's `ShardBits` equally.
* Consumers MUST ignore descriptors whose `DomainID` names a plane they do not implement, rather than rejecting the datagram.

#### Per-domain adoption and live resharding

The BRC-139 normative consumer profile — authoritative quorum, hysteresis, ±1 `shard_bits`-shift bound, manual-pin precedence, divergence telemetry, and Successor-block generation transitions with dual-emit bridging — applies **per domain**, keyed on `DomainID`. Each plane adopts, pins, and transitions its own `ShardBits`/`GenerationID` independently; a generation transition on one plane does not perturb another. Domain `0x0` continues to be coordinated by the top-level BRC-139 fields for backward compatibility, equivalently to a `DomainID = 0x00` descriptor.

`SlotSpan` MAY exceed the value implied by the current `ShardBits` to reserve a widening runway in advance; consumers MUST treat the reserved slots as belonging to that plane even before they are populated.

#### RoleHint

Two `RoleHint` values (BRC-139 offset 37) are added as informational hints that a participant serves the BEEF plane:

| Value | Name           | Meaning                          |
| ----- | -------------- | -------------------------------- |
| `6`   | `ProducerBEEF` | Publishes BEEF-plane objects.    |
| `7`   | `ListenerBEEF` | Subscribes to BEEF-plane groups. |

`RoleHint` is a single informational byte and cannot express a node that serves multiple planes; the per-domain `DomainFlags.Active` bit is the authoritative per-plane participation signal, and the **TopicID and BEEF version** govern per-object routing and filtering. `RoleHint` conveys only a coarse participant-class hint and MUST NOT be relied upon for any routing, filtering, or delivery decision.

### Source Discovery (SSM) for Object Planes

Under SSM, a subscriber must learn each plane's publisher sources before issuing `(S,G)` joins. When an object plane is published by the same sources as the transaction plane, the plane inherits the BRC-139 global Sources payload: a subscriber unions the announced sources and issues `(S,G)` joins for the plane's domain-tagged groups using the same source set. A plane whose `DomainFlags.SourceModeSSM` differs from domain `0x0` selects the corresponding `FF3x`/`FF0x` prefix for that plane's addresses only.

If a future deployment publishes an object plane from a **disjoint** source set, per-domain source advertisement is required; this is reserved for a future revision via the descriptor's `Version` field. In this revision, all object planes MUST be published from the announced global source set.

### Operational and Security Considerations

* **Firewall / PIM / MLD.** Deployments MUST permit join and forwarding for the BEEF plane's address band (`0x1000`–`0x1FFF` at the configured scope and source mode, extended per `SlotSpan`) in addition to the transaction-plane and control-plane ranges. Operators SHOULD scope object-plane groups identically to the transaction plane unless deliberately isolating them.
* **Structural parsing bound.** The fabric never walks BEEF structure (identity and filtering key on ingress-fixed envelope fields), but ingress MUST bound accepted object size, and consumers parsing attacker-influenced objects MUST bound the walk (transaction count, BUMP sizes, nesting) and reject malformed input rather than allocating unboundedly.
* **State bounds at scale.** Every per-topic cost lives at the edge of the system: the fabric's multicast routing state scales with joined groups × sources; ingress holds per-(sender, group) flow counters; a delivery listener holds one filter entry per *elected* topic of its own subscribers; per-frame filter evaluation is O(1). No component's state scales with the global topic population, which is what admits millions of concurrent overlays.
* **Live tail, not history.** Multicast delivery begins at join time; NACK recovers transit gaps, not missed history. A host bootstrapping a topic acquires history through the overlay synchronisation protocols ([BRC-88](/overlays/0088) propagation, [BRC-76](/transactions/0076) Graph Aware Sync, [BRC-136](/overlays/0136) block-anchored sync) and uses this plane for the live tail thereafter.
* **No settlement guarantee.** Because the planes do not bridge, a transaction submitted only to the BEEF plane is not delivered to transaction-plane (settlement) subscribers. This is intentional; publishers requiring settlement MUST also submit on the transaction plane.
* **Group-id override and scope.** The BRC-129 group-id override and scope rules apply unchanged to all planes; a private group-id isolates every plane simultaneously.

## References

* [BRC-22: Overlay Network Data Synchronization](/overlays/0022) — topical submission and admittance model the plane transports
* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062) — BEEF encoding carried on the object plane
* [BRC-74: BSV Unified Merkle Path (BUMP) Format](/transactions/0074) — proof payload embedded in BEEF
* [BRC-76: Graph Aware Sync Protocol](/transactions/0076) — overlay history synchronisation complementing the live tail
* [BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](/overlays/0087) — `tm_*` topic naming hashed into TopicIDs
* [BRC-88: Overlay Services Synchronization Architecture](/overlays/0088) — SHIP/SLAP host discovery and the per-host propagation this plane subsumes
* [BRC-95: Atomic BEEF Transactions](/transactions/0095) — explicit-subject BEEF encoding
* [BRC-96: BEEF V2 Txid Only Extension](/transactions/0096) — TXID-only BEEF encoding
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — data-frame header reused by object-plane frames
* [BRC-126: Multicast Transaction NACK Retransmission Protocol](/transactions/0126) — retransmission machinery inherited by object planes
* [BRC-128: Multicast Extended Transaction Frame Format](/transactions/0128) — Extended Format frames on the transaction plane
* [BRC-129: IPv6 Multicast Group Address Assignments](/transactions/0129) — base addressing scheme this BRC extends
* [BRC-130: Multicast Transaction Frame Fragmentation](/transactions/0130) — fragmentation for large objects
* [BRC-136: Block-Anchored Overlay Synchronization via Block-Aligned Sparse Merkle Trees (BASM)](/overlays/0136) — block-anchored overlay history synchronisation
* [BRC-139: Multicast Shard Manifest Announcement Protocol](/transactions/0139) — manifest protocol this BRC forward-extends
* [BRC-149: Multicast BEEF Object Frame Format](/transactions/0149) — companion: the concrete frame and submission/delivery record formats
* [RFC 4607: Source-Specific Multicast for IP](https://www.rfc-editor.org/rfc/rfc4607) — SSM address range
* [RFC 8815: Deprecating Any-Source Multicast (ASM) for Interdomain Multicast](https://www.rfc-editor.org/rfc/rfc8815) — rationale for SSM-only global scope


# Multicast BEEF Object Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies the three wire forms of the [BRC-148](/transactions/0148) BEEF object plane: the **multicast object frame** (FrameVer `0x09`, assigned here), which carries one BEEF-family transaction object on the plane's domain-tagged shard groups; the **submission record**, the unicast envelope a publisher sends to operator ingress naming one object to one or more overlay topics; and the **delivery record**, the unicast envelope a delivery edge streams to a subscriber. The frame reuses the [BRC-124](/transactions/0124) 92-byte header layout at identical offsets, so existing classifiers, retransmission, and fragmentation infrastructure require no changes. The records exist because BEEF bytes are not self-delimiting without a full structural parse — which the fabric never performs — so unlike the BRC-143/BRC-144 push lanes, BEEF lanes carry an explicit length-carrying envelope.

## Copyright

This BRC is licensed under the Open BSV License.

## Motivation

[BRC-148](/transactions/0148) allocates the BEEF object plane (domain `0x1`), defines its topical sharding, filtering, and coordination, and constrains the header fields that addressing, retransmission, and filtering depend on — but, following the convention that frame formats are specified separately from addressing (BRC-124 vs BRC-129; BRC-143/BRC-144 vs their carriage rules), it defers the concrete formats to this BRC. This BRC assigns the frame version and fixes the byte layouts: the fabric frame, and the two unicast record forms that carry BEEF objects between participants and operator infrastructure.

## Specification

### BEEF object frame (FrameVer `0x09`) — 92-byte header + payload

FrameVer `0x09` is assigned to the BEEF object frame (the next code after BRC-142's `0x08`).

| Offset | Size | Type        | Field            | Description                                                                                                                                                                                                 |
| ------ | ---- | ----------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0      | 4    | `uint32` BE | Network Magic    | `0xE3E1F3E8` (BSV mainnet P2P magic). Frames with incorrect magic are rejected.                                                                                                                             |
| 4      | 2    | `uint16` BE | Protocol Version | `0x02BF` (703). Informational; receivers do not validate.                                                                                                                                                   |
| 6      | 1    | `byte`      | Frame Version    | `0x09` — BEEF object frame. Any other value is handled by a different decoder.                                                                                                                              |
| 7      | 1    | `byte`      | Reserved         | `0x00` on send; ignored on receive (reserved for future plane-level message types). The BEEF encoding version is **not** duplicated here — it is the payload's first four bytes.                            |
| 8      | 32   | `[32]byte`  | ContentID        | `SHA-256d(payload bytes)` — the object's identity; and the BRC-130 reassembly verification hash. With TopicID it keys both fragment reassembly and duplicate suppression. Never the subject TxID.           |
| 40     | 8    | `uint64` BE | HashKey          | Per-(sender, group) flow identifier; stamped at ingress; `0` = unset. Derivation and flow semantics per [BRC-148](/transactions/0148) §Frame carriage (TopicID excluded).                                   |
| 48     | 8    | `uint64` BE | SeqNum           | Per-sender monotonic counter within the (sender, group) flow; stamped at ingress; `0` = unstamped. Drives gap detection, NACK recovery, and retransmit dedup.                                               |
| 56     | 32   | `[32]byte`  | TopicID          | `SHA-256(UTF-8 topic name)`. The delivery-selectivity key: group derivation takes its top bits, and fan-out filters subscribers on it. Occupies the field that carries the SubtreeID in transaction frames. |
| 88     | 4    | `uint32` BE | Payload Length   | Byte length of the payload.                                                                                                                                                                                 |
| 92     | \*   | `[]byte`    | Payload          | The BEEF object **verbatim** — no envelope, no re-encoding, proof data intact.                                                                                                                              |

#### Payload leading bytes — BEEF version word

| Payload `[0:4]` | Type                                         | Encoding                      | Reference                    |
| --------------- | -------------------------------------------- | ----------------------------- | ---------------------------- |
| `0100BEEF`      | `uint32` LE (4022206465)                     | BEEF                          | [BRC-62](/transactions/0062) |
| `0200BEEF`      | `uint32` LE (4022206466)                     | BEEF V2 (TXID-only extension) | [BRC-96](/transactions/0096) |
| `01010101`      | 4-byte prefix (32-byte subject TxID follows) | Atomic BEEF                   | [BRC-95](/transactions/0095) |

The version word is the BRC-148 version filter's input — an encoding-capability gate only, never an overlay namespace. Intentionally absent from the header: the subject TxID (consumer-level semantics inside the payload) and any per-format sub-type byte (the marker is self-identifying at a fixed offset).

#### Fragmentation

Objects exceeding the path MTU are carried as [BRC-130](/transactions/0130) fragments (`FrameVer 0x03`, `OrigFrameVer = 0x09`) with bytes 0–91 layout-identical to the table above, so ContentID and TopicID appear in every fragment; ContentID is the reassembly verification hash. Reassembly MUST key slots on the (ContentID, TopicID) pair — sibling emissions of one object to different topics share a ContentID, so a ContentID-only key collapses them and delivers only one. The interaction with filtering is specified in BRC-148 §Frame carriage.

### Submission record (ingress)

A publisher submits the pair *(topic list, BEEF object)* as one record:

```
Offset  Size  Field
  0       2   Tag         (uint16 BE = 0xBEEF — record discriminator on shared ports)
  2       1   RecordVer   (0x01)
  3       1   TopicCount  (1..15)
  4       …   Topics      (TopicCount × { uint8 NameLen (1..64) ∥ NameLen bytes UTF-8 topic name })
  …       4   ObjectLen   (uint32 BE, ≥ 1; operators bound the maximum accepted size)
  …       …   Object      (the BEEF object; leading marker per the version-word table)
```

For each named topic the ingress derives `TopicID = SHA-256(name)`, computes the object's ContentID once, and emits one `FrameVer 0x09` frame to that topic's group — sibling emissions share a ContentID, and ingress duplicate suppression keys on the (ContentID, TopicID) pair per BRC-148. A record whose object does not lead with a marker from the version-word table, whose lengths violate the bounds above, or whose object exceeds the operator's size bound MUST be rejected. A malformed record desynchronises its stream; the receiver MUST close the connection.

#### Fan-out admission

`TopicCount` ranges 1..15, but a multi-topic record fans one object out to that many full-object frames — an up-to-15× amplification of one submission, attacker-declarable and free on an anonymous path. Admission is therefore conditioned on the ingress identity path: open / public / anonymous ingress MUST admit `TopicCount == 1` and MUST reject a public record with `TopicCount > 1`; multi-topic (`TopicCount > 1`) is an authenticated-ingress capability, where the operator accounts the fan-out (typically the first N topics free and additional topics charged at the operator's delivery rate; N is operator policy). This is an admission policy over an unchanged wire grammar — the 92-byte frame and record layout are identical on both paths.

#### Detection on shared ports

Single-topic BEEF submission records MAY ride the open transaction port (multi-topic records require the authenticated path, above) alongside the existing grammars, distinguished by leading bytes — network magic `0xE3E1F3E8` selects a framed datagram, the `0xBEEF` tag selects a submission record, and anything else is a bare transaction ([BRC-12](/transactions/0012) raw / [BRC-30](/transactions/0030) Extended Format, whose little-endian version byte at offset 1 is `0x00` — the three forms cannot collide). Over TCP the grammar is committed once per connection; over UDP it is detected per datagram, and a record must fit one datagram (larger objects submit over a stream transport). Operators MAY additionally expose a dedicated single-record-grammar port for flow separation; it carries no additional admission semantics.

### Delivery record (egress)

A delivery edge streaming BEEF objects to a subscriber over a unicast lane emits, per delivered object:

```
Offset  Size  Field
  0      32   TopicID    (the matched topic's identifier)
 32       4   ObjectLen  (uint32 BE, ≥ 1)
 36       …   Object     (the BEEF object verbatim)
```

The record carries the TopicID, not the topic name — the subscriber elected its topics and maps identifiers back locally. Subscribers taking whole `FrameVer 0x09` frames instead of a stripped lane need no record; the frame already carries both identifiers.

## References

* [BRC-12: Raw Transaction Format](/transactions/0012) — bare-transaction grammar sharing the open port
* [BRC-30: Extended Format Transaction](/transactions/0030) — bare-transaction grammar sharing the open port
* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062) — BEEF encoding and version word
* [BRC-95: Atomic BEEF Transactions](/transactions/0095) — explicit-subject BEEF encoding
* [BRC-96: BEEF V2 Txid Only Extension](/transactions/0096) — TXID-only BEEF encoding
* [BRC-124: Multicast Transaction Frame Format](/transactions/0124) — the 92-byte header layout this frame reuses
* [BRC-126: Multicast Transaction NACK Retransmission Protocol](/transactions/0126) — retransmission machinery operating on HashKey/SeqNum
* [BRC-130: Multicast Transaction Frame Fragmentation](/transactions/0130) — fragmentation for large objects
* BRC-143 / BRC-144 (Multicast Subtree Data / Block Push Frame Formats) — push-lane format precedent (in submission)
* [BRC-148: Multicast Shard Domain Partitioning and the BEEF Object Plane](/transactions/0148) — the plane, sharding, filtering, and coordination this format serves


# Outpoint BEEF

David Case (<david.case@shruggr.cloud>)

## Abstract

**Outpoint BEEF** is [Atomic BEEF](/transactions/0095) with a **subject outpoint** instead of a subject transaction id. Encoding, inclusion, and validation follow BRC-95 except as in the table below. The body is a standard [BRC-62](/transactions/0062) / [BRC-96](/transactions/0096) BEEF. Use for proofs scoped to a specific output (e.g. 1Sat tip provenance in [BRC-150](/tokens/0150)).

## Specification

|                  | Atomic BEEF ([BRC-95](/transactions/0095))   | Outpoint BEEF (this BRC)                                                                                        |
| ---------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Prefix (4 bytes) | `0x01010101`                                 | `0x16a7beef`                                                                                                    |
| Subject          | TXID (32 bytes)                              | TXID (32 bytes) + vout (uint32 LE, 4 bytes)                                                                     |
| Body             | BEEF                                         | BEEF (same rules)                                                                                               |
| Graph            | Ancestors of the subject **transaction**     | Bundle of transaction proofs needed for the subject **outpoint** (profile-defined; not limited to tx ancestors) |
| Fail if          | Subject tx missing, or unrelated txs present | Any transaction required for the proof is missing                                                               |

```
16a7beef                                                 // prefix
2222222222222222222222222222222222222222222222222222222222222222 // subject TXID
00000000                                                 // subject vout (uint32 LE)
0200beef                                                 // BEEF V2 (example)
...
```

## Notes (informative)

* The embedded BEEF MAY include transactions that are not ancestors of the subject tx (e.g. funding spent beside a 1Sat tip). That is intentional for profiles such as [BRC-150](/tokens/0150).
* Completeness is profile-defined. Outpoint BEEF alone does not define sat ordering or inscription rules — see [BRC-159](/tokens/0159) / [BRC-160](/tokens/0160) / [BRC-150](/tokens/0150).
* Bundle size grows with whatever the profile requires. For 1Sat tip→origin remittance, prefer extending a prior package and omitting oversized bags rather than dropping required input sources; see [BRC-150](/tokens/0150) (*Scalability*).
* Do not nest this envelope inside BRC-150 `beefB64`. Basket remittance stays the JSON object; Outpoint BEEF is a parallel binary wire format (gateways, other transports).


# Bitcoin Script Binary, Hex and ASM Formats

Ty Everett (<ty@projectbabbage.com>)

## Abstract

Bitcoin uses scripts to control transactions, and three of the most common ways to represent a script are binary, hexadecimal, and ASM. This standard aims to provide a detailed description of these formats to facilitate their use in BSV transactions.

## Motivation

The use of scripts in Bitcoin SV transactions allows for greater flexibility and customization of transaction rules. To properly utilize these scripts, it is important to understand the different formats they can be written in. This standard aims to provide a clear and detailed description of the binary, hexadecimal, and ASM formats used in BSV scripts to facilitate their use in BSV transactions.

## Specification

We specify three commonly-used Bitcoin script formats as follows:

### Binary Format

The binary format is a series of bytes that represent the script. Each opcode in the script is represented by a single byte, with any arguments or data being represented by subsequent bytes. The script is executed sequentially, with each opcode performing a specific action on the stack. Bitcoin script uses [reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation).

```
<Buffer 76 a9 14 57 f5 0e 13 db 98 a8 8e 9e 1b 8f 03 f6 5a 80 9d 6e 9c 9c ba 88 ac>
```

### Hexadecimal Format

The hexadecimal format is a string of hexadecimal digits that represents the binary script. Each byte in the script is represented by two hexadecimal digits. The following is an example of a script in hexadecimal format:

```
76a91457f50e13db98a88e9e1b8f03f65a809d6e9c9cba88ac
```

### ASM Format

> Not to be confused with the full [BRC-15 Assembly Language](/scripts/0015).

The ASM format is a human-readable format that represents the script in a more intuitive way. Each opcode and its arguments are represented by a single string. For example, the following script in ASM format checks that a given public key has signed the transaction:

```
OP_DUP OP_HASH160 57f50e13db98a88e9e1b8f03f65a809d6e9c9cba OP_EQUALVERIFY OP_CHECKSIG
```

### SASM Format

The SASM format is a shortening of the above ASM format which uses lowercase and removes the OP\_ prefix from op codes to aid in quick typing and clearer readability. For example, the above ASM script is repeated now in SASM:

```
dup hash160 57f50e13db98a88e9e1b8f03f65a809d6e9c9cba equalverify checksig
```

## Implementations

BSV scripts can be written in any of the above formats and included in a transaction as a locking script or an unlocking script. To create a script in binary format, the opcodes can be written in hexadecimal and then converted to binary. To create a script in hexadecimal format, the binary script can be converted to hexadecimal. To create a script in the ASM format, the opcodes and arguments can be written as a string in the appropriate format.

There are various libraries and tools available for creating and working with BSV scripts in these formats, such as the `bitcoin-cli` command-line tool for the node software. These tools allow for the creation and manipulation of BSV scripts in any of the supported formats.


# Bitcoin Script Assembly Language

Ty Everett (<ty@projectbabbage.com>)

## Abstract

Bitcoin script is a programming language used in Bitcoin transactions to control the spending of coins. While the Bitcoin script opcodes are machine-readable, they are not easily human-readable. We define an assembly language that provides a human-readable format for expressing Bitcoin script opcodes, making it easier for developers to understand and write Bitcoin scripts. This specification defines the rules for expressing Bitcoin script opcodes in assembly format, including indentation, comments, expressing text strings and numbers, templating and other guidelines.

## Motivation

Bitcoin script is a powerful tool for controlling the spending of coins, but its opcodes can be difficult for developers to read and understand. By providing a human-readable assembly format for expressing Bitcoin script opcodes, developers can more easily write and debug Bitcoin scripts. This specification aims to standardize the assembly format for Bitcoin script opcodes, making it easier for developers to work with Bitcoin transactions.

This specification is intended to start the discussion and propose an initial format for representing Bitcoin scripts in an assembly language. Iterative improvement through future BRCs will help improve this standard.

## Specification

We specify an assembly language for Bitcoin script programs as follows:

### Syntax

* Opcodes should be expressed in uppercase and without `OP_` prefixing, e.g. `CHECKSIG`.
* Data to push on the stack is represented by strings, hex values, or template variables.
* Whitespace should be used for indentation and to separate tokens.
* Comments should start with the `#` character and extend to the end of the line.
* `.unlock` and `.lock` can be used to denote the boundary between an unlocking script and its corresponding lock.

For example:

```s
.unlock
  <sig> # The signature used to unlock the script
  <key> # The public key that unlocks the script

.lock
  DUP HASH160 # Duplicate the key and take its hash
  1a98d1ea5702a518b8c4ad9bb736bf34fa9e7291 EQUALVERIFY # Check the hashes are equal
  CHECKSIG # Check that the signature from this key is valid
```

### Data types

* Numeric values should be expressed as their corresponding opcodes, or in hexadecimal format.
* String values should be enclosed in single quotes.
* Hex values should be expressed in lowercase, without `0x` prefixing.
* Template variables are placed in angle brackets like `<hash>`

For example:

```s
OVER 3 SPLIT NIP TRUE SPLIT SWAP SPLIT DROP HASH160 <hash> EQUALVERIFY CHECKSIG
```

### Flow control

Conditional statements should use the `IF` opcode, followed by the conditional expression and the `ELSE` or `ENDIF` opcodes.

For example:

```s
2 3 ADD 5 EQUAL IF # if 2 + 3 = 5
  'yes' RETURN     # Return 'yes'
ELSE               # Otherwise
  'no' RETURN      # Return 'no'
ENDIF              # End
```

### File Extension

We specify that `.basm` files can be used to represent Bitcoin assembly language programs.

## Implementation

The process of assembling programs written in this assembly language comprises:

* Obtaining the values for template variables, either programmatically or by seeking user input
* Substituting template variable placeholders for the actual values
* Removing comments
* Computing and adding the correct PUSHDATA opcodes for adding string and hexadecimal values to the stack
* Converting all string values to hexadecimal
* Substituting all opcode names for their hexadecimal coded values
* Removing `.unlock` and `.lock` annotations if present
* Removing all whitespace to arrive at the fully-assembled program


# Pay to Public Key Hash

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The Pay-to-Public-Key-Hash (P2PKH) output script is a commonly used script in the Bitcoin network that allows users to send funds to a specific Bitcoin address. This standard provides a detailed specification of the P2PKH output script, providing detail on the operations performed, and giving examples for serialized Bitcoin addresses.

## Motivation

The P2PKH output script is used to ensure that only the owner of a specific Bitcoin address can spend the funds sent to that address. This is achieved by requiring the spender to provide a valid signature that corresponds to the public key associated with the address. The P2PKH output script is widely used in the Bitcoin network and is the most common output script used in transactions.

## Specification

The P2PKH output script is defined as follows:

```
OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
```

Where:

* `OP_DUP` duplicates the top stack item.
* `OP_HASH160` computes the RIPEMD160 hash of the SHA256 hash of the top stack item.
* `<PubKeyHash>` is the RIPEMD160 hash of the SHA256 hash of the public key associated with the Bitcoin address.
* `OP_EQUALVERIFY` checks if the top two stack items are equal and removes them if they are.
* `OP_CHECKSIG` verifies that the signature provided by the spender is valid for the public key associated with the address.

## Serialization Format (Base58)

The P2PKH output script is serialized using Base58 encoding. The serialized format is as follows:

```
[Version Byte][PubKeyHash][Checksum]
```

Where:

* `[Version Byte]` is a single byte that identifies the network and the script type. The value of the version byte is `0x00` for the mainnet and `0x6F` for the testnet.
* `[PubKeyHash]` is a 20-byte RIPEMD160 hash of the SHA256 hash of the public key associated with the Bitcoin address.
* `[Checksum]` is a 4-byte checksum computed using the first four bytes of the double-SHA256 hash of the serialized script.

### Examples

P2PKH Bitcoin addresses for mainnet always start with a `1` because the `[Version Byte]` is `0x00`. On testnet, they will always start with either `m` or, less often, `n`, because the `[Version Byte]` is `0x6F`.

Mainnet example P2PKH address: `1PyWzkfKrq1kakvLTeaCdAL8y8UJAcZAqU`

Testnet example P2PKH address: `mineSVDRCrSg2gzBRsY4Swb5QHFgdnGkis`

## How it Works

When a user creates a new Bitcoin address, a public key and a corresponding private key are generated. The public key is then hashed, first with SHA256 and then the result is hashed again using the RIPEMD160 algorithm to obtain a 20-byte hash value, which is the PubKeyHash used in the P2PKH output script.

To send funds to a P2PKH address, the sender creates a transaction that includes an output with the P2PKH output script and the address's PubKeyHash. To spend the funds, the recipient must provide a valid signature that corresponds to the public key associated with the address. The signature is verified using the `OP_CHECKSIG` opcode in the output script.

## Implementations

The P2PKH output script is implemented in most Bitcoin wallets and is widely used in the Bitcoin network. There are also many libraries available that provide functions for creating and validating P2PKH transactions.


# Pay to R Puzzle Hash

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The R-puzzle technique is a powerful cryptographic tool that can be used to create Bitcoin output scripts that can be unlocked by anyone who is able to solve for a value k, without revealing k to the world. The technique relies on the fact that in an ECDSA digital signature, the R component is computed by multiplying a generator point on the elliptic curve with this k-value. By constructing a Bitcoin output script that requires knowledge of this value, it is possible to create puzzles that can be solved by anyone who is able to compute the correct value for k. The R-puzzle technique is flexible and can be used for a variety of applications, including those that benefit when the solver does not reveal their solution to the world, only proving knowledge of it. R-puzzles also provide a way to create Bitcoin output scripts that can be unlocked without requiring knowledge of a specific private key, while still maintaining a high level of security and cryptographic strength. By serializing short-hand script templates in a standard way, implementers can maintain ecosystem-wide compatibility.

## Motivation

The creation of puzzles that can be unlocked by anyone who is able to solve a complex problem has become increasingly popular in recent years, particularly in the field of distributed computing. However, it is important to ensure that these puzzles are designed in such a way that they maintain a high level of security and cryptographic strength, while still allowing anyone to solve them without revealing any sensitive information.

To achieve this, it is valuable to create a script template that enables the solver to prove knowledge over a value, k, without revealing the k-value to the world. This provides a way to create puzzles that are secure and difficult to solve, while still allowing anyone to claim the solution and the associated rewards. In addition, it is desirable to employ a script template that does not require the creator of the puzzle to know which key will be used by the solver in claiming the solution, at the time the puzzle was created. Finally, there is also a need to standardize the way these scripts are expressed and exchanged between users of the Bitcoin network, to enable greater cross-compatibility and wider adoption.

## Specification

We specify the following Bitcoin script template for R-puzzles:

```s
.unlock
  <ECDSA sig>              # This is the first stack element and represents the ECDSA digital signature provided by the 
                           # solver. It must have an R component that, when hashed with SHA256 and then RIPEMD160, 
                           # produces the same value as the <hash> specified in the locking script. The S component can 
                           # be computed using any key of the solver's choosing.
  <key>                    # This is the second stack element and represents the private key that was used to compute the S component of the 
                           # signature. It can be any key of the solver's choosing, as long as it produces a valid S component for the signature that 
                           # was provided as the first stack element.

.lock
  OVER                     # Duplicates the second stack element (the signature) so that it can be used later in the script. Now the stack has 
                           # three elements, <signature> <key> <signature> and we can work with the top <signature> without bothering the bottom one.
  3 SPLIT                  #
  NIP                      #
  TRUE SPLIT               # This section of the script picks out the R-value from the signature.
  SWAP                     #
  SPLIT                    #
  DROP                     # 
  HASH160                  # Here, we hash the R-value with SHA256 and then again with RIPEMD160 (the combined operation is HASH160).
  <hash> EQUALVERIFY       # The hash we calculated in the previous step is compared with <hash> and if they are not equal then the script fails.
  CHECKSIG                 # Finally, the ECDSA signature is checked.
```

We specify that the same private key and k-value must **NEVER EVER** be used, and that steps **MUST** be taken to prevent the same private key and k-value from ever being used to sign different messages in ECDSA. Failure to follow this provision will result in **leakage of private keys and k-values**.

## Serialization Format (Base58)

We specify that the P2RPH output script is serialized using Base58 encoding. The serialized format is as follows:

```
[Version Bytes][RPuzzleHash][Checksum]
```

Where:

* `[Version Bytes]` is a prefix that identifies the network and the script type. The value of the version byte is `0x0e00` for the mainnet and `0x1c00` for the testnet.
* `[PubKeyHash]` is a 20-byte RIPEMD160 hash of the SHA256 hash of the R-value.
* `[Checksum]` is a 4-byte checksum computed using the first four bytes of the double-SHA256 hash of the serialized script.

### Examples

P2RPH Bitcoin addresses for mainnet always start with an `R` because the `[Version Bytes]` we selected are `0x0e00`. On testnet, they will always start with lowercase `r` because the `[Version Bytes]` are `0x1c00`.

Mainnet example P2RPH address: `RsQphNVccTzET8zwMNESLmTeJ4cJYxyc9cx`

Testnet example P2RPH address: `rjQPANNiecDK1a6jedyCsQ6UND4SnK6rJNe`

## How it Works

The locking script is the script that is attached to the Bitcoin output that is being locked with the R-puzzle. When a Bitcoin transaction is created that spends this output, the unlocking script must provide the correct input to satisfy this locking script.

The locking script essentially creates a puzzle that can only be unlocked by providing an ECDSA digital signature that satisfies certain conditions. Specifically, the signature must have an R component that, when hashed with SHA256 and then RIPEMD160, produces the same value as the \<hash> specified in the script. The S component of the signature can be computed using any key of the solver's choosing.

The unlocking script essentially satisfies the conditions set out by the locking script by providing a valid ECDSA digital signature with an R component that matches the hash specified in the locking script, and an S component that is computed using the solver's chosen private key.

The locking and unlocking scripts for the R-puzzle comprise a powerful and flexible tool for creating puzzles that can be solved by anyone who is able to compute the correct value for the k parameter used in the ECDSA digital signature. By using this technique, it is possible to create Bitcoin outputs that can be unlocked by anyone who is able to solve the puzzle, without requiring knowledge of a specific private key.

## Security Considerations

It is crucial to emphasize the importance of never using the same private key and k-value for different messages in ECDSA. Doing so can compromise the security of the digital signatures and result in the leakage of private keys and k-values, which can lead to the loss of funds or assets associated with those signatures.

Therefore, it is essential to take steps to ensure that private keys and k-values are never reused. One way to achieve this is by using a secure random number generator to generate new values for each signature. It is also important to store private keys securely and to use secure communication channels when transmitting them.

To further emphasize the importance of this provision, it is advisable to include clear warnings and instructions in any documentation or educational material related to the R-puzzle technique. This will help ensure that users understand the potential risks of reusing private keys and k-values and take the necessary precautions to prevent such incidents from occurring.

## Implementations

The first-known implementation for R-puzzles was created by [Dean Little](https://github.com/deanmlittle/rpuzzle).


# Pay to False Return

## Abstract

The OP\_FALSE OP\_RETURN script template is a method for storing data on the Bitcoin SV blockchain. It creates a non-spendable output and appends the desired data to the end of the script for storage. This standard aims to define the rules and best practices for using this script template.

## Motivation

The low transaction fees and high capacity of the Bitcoin SV network make it an attractive option for storing data on the blockchain. The OP\_FALSE OP\_RETURN script template is a widely used method for achieving this goal due to its simplicity and ease-of-use.

## Specification

To use the OP\_FALSE OP\_RETURN script template, an output must be included in a Bitcoin transaction with a locking script comprising OP\_FALSE followed by OP\_RETURN, followed by the data to store in the script. The data can be pushed into one or multiple stack elements after the OP\_RETURN opcode.

For example:

```s
.lock
  FALSE RETURN
  <data1>
  <data2>
  <data3>
```

## Implementations

Several implementations and protocols make use of the OP\_RETURN script template, including the [RUN protocol](https://run.network/docs/#introduction) and [Bitcoin OP\_RETURN Bytecode (BOB)](https://medium.com/@_unwriter/hello-bob-94701d278afb).

## Limitations

The main limitation of using OP\_FALSE OP\_RETURN is the non-spendability of these outputs and their ability to be pruned by miners. They are records rather than tokens. Artefacts predominantly as a proof of existence of data at a certain time - timestamped by the Bitcoin system as a hash with a merkleproof to a block, even if miners themselves prune the data.


# Pay to True Return

Ty Everett (<ty@projectbabbage.com>)

## Abstract

In one view of Bitcoin, only spendable output scripts with satoshis attached constitute valid Bitcoin tokens, because Bitcoin tokens are UTXOs. Since traditional [BRC-18](/scripts/0018) scripts are not compatible with this view, we propose a methodology for creating OP\_RETURN scripts that are spendable and contain satoshis. This provides a way for software to automatically convert non-compliant OP\_RETURNs into proper Bitcoin tokens while encouraging developers to consider the spendability constraints that govern their tokens.

## Motivation

Bitcoin is a token system, and as such, every output should be spendable and have value in satoshis. This fundamental principle ensures that Bitcoin remains a secure and reliable system that can be trusted by users around the world. However, traditional [BRC-18](/scripts/0018) scripts, employing the OP\_FALSE OP\_RETURN pattern, are not compatible with this view.

OP\_FALSE OP\_RETURN outputs are non-spendable and carry no value, which means they cannot be considered tokens under this definition. This creates a problem for developers who want to store data on the blockchain using OP\_RETURN, as it goes against the basic principles of the Bitcoin token system.

To address this issue, we propose a new methodology for creating OP\_RETURN scripts that are spendable and contain satoshis. This provides a way for software to automatically convert non-compliant OP\_RETURNs into proper Bitcoin tokens while encouraging developers to consider the spendability constraints that govern their tokens.

By adding the cost of one single satoshi and the fully-open spendability constraints to OP\_RETURN outputs, developers will be encouraged to consider the actual constraints that govern their tokens. This will drive them towards script patterns that protect what their tokens represent and promote a more robust and secure token system for Bitcoin.

This standard offers a solution that maintains the fundamental principles of the Bitcoin token system while providing a practical way for developers to store data on the blockchain using OP\_RETURN. By adopting this methodology, we can ensure that Bitcoin remains a secure and reliable system that can be trusted by users worldwide.

## Specification

We specify the same script template as [BRC-18](/scripts/0018), except that instead of OP\_FALSE being the first opcode, it is replaced with OP\_TRUE. We also stipulate that at least one satoshi must be locked in the output.

For example:

```s
.lock
  TRUE RETURN
  <data1>
  <data2>
  <data3>
```

## Risks and Limitations

Because [BRC-18](/scripts/0018) implementers did not define the constraints under which their Bitcoin tokens would be unlocked, we have defined a fully-open system in which anyone can redeem these tokens. If implementers see this as a problem, they should consider being cognisant of the mechanisms that define the spendability constraints of their Bitcoin outputs.


# Push TX

## Abstract

Push TX is a technique that enables users to enforce and access transactional states and conditions within Bitcoin script using ECDSA signature messages. It allows for the enforcement of several transaction elements, such as the number of inputs, nSequence values, input and output values, and script conditions, as well as the ability to specify the script into which tokens will be spent. The Push TX algorithm works by pushing the transaction pre-image message that generates the signature onto the stack as part of the input's unlocking script. This message can be pushed as a single contiguous blob, multiple separate elements, or as a partial set, with the remaining elements of the message set via the output's locking script. The signature is then computed on-chain within the script and checked against the public key using OP\_CHECKSIG to ensure the current transaction is valid. This standard outlines the motivation and specifications for implementing the Push TX technique within Bitcoin script.

## Motivation

The Push TX technique offers several benefits to Bitcoin users and developers. Firstly, it allows for the enforcement of complex transactional states and conditions at the consensus layer, making it easier to create Turing complete machines within the Bitcoin ecosystem. This is achieved by enabling users to specify the script into which tokens will be spent, which can be used to enforce next state conditions. Additionally, it enables users to specify and enforce various transaction elements, such as input and output values, nSequence values, and the nLocktime condition. This improves the security and efficiency of Bitcoin transactions by ensuring that all transactional conditions are met and enforced at the consensus layer.

## Specification

The Push TX algorithm works as follows:

1. The user or process that is using the UTXO pushes the transaction pre-image message that generates the signature onto the stack as part of the unlocking script. This message can be pushed as a single contiguous blob, multiple separate elements, or as a partial set, with the remaining elements of the message set via the output's locking script.
2. The algorithm pushes the current transaction onto the stack.
3. The algorithm pushes a dummy private key onto the stack.
4. Using the transaction and private key pushed in steps 2 and 3, the algorithm generates an ECDSA signature on-chain within the script.
5. The algorithm pushes the public key derived from the private key onto the stack.
6. The algorithm uses OP\_CHECKSIG to check the signature against the public key, ensuring that the current transaction is valid.

The Push TX algorithm can be used to enforce several elements of the transaction, including but not limited to the number of inputs, nSequence values, input and output values, and script conditions, as well as the ability to specify the script into which tokens will be spent.

## Implementations

This has been [implemented in the sCrypt ecosystem](https://scryptdoc.readthedocs.io/en/latest/contracts.html#library-tx).

## Commentary

Bitcoin script is often misunderstood as being limited to the data provided in the locking and unlocking scripts. However, the Push TX technique allows for the inspection of the entire transaction within a contract itself, including all inputs and outputs. This opens up boundless possibilities for smart contracts on Bitcoin.

The ability to place arbitrary constraints on inputs and outputs within a contract means that a wide range of use cases can be implemented on Bitcoin. For example, contracts that enforce multi-signature transactions or time-based restrictions can be easily created using Push TX. Additionally, the technique enables the creation of more complex contracts that can enforce next state conditions, making it possible to create Turing complete machines within the Bitcoin ecosystem.

The high-level functionality of Push TX is relatively simple: it allows for the inspection and enforcement of transactional states and conditions within Bitcoin script. However, the implications of this functionality are far-reaching, as it enables the creation of a wide range of smart contracts and decentralized applications on the Bitcoin blockchain. This is important for Bitcoin because it expands its use cases and makes it a more versatile platform for developers and businesses looking to leverage blockchain technology.

## References

[nChain WP-1605](https://nchain.com/wp-content/uploads/2022/03/WP1605_PUSHTX-and-its-Building-Blocks.pdf)\
[Xiaohui Liu Article](https://xiaohuiliu.medium.com/op-push-tx-3d3d279174c1)


# Bare Multi-Signature

## Abstract

This BRC standard outlines the implementation and use of bare multi-signature (multi-sig) transaction output scripts within the Bitcoin SV digital asset ecosystem. By employing OP\_MULTISIG opcodes directly, this approach offers simplicity and ease of implementation while providing enhanced security and access control for transactions. However, it also highlights the trade-offs in terms of privacy for participants. The standard comprises a motivation section, detailing the benefits of bare multi-sig transactions; a specification section, explaining the structure of these transactions; an example section, demonstrating a 2-of-3 multi-signature locking and unlocking script; and a "how it works" section, delving into the fundamentals of bare multi-sig transactions and their role in the Bitcoin SV ecosystem.

## Motivation

Bare multi-sig transactions offer a simple and straightforward method to enable multiple parties to authorize a transaction in a decentralized manner. This approach is particularly useful for securing funds, enhancing trust between parties, and enabling flexible access control. Although bare multi-sig transactions come with certain privacy trade-offs, their ease of implementation and direct use of low-level Bitcoin scripting constructs make them a valuable option in the Bitcoin SV ecosystem.

## Specification

A bare multi-signature transaction output script adheres to the following structure:

```
<minimum_signatures> <pubkey1> <pubkey2> ... <pubkeyn> <maximum_signatures> OP_CHECKMULTISIG
```

* `minimum_signatures`: The minimum number of signatures required to unlock the funds (also known as the "M" value).
* `pubkey1, pubkey2, ..., pubkeyn`: The public keys involved in the multi-signature scheme.
* `maximum_signatures`: The maximum number of public keys (also known as the "N" value).
* `OP_CHECKMULTISIG`: The Bitcoin opcode that validates the provided signatures against the specified public keys.

## Example

The following example demonstrates a 2-of-3 multi-signature locking script:

Locking Script:

```
2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG
```

To spend the funds locked by this script, an unlocking script containing the required signatures must be provided:

```
OP_0 <signature1> <signature2>
```

In this example, the OP\_0 opcode is required due to a known bug in the original implementation of the OP\_CHECKMULTISIG opcode, which results in an extra item being consumed from the stack.

## How it Works

Bare multi-sig transactions work by requiring a minimum number of signatures from a given set of public keys to unlock the funds. When the locking script is executed, the OP\_CHECKMULTISIG opcode verifies if the provided signatures correspond to the specified public keys and meet the minimum signature requirement. If the validation succeeds, the funds are unlocked and can be spent in a new transaction.

While bare multi-sig transactions offer simplicity in implementation, they also expose the public keys and the multi-signature scheme directly in the transaction output script, which may reveal information about the participants and their relationships. Despite these privacy concerns, bare multi-sig remains an important tool for creating secure and flexible transactions within the Bitcoin SV digital asset ecosystem.


# Pay to Push Drop

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This standard provides a script template that enables data-rich tokens on the Bitcoin SV blockchain, while still allowing for the representation of transfers of ownership. By pushing arbitrary data into stack elements and subsequently dropping them, followed by adding a simple P2PK lock, this script template allows for the creation of tokens with metadata, which can be exchanged on overlay networks. This standard facilitates improved scalability by representing tokens as UTXOs, which can be easily modeled and used in graph structures.

## Motivation

The need for this standard arises from the growing demand for data-rich tokens in the ecosystem. Current tokenization methods such as OP\_RETURN lack output spendability and representation of ownership, making them unsuitable for many use cases. This standard allows developers to push arbitrary data onto the stack, while still enabling ownership transfer with a simple P2PK lock. This feature facilitates improved scalability by representing tokens as UTXOs, which can be more easily modeled and used in graph structures. Use-cases for these tokens are numerous, ranging from deeds to cars to metadata-rich digital assets. While it does not purport to solve every use-case, such as those requiring complicated spending constraints, this standard is a crucial step in the direction of enabling more sophisticated data-rich tokenization on the Bitcoin network.

## Specification

We specify that output scripts must first push data onto the stack, followed by using OP\_DROP and OP\_2DROP to drop the pushed data. Next, the public key of the owner of the token is pushed, followed by OP\_CHECKSIG. The unlocking script comprises a digital signature from the owner's public key, which facilitates spending of the token. Each token output must contain at least one satoshi. This standard does not define specific forms of tokens or specific requirements for higher-order overlays with validation rules for their transfer and conveyance.

### Example Output Script:

The following is an example output script for a token as defined by this standard:

```
<arbitrary data> <arbitrary data> <arbitrary data> OP_DROP OP_2DROP <public key> OP_CHECKSIG
```

## Implementations

This script template is implemented by the [`PushDrop` template](https://github.com/bsv-blockchain/ts-stack/blob/main/packages/sdk/src/script/templates/PushDrop.ts) in the maintained `@bsv/sdk` package.

## How it Works

This script template enables tokenization on the Bitcoin SV blockchain by providing a simple and effective way to create data-rich tokens that are still spendable and locked to their owners. This is achieved by pushing arbitrary data onto the stack, dropping it with OP\_DROP or OP\_2DROP, and then adding a simple P2PK lock with a public key and OP\_CHECKSIG.

When a token is created using this template, it is represented as an unspent transaction output (UTXO) that contains at least one satoshi. This UTXO can then be transferred to another user by creating a transaction that spends the UTXO and includes the owner's digital signature in the unlocking script.

The template facilitates tokenization by allowing developers to specify any number of stack elements with arbitrary data, representing tokens on overlays, while still enabling spending and ownership with simple locks. This makes it easier to model and use tokens in graph structures, improving scalability and enabling a wide range of use-cases.


# Bitcoin Script ASM Format

Freddie Honohan (<freddie@yenpoint.jp>)

## Abstract

This proposal introduces a standardised method of dealing with the ASM representation of Bitcoin Script across the ecosystem language libraries (Py-SDK, TS-SDK, Go-SDK, Teranode etc).

## Motivation

The purpose of this proposal is to provide a generalised standard in order for deterministic translation of Bitcoin Script to and from ASM for cross-language compatibility.

Currently, different implementations may produce inconsistent ASM representations for the same script. For example, the boolean value `false` might be rendered as `OP_0`, `OP_FALSE` depending on the library used. This lack of standardisation creates interoperability issues when scripts are shared across different tools and platforms.

## Specification

1. It is important that ASM format scripts operate correctly regardless of where they were produced or processed.
2. The Hexadecimal and Binary representations are defined in BRC-14.
3. Since ASM format is primarily used for human-readability, op-codes with multiple representations like the boolean pair `[OP_0, OP_1]` are to be represented as their english full-name counterpart e.g. `[OP_FALSE, OP_TRUE]` **Example:**

```
   Hex:  0x00 0x51
   ASM:  OP_FALSE OP_TRUE
   Not:  OP_0 OP_1
```

4. Other discrepancies were found and documented [here](https://docs.google.com/spreadsheets/d/106aSWsOeNayzXOU9tRmRIvSu2Cz21YZQ5uCrwMZg1WU/) and rules are set out below to deal with them.

## Implementation

1. The logic dealing with Bitcoin Script ASM is to be standardised across libraries and languages as described hereunder.
2. Op-Codes with several names must be parsed into the correct hex/binary byte despite the chosen ASM format name. **Example Input Parsing:**

```javascript
   // All of these should parse to 0x00
   parseASM("OP_0")      // ✓ valid
   parseASM("OP_FALSE")  // ✓ valid
   
   // Both should output the same hex
   parseASM("OP_0") === parseASM("OP_FALSE")  // true
```

3. However op-codes with several names must be output into the most human-readable format (e.g. `OP_0` will always output as `OP_FALSE`). **Example Output Serialization:**

```javascript
   // Input hex, always outputs human-readable form
   toASM(0x00)  // "OP_FALSE" (not "OP_0")
   toASM(0x51)  // "OP_TRUE"  (not "OP_1")
```

4. Full Op-Code to Hex Table With Statuses

| Index | Word                    | Hex       | Legacy   | Chronicles |
| ----- | ----------------------- | --------- | -------- | ---------- |
| 0     | OP\_FALSE               | 0x00      | ✅        | ✅          |
| 1-75  | OP\_PUSHDATA            | 0x01-0x4b | ✅        | ✅          |
| 76    | OP\_PUSHDATA1           | 0x4c      | ✅        | ✅          |
| 77    | OP\_PUSHDATA2           | 0x4d      | ✅        | ✅          |
| 78    | OP\_PUSHDATA4           | 0x4e      | ✅        | ✅          |
| 79    | OP\_1NEGATE             | 0x4f      | ✅        | ✅          |
| 80    | OP\_RESERVED            | 0x50      | Reserved | Reserved   |
| 81    | OP\_TRUE                | 0x51      | ✅        | ✅          |
| 82    | OP\_2                   | 0x52      | ✅        | ✅          |
| 83    | OP\_3                   | 0x53      | ✅        | ✅          |
| 84    | OP\_4                   | 0x54      | ✅        | ✅          |
| 85    | OP\_5                   | 0x55      | ✅        | ✅          |
| 86    | OP\_6                   | 0x56      | ✅        | ✅          |
| 87    | OP\_7                   | 0x57      | ✅        | ✅          |
| 88    | OP\_8                   | 0x58      | ✅        | ✅          |
| 89    | OP\_9                   | 0x59      | ✅        | ✅          |
| 90    | OP\_10                  | 0x5a      | ✅        | ✅          |
| 91    | OP\_11                  | 0x5b      | ✅        | ✅          |
| 92    | OP\_12                  | 0x5c      | ✅        | ✅          |
| 93    | OP\_13                  | 0x5d      | ✅        | ✅          |
| 94    | OP\_14                  | 0x5e      | ✅        | ✅          |
| 95    | OP\_15                  | 0x5f      | ✅        | ✅          |
| 96    | OP\_16                  | 0x60      | ✅        | ✅          |
| 97    | OP\_NOP                 | 0x61      | ✅        | ✅          |
| 98    | OP\_VER                 | 0x62      | ❌        | ✅          |
| 99    | OP\_IF                  | 0x63      | ✅        | ✅          |
| 100   | OP\_NOTIF               | 0x64      | ✅        | ✅          |
| 101   | OP\_VERIF               | 0x65      | ❌        | ✅          |
| 102   | OP\_VERNOTIF            | 0x66      | ❌        | ✅          |
| 103   | OP\_ELSE                | 0x67      | ✅        | ✅          |
| 104   | OP\_ENDIF               | 0x68      | ✅        | ✅          |
| 105   | OP\_VERIFY              | 0x69      | ✅        | ✅          |
| 106   | OP\_RETURN              | 0x6a      | ✅        | ✅          |
| 107   | OP\_TOALTSTACK          | 0x6b      | ✅        | ✅          |
| 108   | OP\_FROMALTSTACK        | 0x6c      | ✅        | ✅          |
| 109   | OP\_2DROP               | 0x6d      | ✅        | ✅          |
| 110   | OP\_2DUP                | 0x6e      | ✅        | ✅          |
| 111   | OP\_3DUP                | 0x6f      | ✅        | ✅          |
| 112   | OP\_2OVER               | 0x70      | ✅        | ✅          |
| 113   | OP\_2ROT                | 0x71      | ✅        | ✅          |
| 114   | OP\_2SWAP               | 0x72      | ✅        | ✅          |
| 115   | OP\_IFDUP               | 0x73      | ✅        | ✅          |
| 116   | OP\_DEPTH               | 0x74      | ✅        | ✅          |
| 117   | OP\_DROP                | 0x75      | ✅        | ✅          |
| 118   | OP\_DUP                 | 0x76      | ✅        | ✅          |
| 119   | OP\_NIP                 | 0x77      | ✅        | ✅          |
| 120   | OP\_OVER                | 0x78      | ✅        | ✅          |
| 121   | OP\_PICK                | 0x79      | ✅        | ✅          |
| 122   | OP\_ROLL                | 0x7a      | ✅        | ✅          |
| 123   | OP\_ROT                 | 0x7b      | ✅        | ✅          |
| 124   | OP\_SWAP                | 0x7c      | ✅        | ✅          |
| 125   | OP\_TUCK                | 0x7d      | ✅        | ✅          |
| 126   | OP\_CAT                 | 0x7e      | ✅        | ✅          |
| 127   | OP\_SPLIT               | 0x7f      | ✅        | ✅          |
| 128   | OP\_NUM2BIN             | 0x80      | ✅        | ✅          |
| 129   | OP\_BIN2NUM             | 0x81      | ✅        | ✅          |
| 130   | OP\_SIZE                | 0x82      | ✅        | ✅          |
| 131   | OP\_INVERT              | 0x83      | ✅        | ✅          |
| 132   | OP\_AND                 | 0x84      | ✅        | ✅          |
| 133   | OP\_OR                  | 0x85      | ✅        | ✅          |
| 134   | OP\_XOR                 | 0x86      | ✅        | ✅          |
| 135   | OP\_EQUAL               | 0x87      | ✅        | ✅          |
| 136   | OP\_EQUALVERIFY         | 0x88      | ✅        | ✅          |
| 137   | OP\_RESERVED1           | 0x89      | Reserved | Reserved   |
| 138   | OP\_RESERVED2           | 0x8a      | Reserved | Reserved   |
| 139   | OP\_1ADD                | 0x8b      | ✅        | ✅          |
| 140   | OP\_1SUB                | 0x8c      | ✅        | ✅          |
| 141   | OP\_2MUL                | 0x8d      | ❌        | ✅          |
| 142   | OP\_2DIV                | 0x8e      | ❌        | ✅          |
| 143   | OP\_NEGATE              | 0x8f      | ✅        | ✅          |
| 144   | OP\_ABS                 | 0x90      | ✅        | ✅          |
| 145   | OP\_NOT                 | 0x91      | ✅        | ✅          |
| 146   | OP\_0NOTEQUAL           | 0x92      | ✅        | ✅          |
| 147   | OP\_ADD                 | 0x93      | ✅        | ✅          |
| 148   | OP\_SUB                 | 0x94      | ✅        | ✅          |
| 149   | OP\_MUL                 | 0x95      | ✅        | ✅          |
| 150   | OP\_DIV                 | 0x96      | ✅        | ✅          |
| 151   | OP\_MOD                 | 0x97      | ✅        | ✅          |
| 152   | OP\_LSHIFT              | 0x98      | ✅        | ✅          |
| 153   | OP\_RSHIFT              | 0x99      | ✅        | ✅          |
| 154   | OP\_BOOLAND             | 0x9a      | ✅        | ✅          |
| 155   | OP\_BOOLOR              | 0x9b      | ✅        | ✅          |
| 156   | OP\_NUMEQUAL            | 0x9c      | ✅        | ✅          |
| 157   | OP\_NUMEQUALVERIFY      | 0x9d      | ✅        | ✅          |
| 158   | OP\_NUMNOTEQUAL         | 0x9e      | ✅        | ✅          |
| 159   | OP\_LESSTHAN            | 0x9f      | ✅        | ✅          |
| 160   | OP\_GREATERTHAN         | 0xa0      | ✅        | ✅          |
| 161   | OP\_LESSTHANOREQUAL     | 0xa1      | ✅        | ✅          |
| 162   | OP\_GREATERTHANOREQUAL  | 0xa2      | ✅        | ✅          |
| 163   | OP\_MIN                 | 0xa3      | ✅        | ✅          |
| 164   | OP\_MAX                 | 0xa4      | ✅        | ✅          |
| 165   | OP\_WITHIN              | 0xa5      | ✅        | ✅          |
| 166   | OP\_RIPEMD160           | 0xa6      | ✅        | ✅          |
| 167   | OP\_SHA1                | 0xa7      | ✅        | ✅          |
| 168   | OP\_SHA256              | 0xa8      | ✅        | ✅          |
| 169   | OP\_HASH160             | 0xa9      | ✅        | ✅          |
| 170   | OP\_HASH256             | 0xaa      | ✅        | ✅          |
| 171   | OP\_CODESEPARATOR       | 0xab      | ✅        | ✅          |
| 172   | OP\_CHECKSIG            | 0xac      | ✅        | ✅          |
| 173   | OP\_CHECKSIGVERIFY      | 0xad      | ✅        | ✅          |
| 174   | OP\_CHECKMULTISIG       | 0xae      | ✅        | ✅          |
| 175   | OP\_CHECKMULTISIGVERIFY | 0xaf      | ✅        | ✅          |
| 176   | OP\_NOP1                | 0xb0      | ✅        | ✅          |
| 177   | OP\_NOP2                | 0xb1      | ✅        | ✅          |
| 178   | OP\_NOP3                | 0xb2      | ✅        | ✅          |
| 179   | OP\_SUBSTR              | 0xb3      | ❌        | ✅          |
| 180   | OP\_LEFT                | 0xb4      | ❌        | ✅          |
| 181   | OP\_RIGHT               | 0xb5      | ❌        | ✅          |
| 182   | OP\_NOP4                | 0xb6      | ✅        | ✅          |
| 183   | OP\_NOP5                | 0xb7      | ✅        | ✅          |
| 184   | OP\_NOP6                | 0xb8      | ✅        | ✅          |
| 185   | OP\_NOP7                | 0xb9      | ✅        | ✅          |
| 186   | OP\_NOP8                | 0xba      | ✅        | ✅          |
| 187   | OP\_NOP9                | 0xbb      | ✅        | ✅          |
| 188   | OP\_NOP10               | 0xbc      | ✅        | ✅          |
| 253   | OP\_PUBKEYHASH          | 0xfd      | Pseudo   | Pseudo     |
| 254   | OP\_PUBKEY              | 0xfe      | Pseudo   | Pseudo     |
| 255   | OP\_INVALIDOPCODE       | 0xff      | Invalid  | Invalid    |

## References

* [BRC-14](/scripts/0014): Bitcoin Script Binary and Hex Formats - Ty Everett (<ty@projectbabbage.com>)


# There is no BRC-20

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This document serves to clarify that there is no BRC-20 standard for tokenization on Bitcoin SV. While Ethereum has the ERC-20 standard for account-based tokens, there is no equivalent for UTXO-based systems such as Bitcoin SV. In order to avoid confusion and prevent people from conflating ERC-20 with BRC-20, it is necessary to clarify that **BRC-20 does not exist on Bitcoin SV**. Instead, proposals for tokenization should be judged on their own merits. Being associated with ERC-20 will not make any specific tokenization proposal better, and there is not a desire to confer greater legitimacy to one proposal or another.

## Motivation

As the Bitcoin ecosystem continues to mature and grow, there is an increasing desire to create standards for tokenization. However, it is important to recognize that the architecture of Bitcoin is fundamentally different from that of Ethereum. While Ethereum is an account-based system, Bitcoin is a UTXO-based system. This means that there is no direct equivalent for something like ERC-20, which defines account-based tokens on top of Ethereum. The goal of this document is to prevent unnecessary confusion.

## Arguments

* **UTXO-based systems are fundamentally different from account-based systems.** While Ethereum has the ERC-20 standard for account-based tokens, there is no equivalent for UTXO-based systems such as Bitcoin. Tokenization will necessarily require a different approach, one which should not be conflated.
* **Avoiding confusion is essential.** If there were a BRC-20 standard for Bitcoin SV, it would likely be confused with the ERC-20 standard on Ethereum. This could lead to a lack of clarity and understanding among developers and users, which could ultimately harm the growth and adoption of tokenization in both Bitcoin and Ethereum.
* **Proposals for tokenization should be judged on their own merits.** The absence of a BRC-20 standard on Bitcoin SV does not mean that tokenization is not possible. Instead, proposals for tokenization should be evaluated based on their own technical merits and practical considerations. A proposal called BRC-20 will not be better on account of its name.

In summary, there is **no BRC-20 standard for tokenization on Bitcoin SV.** While there may be a desire to create such a standard, it is important to recognize that the architecture of Bitcoin is fundamentally different from that of Ethereum. Instead of trying to emulate the ERC-20 standard, proposals for tokenization should be evaluated based on their own technical merits and practical considerations. This approach will ultimately lead to a more mature and professional ecosystem for tokenization.

As with any other BRC, tokenization proposals will be assigned sequential numbering.


# Definition of UTXOs as Bitcoin Tokens

Ty Everett (<ty@projectbabbage.com>)

## Abstract:

This document proposes an examination of Unspent Transaction Outputs (UTXOs) as the central framework of tokenization in Bitcoin, justifying their usage based on their inherent simplicity, efficiency, and secure transfer processes. Through UTXOs, Bitcoin allows the delineation of rules governing the transfer of valuable tokens via scripting. In contrast to alternative tokenization proposals, UTXOs render the need for comprehensive, trusted indexing and chain scanning systems obsolete. Instead, transaction parties require only to validate transactions pertinent to them, employing miners to mitigate double-spend attempts, thus achieving tokenization of assets at scale.

## Background:

**Tokenization**, in the realm of digital assets, represents the process of substituting valuable, real-world assets with digital tokens on a blockchain. Within the context of Bitcoin, tokenization predominantly revolves around Unspent Transaction Outputs (UTXOs).

**Unspent Transaction Outputs (UTXOs)** are remnants of Bitcoin transactions yet to be spent or used as inputs for newer transactions. Each UTXO in Bitcoin can be seen as a distinct token. Unlike other tokenization methods, UTXOs are directly transferred from sender to recipient, the legitimacy of which can be independently verified, thus significantly reducing computational and time complexities.

**Scripts** in Bitcoin define the conditions under which UTXOs can be spent. These conditions form the essence of token transfer rules, ensuring secure transactions of digital tokens.

**Simplified Payment Verification (SPV)** is a method used in Bitcoin for lightweight clients to verify transactions without requiring the entire blockchain's download. The [BRC-9](/transactions/0009) process for SPV enables recipients to verify transfers quickly.

## Argument: Benefits of UTXOs as the Fundamental Unit of Tokenization:

UTXOs offer a unique advantage as the foundational unit of tokenization in Bitcoin due to their inherent characteristics and the robustness they bring to the transaction system:

1. Decentralization and Trust Minimization: With UTXOs, parties need only to validate transactions that concern them, eliminating the necessity for a trusted intermediary to index and scan every transaction on the blockchain. This process enhances the decentralized nature of Bitcoin and minimizes trust requirements.
2. Scalability: By treating UTXOs as the base unit of tokenization, we can facilitate efficient asset tokenization at scale. With direct transfers, the UTXO model bypasses complex indexing systems, thus reducing computational overhead and increasing transaction speed.
3. Double-Spend Protection: Miners play an integral role in UTXO transactions by preventing double-spending. As each UTXO can only be spent once, miners verify and confirm that no UTXO is duplicated or spent twice, preserving the security of the blockchain.
4. Simplified Verification: The [BRC-9](/transactions/0009) process for SPV enables easy verification of transfers without needing the full blockchain data. In UTXO-based transactions, the recipient can independently validate the transaction, making it efficient and user-friendly.

In summary, the UTXO model provides an efficient and scalable framework for tokenization in Bitcoin, while upholding the principles of decentralization and trust minimization. Its distinctive attributes such as direct transfer, double-spend prevention, and simplified verification lend themselves to a secure and streamlined tokenization process, validating the assertion that UTXOs are the fundamental unit of tokenization in Bitcoin.


# Token Exchange Protocol for UTXO-based Overlay Networks

Ty Everett (<ty@projectbabbage.com>)

## Abstract

We propose a peer-to-peer token exchange protocol under which two parties can securely exchange digital tokens (assets) within Bitcoin SV's BRC-22 UTXO-based Overlay Networks.

## Motivation

The motivation behind this protocol is the need for an efficient and secure mechanism to exchange different types of tokenized assets on top of overlay networks between two parties in the BSV ecosystem.

## Specification

The protocol is initiated when two parties, Alice and Bob, desire to conduct an asset exchange. It assumes that both parties are online. We specify this protocol conceptually, by example:

1. **Offer Initiation:** Alice wants to sell 5 apples for 3 USD. She creates a new transaction moving her 5 apples into a new output. This output includes her exchange request, peer-to-peer contact information (such as [BRC-33](/peer-to-peer/0033)), and the asset ID for the asset she would like to exchange, reflected in a new transaction on the overlay network.
2. **Revocation by the Initiator:** If Alice decides to cancel her proposition, she can spend the offer output and move her apples back, retracting the offer.
3. **Offer Acceptance:** Bob is looking to purchase 5 apples for his 3 USD. He sees Alice's offer, verifying her offered UTXO(s) are registered on the overlay. Bob creates a new transaction, conditionally signing his USD over to Alice using `SIGHASH_SINGLE`. He ensures the signature is only valid over all the inputs from both Alice and Bob. The single output which he signs pays himself the 5 apples from Alice's inputs.
4. **Offer Presentation:** Bob contacts Alice, identifies himself and shares the transaction and signature with her.
5. **Verification:** Alice verifies the information and conducts due diligence to confirm Bob's outpoints on the overlay network. She verifies the signature and the validity of the transaction. If she decides not to proceed, she can simply do nothing or send Bob a rejection message.
6. **Transaction Settlement:** Alice responds by signing her inputs with a `SIGHASH_ALL` signature, spending her inputs, and adding an output that pays her Bob's 3 USD. She adds her signature to the transaction, and broadcasts it to Bob and the overlay network. Once her transaction reaches the overlay network, Alice now possesses Bob's 3 USD and Bob is the owner of Alice's 5 apples.

## Implementation

This Token Exchange Protocol can be implemented on any UTXO-based overlay network adhering to [BRC-59](/opinions/0059), [BRC-45](/tokens/0045), [BRC-22](/overlays/0022) and [BRC-24](/overlays/0024). This protocol has security built-in, stipulating both `SIGHASH` type usage and broadcasting and topical membership verification methods.

## Future work

In general, overlay network recovery mechanisms for uncooperative participant handling (non-registration or improper broadcast of transactions) need to be defined as part of future specifications. Further tooling and guidance for developers would expand adoption.


# Mandala Token Protocol

## BRC-92: Mandala Token Protocol

Deggen (<deggen@kschw.com>)

### Abstract

Minimalist protocol for tokenization, issuance, transfer, recovery, and redemption.

### Motivation

There is a lack of clarity with respect to how tokens can be defined and managed within the context of Overlays. This proposal aims to demonstrate the minimim viable solution for tokens, having considered all available options, picked the most viable, and made small improvements to allow for simple extension of functionality.

### Specification

1. Use a genesis transaction output as an `assetId` concatenating the txid and vout.
2. Push that `assetId` to the stack in any output script you want to send that token to so that overlays, wallets, and smart contracts can evaluate it.
3. Push the `amount` of tokens the output represents if it's a fungible token.
4. Drop the data so that you can use whatever functional logic you like thereafter `...`
5. 1 satoshi assigned to each output.
6. Prefix everything with a UTF8 exclamation point `!` (0x21 in hex) for sake of measuring adoption.

#### Fungible Token

```xml
21 <assetId> <amount> OP_DROP OP_2DROP ...
```

#### NFT Script

```xml
21 <assetId> OP_2DROP ...
```

### Transfers

The sum of input token amounts must equal the sum of output token amounts of the same `assetId`.

![MFT](https://github.com/user-attachments/assets/a4aa7438-3269-40f1-a71f-c3712fdf20c9)

| inputs                  | outputs                                        |
| ----------------------- | ---------------------------------------------- |
| output holding 9 tokens | `21` `assetId` `04` `OP_DROP` `OP_2DROP` `...` |
|                         | `21` `assetId` `05` `OP_DROP` `OP_2DROP` `...` |

The order of inputs and outputs is disregarded.

### Design Justification

#### Include Genesis Output and Protocol Identifier In Every Output

Token outputs each refer to the genesis output as a way to avoid collisions when identifying the asset they represent. This acts as a universal asset identifier for enabling swap contracts and token based payment protocols. The randomness helps us avoid things like people competeing to claim the "USD" assetId or other potentially popular labels.

They also include a prefix of `!` in utf8 as a way to tag outputs for tracking global adoption of the protocol; to reduce the cost of recovery from archival services; and for use within the context of IPv6 multicast group address routing.

#### Why Push Data Formatting?

The only reason to include the data in the outputs at all is for access to the data in smart contracts, any metadata ought to be kept in the application layer if needed.

Bitcoin Number format is used for the token amount.

Transaction outpoint format is used for the genesis outpoint information.

This is to ensure smart contracts can more easily parse the data within a transaction to enforce conditional logic based on token values, and enforce token type in a format which is already incorporated into the transaction format itself.

#### Single Satoshi Outputs

All outputs have 1 satoshi assigned to avoid AML problems like sending “a worthless bean token” to someone which actually has 100 BSV under it.

## Tokenization: Deep Dive

#### NFTs

Non-Fungible tokenization is the process of associating something with a particular transaction output which will thereafter represent a claim to that something. We assume that the issuer has already registered a public key as associated with them: `identityPublicKey`.

```js
const details = {
	entity: 'Local Comedy Troupe',
	asset: 'Ticket for show on 9/01/2024',
	address: '412 E 6th St, Austin, TX 78701'
}

const commitment = hash(details)
const anyone = new PrivateKey(1)
const pubkey = identityPublicKey.deriveChild(anyone, commitment)
```

We propose this key be referred to as a `BoundKey`.

The output looks like a regular P2PK but if you know the owner’s identity public key and the token details you are able to verify the association. The txid and vout of this transaction form the `assetId` for this NFT.

![boundKeyNFT](https://github.com/user-attachments/assets/51d63137-f3d6-4e66-b798-886499f449a7)

***

#### Fungible Tokens

Fungible tokens sometimes require administrative management: multiple issuances to increase available supply, redemptions to reduce supply, recovery from loss in case of errors or theft. These things require an known issuer to steward the token system as a whole while keeping individual transactions private.

![boundKeyFT](https://github.com/user-attachments/assets/a8c0994b-796d-4a34-9549-1ebb8fd0770b)

**Registration**

There is an issuer who is responsible for maintaining the relationship between tokens and the real world assets they represent. Before we issue any tokens, we register a public key by creating an authorized outpoint with a key derived from the issuer’s identity key. This transaction is used as the basis for a particular fungible token.

| inputs | outputs                                                                                            |
| ------ | -------------------------------------------------------------------------------------------------- |
| any    | `21` `OP_DROP` `boundKey` `OP_CHECKSIG` (the genesis outpoint, assetId, and authorized outpoint 0) |

**Issue**

Once registered, tokens can be issued by creating a single transaction which:

* spends the genesis outpoint
* creates token outputs
* creates the next authorized outpoint

| inputs                | outputs                                                           |
| --------------------- | ----------------------------------------------------------------- |
| authorized outpoint n | `21` `OP_DROP` `boundKey` `OP_CHECKSIG` (authorized outpoint n+1) |
|                       | `21` `assetId` `09` `OP_2DROP` `P2PKH` (creates 9 tokens)         |

***

**Redeem**

Redemption transactions spend token outputs without creating new ones, taking them out of circulation. The boundKey in this case could incorporate data associated with a withdrawal of funds from an associated bank account for example.

| inputs                                        | outputs                                                           |
| --------------------------------------------- | ----------------------------------------------------------------- |
| authorized outpoint n                         | `21` `OP_DROP` `boundKey` `OP_CHECKSIG` (authorized outpoint n+1) |
| Any token outpoint with our genesis\_outpoint | no token outputs                                                  |

A chain of authorization outpoints is created such that the transaction DAG works as an immutable linked hash chain of all administrative actions taken since genesis. Public audit-ability. No hidden issuances or redemptions. The token supply is known and provable.

***

#### Burning ⇒ Loss

When a token owner creates a valid BSV transaction which does not conform to the token transfer rules, the tokens are burned, and the transaction will not be accepted by the token overlay, despite perhaps being broadcast on the blockchain itself.

| inputs       | outputs                      |
| ------------ | ---------------------------- |
| Valid tokens | no token outputs, or too few |

In this case, `tokens in` does not equal `tokens out`.

***

#### Recovery

Post burning, tokens which were lost can be recovered by the issuer by spending and creating an authorized output while including metadata into the derivation of a pubkey to indicate which txids are to be processed as having been spent invalidly.

![recovery](https://github.com/user-attachments/assets/3ea03c5e-3733-40d1-b94a-063013f89165)

| inputs                | outputs                                                           |
| --------------------- | ----------------------------------------------------------------- |
| authorized outpoint n | `21` `OP_DROP` `boundKey` `OP_CHECKSIG` (authorized outpoint n+1) |
|                       | `21` `assetId` `05` `OP_2DROP` `P2PKH`                            |

***

### Example Transactions

1. [Register](https://whatsonchain.com/tx/99476f7c4a1479e900c9d7901400a36ab949d21bfa3d906aa45f602edfc90e7a)
2. [Issue](https://whatsonchain.com/tx/9230b66fbc021294d35632dd2e8d31568b9c9fac8639a8d2bb5cd515f7957f31)
3. [Transfer](https://whatsonchain.com/tx/906aa812537af107a3ab1a094579f690c42145ec2e7e6a0cd8f16faa42cf1e55)
4. [Redeem](https://whatsonchain.com/tx/906aa812537af107a3ab1a094579f690c42145ec2e7e6a0cd8f16faa42cf1e55)
5. [Burn](https://whatsonchain.com/tx/27f092c784604a94c52fc45acecbac4f868fa41eab90a77290076d22eb2057a4)

### Implementations

Not yet available. Proposal stage.


# Enhanced Mandala Token Protocol

Jake Jones (<jake.jones@bsvassociation.org>)

## Abstract

This proposal extends BRC-92 (Mandala Token Protocol) with cryptographic commitments to provide tamper-evident token amounts and SPV-friendly verification while maintaining the minimalist design philosophy. The enhancement adds a commitment hash to each token output that cryptographically binds the token amount to its transaction history, enabling lightweight clients to verify token integrity without accessing the full blockchain or trusting overlay networks.

## Motivation

The original BRC-92 Mandala Token Protocol provides a minimalist approach to tokenization on BSV, but relies entirely on overlay networks to validate token conservation rules. In real-world usage scenarios, particularly SPV wallets and point-of-sale transactions, users need cryptographic assurance that token amounts haven't been tampered with. This enhancement addresses these limitations while preserving the simplicity that makes BRC-92 attractive.

Key problems this solves:

* SPV wallets cannot verify token conservation without trusting overlays
* No cryptographic proof linking token amounts to their history
* Potential for amount tampering between transaction creation and overlay submission
* Lack of standardized verification protocol for lightweight clients

## Specification

### Enhanced Token Output Format

Building upon BRC-92's structure, we add a cryptographic commitment field:

#### Fungible Token Output

```xml
21 <assetId> <amount> <commitment> OP_2DROP OP_2DROP OP_DROP ...
```

Where:

* `21` (0x21): UTF-8 exclamation mark prefix (unchanged from BRC-92)
* `assetId`: Genesis transaction outpoint (txid:vout) (unchanged)
* `amount`: Token amount in Bitcoin number format (unchanged)
* `commitment`: **NEW** - Cryptographic commitment hash
* `...`: Functional locking script (e.g., P2PKH) (unchanged)

#### Commitment Construction

The commitment is calculated as:

```
commitment = H(assetId || amount || prevTxid || nonce)
```

Where:

* `H`: SHA-256 hash function
* `||`: Concatenation operator
* `prevTxid`: Transaction ID of the input being spent
* `nonce`: Optional 32-byte random value for privacy

### NFT Token Output

```xml
21 <assetId> <commitment> OP_2DROP OP_DROP ...
```

For NFTs, the commitment is:

```
commitment = H(assetId || prevTxid || metadata_hash)
```

### SPV Verification Protocol

When transferring tokens in an SPV context, the sender provides:

```json
{
  "transaction": "hex_encoded_transaction",
  "tokenProofs": {
    "inputs": [
      {
        "index": 0,
        "amount": 100,
        "commitment": "abc123...",
        "prevTx": "def456...",
        "merkleProof": "..."
      }
    ],
    "outputs": [
      {
        "index": 0,
        "amount": 60,
        "commitment": "ghi789..."
      },
      {
        "index": 1,
        "amount": 40,
        "commitment": "jkl012..."
      }
    ],
    "conservationProof": {
      "totalIn": 100,
      "totalOut": 100
    }
  }
}
```

### Verification Steps

Recipients MUST perform the following verification:

1. **Commitment Verification**: For each input and output, verify:

   ```
   commitment == H(assetId || amount || prevTxid || nonce)
   ```
2. **Conservation Check**: Verify that:

   ```
   sum(input_amounts) == sum(output_amounts)
   ```
3. **Merkle Proof Validation**: Verify SPV proofs for recent transactions
4. **Accept/Reject**: Transaction is valid if all checks pass

### Backward Compatibility

This enhancement maintains full backward compatibility with BRC-92:

* Existing BRC-92 tokens continue to function
* Overlays that don't recognize commitments can ignore them (treated as extra data)
* The drop pattern ensures scripts execute identically
* Migration path: New tokens use commitments, old tokens grandfathered

### Privacy Considerations

The commitment includes an optional nonce field to prevent amount analysis:

* Without nonce: Same amounts produce same commitments (linkable)
* With nonce: Same amounts produce different commitments (unlinkable)

## Design Justification

### Why Commitments Instead of Script Enforcement?

Bitcoin Script's execution model prevents cross-input/output verification within scripts. Cryptographic commitments provide the next best solution:

* **Tamper-evident**: Can't change amounts without breaking commitments
* **SPV-compatible**: Verifiable with just transaction data
* **Minimalist**: Adds only one hash field
* **Overlay-independent**: Cryptographic proof doesn't require trust

### Why Include prevTxid?

Including the previous transaction ID in the commitment:

* Creates an unforgeable chain of custody
* Prevents replay attacks with old commitments
* Links each token to its complete history
* Enables proof of lineage back to genesis

### Why Optional Nonce?

The nonce field balances transparency and privacy:

* Public tokens (securities): No nonce for full transparency
* Private tokens (cash-like): Nonce prevents amount correlation
* User choice: Applications decide based on use case

## Example Transactions

### Token Transfer with Commitments

**Input** (100 tokens from previous tx):

```
Previous output:
21 <assetId> <100> <H(assetId||100||prev_prev_txid||nonce)> OP_2DROP OP_2DROP OP_DROP
```

**Outputs**:

```
Output 0 (60 tokens to Bob):
21 <assetId> <60> <H(assetId||60||current_input_txid||nonce1)> OP_2DROP OP_2DROP OP_DROP P2PKH(Bob)

Output 1 (40 tokens change to Alice):
21 <assetId> <40> <H(assetId||40||current_input_txid||nonce2)> OP_2DROP OP_2DROP OP_DROP P2PKH(Alice)
```

### SPV Verification Example

Bob receives a payment at a coffee shop:

1. **Alice sends**: Transaction + commitment proofs + merkle proof
2. **Bob's wallet verifies** (< 100ms):
   * Commitment: `H(assetId||30||prevTxid||nonce) == provided_commitment` ✓
   * Conservation: `50 in == 30 + 20 out` ✓
   * Merkle proof: Transaction exists in blockchain ✓
3. **Bob accepts**: Payment verified without blockchain access

## Implementation Considerations

### Wallet Integration

Wallets implementing this standard should:

1. Generate commitments when creating token transactions
2. Store nonces for privacy-enabled tokens
3. Verify commitments before accepting tokens
4. Provide SPV proof packages to recipients

### Overlay Network Integration

Overlay networks can:

1. Reject transactions with invalid commitments
2. Use commitments for faster validation
3. Provide commitment verification as a service
4. Index tokens by commitment for quick lookups

### Performance Impact

* **Additional data**: 32 bytes per output (commitment)
* **Computation**: One SHA-256 hash per input/output
* **Verification time**: < 1ms per token on modern devices
* **Storage**: Negligible increase (< 5% for typical token transactions)

## Security Considerations

### Commitment Forgery

Forging a commitment requires finding a hash collision:

* SHA-256 preimage resistance: 2^256 operations
* Collision resistance: 2^128 operations
* Practically impossible with current technology

### Replay Attacks

Including prevTxid prevents replay:

* Old commitments won't match new transactions
* Each commitment is unique to its transaction chain

### Amount Tampering

Changing amounts breaks commitments:

* Detectable by any verifier
* Rejected by overlays and recipients

## Migration Path

1. **Phase 1**: Wallets add commitment generation (optional)
2. **Phase 2**: Overlays prefer commitment-enabled tokens
3. **Phase 3**: Recipients require commitments for new tokens
4. **Phase 4**: Full ecosystem adoption

## References

* BRC-92: Mandala Token Protocol
* BRC-62: Background Evaluation Extended Format (BEEF) Transactions
* BRC-67: Simplified Payment Verification

## Test Vectors

### Commitment Calculation

Input:

```
assetId = "abc...def:0"
amount = 100
prevTxid = "123...456"
nonce = "000...000"
```

Output:

```
commitment = SHA256("abc...def:0" || 100 || "123...456" || "000...000")
           = "a7b8c9d0e1f2..."
```

### Conservation Verification

Valid (conserves tokens):

```
Inputs: [50, 30, 20] (sum = 100)
Outputs: [60, 40] (sum = 100)
Result: VALID ✓
```

Invalid (creates tokens):

```
Inputs: [50, 30] (sum = 80)
Outputs: [60, 40] (sum = 100)
Result: INVALID ✗
```

## Implementations

Reference implementations available at:

* TypeScript: Coming Soon
* Go: Coming Soon
* Python: Coming Soon


# Identity-Linked Token Protocol

Jake Jones (<jake.jones@bsvassociation.org>)

## Abstract

This proposal extends the Enhanced Mandala Token Protocol (BRC-107) with identity certificate integration based on BRC-52/53. It enables tokens to enforce real-world compliance requirements, KYC/AML regulations, and identity-based access controls while preserving privacy through selective field revelation. The protocol supports regulated security tokens, accredited investor requirements, geographic restrictions, and identity-based recovery mechanisms, all validated through SPV-friendly cryptographic proofs.

## Motivation

Current token systems on BSV lack native integration with identity verification, making regulatory compliance difficult and limiting adoption for security tokens and regulated assets. While BRC-52/53 provides a robust identity certificate system, there's no standard for linking these certificates to token ownership and transfers.

This proposal addresses:

* Regulatory compliance for security tokens requiring KYC/AML
* Accredited investor verification for restricted offerings
* Geographic restrictions for regulatory jurisdictions
* Identity-based recovery for lost keys
* Privacy-preserving compliance through selective revelation
* Fraud prevention through identity linkage

## Specification

### Identity-Bound Token Types

We define three levels of identity requirements for tokens:

1. **Open Tokens**: No identity requirements (backward compatible with BRC-92/BRC-107)
2. **Verified Tokens**: Require valid BRC-52 certificate
3. **Restricted Tokens**: Require specific certificate fields or types

### Enhanced Genesis Output

The genesis transaction establishes identity requirements:

```xml
21 <assetId>
<issuerCertificateHash>
<complianceRules>
<identityCommitment>
OP_2DROP OP_2DROP OP_2DROP OP_DROP ...
```

Where:

* `issuerCertificateHash`: SHA-256 hash of issuer's BRC-52 certificate
* `complianceRules`: Encoded rules for token transfers
* `identityCommitment`: `H(assetId || issuerCertificate || complianceRules || maxSupply)`

### Compliance Rules Structure

```json
{
  "version": 1,
  "requireIdentity": true,
  "allowedCertificateTypes": [
    "8l5phhdm2Hi80s6QqFOLS0NwUzDzJhlUTWv2BezmstE="
  ],
  "requiredFields": ["country", "accreditedStatus"],
  "restrictions": {
    "allowedCountries": ["US", "CA", "GB"],
    "minCertificateLevel": "enhanced_kyc",
    "maxAmountPerIdentity": 10000,
    "cooldownPeriod": 86400
  }
}
```

### Identity-Linked Token Output

```xml
21 <assetId> <amount>
<recipientCertificateHash>
<identityCommitment>
OP_2DROP OP_2DROP OP_2DROP OP_DROP P2PKH
```

Where:

* `recipientCertificateHash`: SHA-256 hash of recipient's BRC-52 certificate
* `identityCommitment`: `H(assetId || amount || certificateHash || prevTxid)`

### Identity-Aware Transfer Protocol

When transferring identity-linked tokens, the sender provides:

```json
{
  "transaction": "hex_encoded_transaction",
  "tokenProofs": {
    "inputs": [...],
    "outputs": [...],
    "conservationProof": {...}
  },
  "identityProofs": {
    "sender": {
      "certificate": {
        "type": "8l5phhdm2Hi80s6QqFOLS0NwUzDzJhlUTWv2BezmstE=",
        "subject": "0376d67c86b45be3c36c328c2aa5c5dd79c546d2...",
        "serialNumber": "kUahacBHmYL2nkzemkatFg==",
        "certifier": "035ce8cc44dbcf4c991d666d381d67263aed9123...",
        "revocationOutpoint": "48645047cd66f7b48b24efb080ec7e27...1",
        "signature": "3045022100c0686907...",
        "keyring": {
          "country": "encrypted_for_recipient",
          "accreditedStatus": "encrypted_for_recipient"
        }
      },
      "certificateProof": {
        "merkleProof": "...",
        "blockHeight": 850000
      }
    },
    "recipient": {
      "certificateHash": "sha256_hash_of_recipient_certificate",
      "certificateType": "8l5phhdm2Hi80s6QqFOLS0NwUzDzJhlUTWv2BezmstE="
    }
  },
  "complianceAttestation": {
    "rulesVersion": 1,
    "checksPassed": [
      "country_allowed",
      "accredited_investor",
      "amount_within_limit"
    ],
    "timestamp": 1234567890,
    "attestorSignature": "..."
  }
}
```

### Verification Protocol

Recipients and overlays MUST verify:

#### 1. Standard Token Checks (from BRC-106)

* Commitment verification
* Conservation check
* Merkle proof validation

#### 2. Identity Checks

```javascript
function verifyIdentityCompliance(transfer) {
  // Verify sender's certificate
  if (!verifyCertificate(transfer.identityProofs.sender.certificate)) {
    return false;
  }

  // Check certificate not revoked
  if (isSpent(transfer.identityProofs.sender.certificate.revocationOutpoint)) {
    return false;
  }

  // Verify certificate matches output
  senderCertHash = H(transfer.identityProofs.sender.certificate);
  if (senderCertHash != transfer.transaction.inputs[0].certificateHash) {
    return false;
  }

  // Decrypt and verify required fields
  fields = decryptFields(transfer.identityProofs.sender.certificate.keyring);
  if (!meetsComplianceRules(fields, token.complianceRules)) {
    return false;
  }

  // Verify recipient certificate type
  if (!token.complianceRules.allowedCertificateTypes.includes(
    transfer.identityProofs.recipient.certificateType)) {
    return false;
  }

  return true;
}
```

### Selective Field Revelation

Certificate holders reveal only required fields:

1. **Determine Required Fields**: Based on token's compliance rules
2. **Encrypt for Recipient**: Using BRC-52/53 key derivation
3. **Include in Transfer**: Add encrypted keys to certificate keyring
4. **Recipient Decrypts**: Using their identity key

Example revealing only country for geographic restriction:

```json
{
  "certificate": {
    "fields": {
      "firstName": "encrypted_not_revealed",
      "lastName": "encrypted_not_revealed",
      "country": "encrypted_value",
      "city": "encrypted_not_revealed"
    },
    "keyring": {
      "country": "encrypted_revelation_key_for_recipient"
    }
  }
}
```

### Identity-Based Recovery

Tokens can include recovery conditions:

```xml
21 <assetId> <amount>
<primaryCertificateHash>
<recoveryCommitment>
OP_IF
  <recoveryDelay> OP_CHECKSEQUENCEVERIFY OP_DROP
  <recoveryCertificateHash> OP_EQUAL
OP_ELSE
  <primaryCertificateHash> OP_EQUAL
OP_ENDIF
```

Where:

* `recoveryCommitment`: `H(certificateType || recoveryFields || recoveryPubkey)`
* `recoveryDelay`: Time lock before recovery (e.g., 30 days)

### Certificate Levels and Permissions

```json
{
  "certificateLevels": {
    "basic_kyc": {
      "maxTransferAmount": 1000,
      "dailyLimit": 5000,
      "allowedOperations": ["transfer", "receive"]
    },
    "enhanced_kyc": {
      "maxTransferAmount": 10000,
      "dailyLimit": 50000,
      "allowedOperations": ["transfer", "receive", "stake"]
    },
    "institutional": {
      "maxTransferAmount": null,
      "dailyLimit": null,
      "allowedOperations": ["transfer", "receive", "stake", "mint", "burn"]
    }
  }
}
```

## Use Cases

### 1. Security Token Offering (STO)

```javascript
// Genesis specifies accredited investor requirement
complianceRules = {
  requiredFields: ["accreditedStatus", "country"],
  allowedCountries: ["US"],
  restrictions: {
    mustBeAccredited: true,
    minInvestment: 10000
  }
}
```

### 2. Geographic Restrictions

```javascript
// Token only transferable within specific regions
complianceRules = {
  requiredFields: ["country"],
  allowedCountries: ["EU", "UK", "CH"],
  restrictions: {
    blockRestrictedCountries: ["US", "CN", "RU"]
  }
}
```

### 3. Age-Restricted Assets

```javascript
// Gaming tokens requiring age verification
complianceRules = {
  requiredFields: ["ageRange"],
  restrictions: {
    minimumAge: 18,
    requireAgeAttestation: true
  }
}
```

### 4. Institutional Trading

```javascript
// Different limits based on certificate type
if (certificate.type == "retail") {
  maxDailyVolume = 10000;
} else if (certificate.type == "qualified") {
  maxDailyVolume = 100000;
} else if (certificate.type == "institutional") {
  maxDailyVolume = unlimited;
}
```

## Privacy Considerations

### Zero-Knowledge Proofs

For sensitive compliance checks, support ZK proofs:

* Prove age > 18 without revealing exact age
* Prove accredited status without revealing net worth
* Prove country membership without revealing specific country

### Encrypted Field Storage

All certificate fields remain encrypted until revelation:

* Only revealed fields are decrypted
* Revelation is counterparty-specific
* No permanent plaintext exposure

### Pseudonymous Compliance

Users can maintain pseudonymity while proving compliance:

* Certificate links to pubkey, not real name
* Only certifier knows real identity
* Selective revelation preserves privacy

## Security Considerations

### Certificate Validation

* Always verify certificate signatures
* Check revocation status via UTXO
* Validate certifier is trusted
* Ensure certificate hasn't expired

### Replay Protection

* Include prevTxid in commitments
* Timestamp compliance attestations
* Use nonces for uniqueness

### Key Compromise

* Certificate revocation via UTXO spending
* Recovery mechanisms for token access
* Time-locked recovery periods

## Migration and Compatibility

### Backward Compatibility

* Open tokens work without identity
* Existing BRC-92/106 tokens unaffected
* Optional identity for gradual adoption

### Migration Path

1. **Phase 1**: Deploy identity-aware wallets
2. **Phase 2**: Certifiers issue BRC-52 certificates
3. **Phase 3**: Token issuers adopt compliance rules
4. **Phase 4**: Overlays enforce identity requirements
5. **Phase 5**: Full ecosystem adoption

## Implementation Examples

### Creating Identity-Linked Token

```javascript
async function createIdentityToken(params) {
  // Get issuer's certificate
  const issuerCert = await wallet.getCertificate();

  // Define compliance rules
  const rules = {
    requireIdentity: true,
    allowedCertificateTypes: [params.certificateTypeId],
    requiredFields: params.requiredFields,
    restrictions: params.restrictions
  };

  // Create genesis with identity binding
  const genesis = {
    assetId: generateAssetId(),
    issuerCertificateHash: hash(issuerCert),
    complianceRules: rules,
    identityCommitment: hash(
      assetId + issuerCert + rules + params.maxSupply
    )
  };

  return createGenesisTransaction(genesis);
}
```

### Transferring with Compliance

```javascript
async function transferWithCompliance(amount, recipient) {
  // Get certificates
  const senderCert = await wallet.getCertificate();
  const recipientCert = await requestCertificate(recipient);

  // Check compliance
  const compliance = await checkCompliance(
    senderCert,
    recipientCert,
    token.complianceRules
  );

  if (!compliance.passed) {
    throw new Error(`Compliance failed: ${compliance.reason}`);
  }

  // Reveal required fields
  const keyring = await revealFields(
    senderCert,
    token.complianceRules.requiredFields,
    recipient
  );

  // Create transfer
  return {
    transaction: createTokenTransfer(amount, recipient),
    identityProofs: {
      sender: {
        certificate: senderCert,
        keyring: keyring
      },
      recipient: {
        certificateHash: hash(recipientCert)
      }
    },
    complianceAttestation: compliance.attestation
  };
}
```

## Test Vectors

### Identity Commitment

```
Input:
  assetId: "abc...def:0"
  certificateHash: "123...789"
  amount: 100
  prevTxid: "def...456"

Output:
  commitment = SHA256(assetId || amount || certificateHash || prevTxid)
             = "xyz...123"
```

### Compliance Check

```
Certificate Fields:
  country: "US"
  accreditedStatus: "true"

Compliance Rules:
  allowedCountries: ["US", "CA"]
  mustBeAccredited: true

Result: PASS ✓
```

## References

* BRC-52: Identity Certificates
* BRC-53: Certificate Creation and Revelation
* BRC-92: Mandala Token Protocol
* BRC-107: Enhanced Mandala Token Protocol
* BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties
* BRC-2: Data Encryption and Decryption
* BRC-3: Digital Signature Creation and Verification

## Implementations

Reference implementations:

* TypeScript: Coming Soon
* Go: Coming Soon
* Python: Coming Soon


# Merkle Proof Token

### Merkle Proof Token

Feb 4, 2026

**Introduction by Metro Gnome**

**The simple idea**

There needs to be a token that lives within the native Bitcoin architecture, enabling all the advantages SPV, while removing all the overhead of managing the token itself.

Every token set begins with a genesis mint transaction. Once mined into a block, this becomes the immutable anchor for all the tokens in that mint. The chain is "the single source of truth" and SPV, the supersonic highway back to genesis.

#### Use cases

**Blockchain gaming**

Tokens can be used in a multiplayer blockchain game. If NFTs were considered to be keys, then these "keys" could be used to unlock "doors" within the game.

**Content delivery**

Books, music, films can be locked within or behind and NFT. Ensuring that only the legitimate owner of the NTF can access the contents.

**Retail transactions**

Both the fungible and non fungible form of MPT enables invoices, receipts or documents to be attached to a token.

**NFT coupons**

NFT coupons can be created by a retailer and sent to their customers. Coupons can be programmed to be non transferable or only valid within a certain time window.

**Loyalty cards**

A transparent and tamper proof loyalty point card could be minted by a retailer and distributed to customers.

### Overview

The Merkle Proof Token (MPT) is a token protocol on BSV mainnet that uses P2PKH outputs for ownership and OP\_RETURN outputs for metadata. Token validity is proven exclusively through Merkle proofs and block headers (SPV), with no dependency on UTXO lookups, indexers, or trusted third parties for verification.

MPT supports two token modes:

* **NFT Mode**: Each 1-sat output has a unique Token ID based on its genesis output index. Suitable for non-fungible tokens, collectibles, and divisible token fragments.
* **Fungible Mode**: All UTXOs share a single Token ID (genesisOutputIndex fixed at 1). Each satoshi equals one token unit. Multiple UTXOs form a "basket" that can be split and merged through transfers.

### SPV Token Verification

An MPT token can be verified using only three pieces of data: the **Token ID**, the **genesis transaction ID**, and a **block header**.

#### Token ID Derivation

The Token ID is a single SHA-256 hash over the concatenation of:

```
Token ID = SHA-256(genesisTxId || outputIndex LE || immutableChunkBytes)
where immutableChunkBytes = tokenName + tokenScript + tokenRules
```

* `genesisTxId`: 32 bytes, the hash of the genesis transaction
* `outputIndex LE`: 4 bytes little-endian, the P2PKH output index in the genesis TX (starts at 1; output 0 is OP\_RETURN)
* `immutableChunkBytes`: The raw pushdata bytes of tokenName, tokenScript, and tokenRules concatenated in order
* **Important:** tokenAttributes is **mutable** and NOT included in the Token ID. This allows tokenAttributes to be updated on each transfer without affecting token identity.

This is a purely local computation. No network access is required. If any immutable field has been tampered with, the recomputed Token ID will not match the claimed one.

#### Technical Note on Chunk Indices

The OP\_RETURN script contains OP\_0 and OP\_RETURN opcodes at positions \[0] and \[1]. When parsing the OP\_RETURN data, these opcodes are stripped, creating an index offset:

* Encoder perspective: chunks \[2], \[3], \[4], \[5] contain the token data
* Parser perspective: chunks \[0], \[1], \[2], \[3] contain the same token data (offset by 2)

This distinction is critical for understanding the codec implementation and how token data is serialized and deserialized.

#### Merkle Proof Verification

Given the genesis transaction ID, the verifier obtains its Merkle proof -- an ordered list of sibling hashes that, combined with the transaction hash, reproduce the block's Merkle root.

Verification proceeds bottom-up: starting from the double-SHA-256 of the genesis transaction ID, the verifier concatenates each sibling hash (left or right, as indicated by the proof path) and double-SHA-256s the pair at each level. The final output is the computed Merkle root.

#### Block Header Confirmation

The computed Merkle root is compared against the `hashMerkleRoot` field in the block header at the genesis transaction's confirmed height. A match proves the genesis transaction was included in that block. The block header itself is an 80-byte structure whose validity is established by its proof-of-work -- it must hash below the difficulty target for that height.

#### Why Only the Genesis Transaction

Transfer transactions do not require independent block header verification. Each transfer spends the previous token UTXO as an input; miners validate that the input exists and is unspent before accepting the transaction into a block. A transfer transaction that references a non-existent or already-spent UTXO is rejected by the network. This is the Bitcoin UTXO model's built-in guarantee -- once a transfer is mined, its validity is implicit.

The genesis transaction is the only one that creates value from nothing (from the token protocol's perspective). Proving it was mined is sufficient to establish that the token's origin is legitimate and that all subsequent transfers were validated by miners through normal transaction processing.

### Token Lifecycle

```
╔══════════════════════════════════════════════════════════════════════════════╗
║                     MPT TOKEN LIFECYCLE DIAGRAM                              ║
╚══════════════════════════════════════════════════════════════════════════════╝


  ┌─────────────────────────────────────────────────────────────────────────┐
  │                        1. GENESIS (MINT)                                │
  │                        Wallet A creates tokens                          │
  └────────────────────────────────┬────────────────────────────────────────┘
                                   │
                                   ▼
                  ┌───────────────────────────────────┐
                  │         Genesis Transaction       │
                  │                                   │
                  │  Input 0: Funding UTXO(s)         │
                  │          (Wallet A's BSV)         │
                  │                                   │
                  │  Output 0: OP_RETURN (0 sats)     │
                  │    ┌───────────────────────┐      │
                  │    │ "MPT" | version       │      │
                  │    │ tokenName             │      │
                  │    │ tokenScript           │      │
                  │    │ tokenRules            │      │
                  │    │ tokenAttributes       │      │
                  │    │ stateData             │      │
                  │    └───────────────────────┘      │
                  │                                   │
                  │  Output 1: P2PKH → Wallet A (1sat)│─── Token #1
                  │  Output 2: P2PKH → Wallet A (1sat)│─── Token #2
                  │  Output 3: P2PKH → Wallet A (1sat)│─── Token #3
                  │  ...                              │
                  │  Output N: P2PKH → Wallet A (1sat)│─── Token #N
                  │                                   │
                  │  [Optional: File OP_RETURN]       │
                  │  Output N+1: Change → Wallet A    │
                  └─────────────────┬─────────────────┘
                                    │
                                    ▼
  ┌─────────────────────────────────────────────────────────────────────────┐
  │                  2. RETURN TO MINTER'S WALLET                           │
  │                                                                         │
  │  Token ID = SHA-256(genesisTxId || outputIndex LE || immutableChunkBytes)│
  │  where immutableChunkBytes = tokenName + tokenScript + tokenRules      │
  │                                                                         │
  │  Each token is stored with:                                             │
  │    • tokenId (derived, immutable)                                       │
  │    • genesisTxId + genesisOutputIndex (origin reference)                │
  │    • currentTxId + currentOutputIndex (spendable UTXO)                  │
  │    • All metadata fields (name, script, rules, attrs, state)            │
  │    • Proof chain: empty at genesis (no Merkle proof yet)                │
  │    • Status: "active"                                                   │
  └────────────────────────────────┬────────────────────────────────────────┘
                                   │
            ┌──────────────────────┼──────────────────────┐
            │                      │                      │
            ▼                      ▼                      ▼
  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
  │  Transfer #1    │  │  Transfer #2    │  │  Transfer #N    │
  │  Token #1       │  │  Token #2       │  │  Token #N       │
  │  → Wallet B     │  │  → Wallet C     │  │  → Wallet D     │
  └────────┬────────┘  └────────┬────────┘  └────────┬────────┘
           │                    │                     │
           ▼                    ▼                     ▼
  ┌─────────────────────────────────────────────────────────────────────────┐
  │                     3. TRANSFER TRANSACTION                             │
  │                     (one per token)                                     │
  │                                                                         │
  │  Input 0: Token UTXO (1 sat P2PKH, signed by Wallet A)                  │
  │  Input 1+: Funding UTXO(s) (Wallet A's BSV for miner fee)               │
  │                                                                         │
  │  Output 0: P2PKH → Recipient address (1 sat)  ← new token UTXO          │
  │  Output 1: OP_RETURN (0 sats)                                           │
  │    ┌───────────────────────────────────────┐                            │
  │    │ "MPT" | version                       │                            │
  │    │ tokenName                             │                            │
  │    │ tokenScript                           │                            │
  │    │ tokenRules                            │                            │
  │    │ tokenAttributes (mutable)             │                            │
  │    │ stateData (mutable)                   │                            │
  │    │ ─── transfer-only fields ───          │                            │
  │    │ genesisTxId (32 bytes)                │                            │
  │    │ proofChain (binary bundle)            │                            │
  │    │ genesisOutputIndex (4 bytes LE)       │                            │
  │    └───────────────────────────────────────┘                            │
  │  Output 2: Change → Wallet A                                            │
  │                                                                         │
  │  Wallet A status: "pending_transfer" → "transferred"                    │
  └────────────────────────────────┬────────────────────────────────────────┘
                                   │
                                   │  Transaction broadcast to BSV network
                                   │  Miners validate and include in block
                                   │
                                   ▼
  ┌─────────────────────────────────────────────────────────────────────────┐
  │                   4. RECIPIENT WALLET DETECTION                         │
  │                                                                         │
  │  Recipient wallet sees a 1-sat UTXO arrive                              │
  │                                                                         │
  │           ┌──────────────────────────────┐                              │
  │           │   1-sat UTXO quarantine zone │                              │
  │           │                              │                              │
  │           │  All 1-sat UTXOs land here   │                              │
  │           │  (could be tokens or dust)   │                              │
  │           └──────────────┬───────────────┘                              │
  │                          │                                              │
  │           Two detection paths:                                          │
  │           • Auto-import (fire-and-forget from quarantine)               │
  │           • Manual scan ("Check Incoming Tokens" button)                │
  │                          │                                              │
  │           ┌──────────────▼────────────────┐                             │
  │           │  Fetch TX, decode OP_RETURN   │                             │
  │           │  Check for "MPT" + v0x02      │                             │
  │           │  Check P2PKH pays to us       │                             │
  │           └──────────────┬────────────────┘                             │
  │                          │                                              │
  │                    Is it a valid MPT?                                   │
  │                     NO → skip (stays in quarantine)                     │
  │                     YES ↓                                               │
  └──────────────────────────┬──────────────────────────────────────────────┘
                             │
                             ▼
  ┌─────────────────────────────────────────────────────────────────────────┐
  │                 5. SPV VERIFICATION GATE                                │
  │                 (before any token is accepted)                          │
  │                                                                         │
  │  Step 1: Recompute Token ID                                             │
  │    Token ID = SHA-256(genesisTxId || outputIndex LE || immutableChunkBytes)
  │    where immutableChunkBytes = tokenName + tokenScript + tokenRules    │
  │    Computed ID must match claimed ID                                    │
  │    ✗ Mismatch → REJECT                                                  │
  │                                                                         │
  │  Step 2: Obtain Merkle proof                                            │
  │    Transfer TX: proof chain embedded in OP_RETURN                       │
  │    Genesis TX: fetch Merkle proof from network                          │
  │    ✗ No proof available (unconfirmed) → remains in quarantine           │
  │                                                                         │
  │  Step 3: Verify genesis Merkle proof                                    │
  │    Hash from txId through proof path → compute Merkle root              │
  │    (Bitcoin double SHA-256 at each level)                               │
  │    ✗ Invalid proof → REJECT                                             │
  │                                                                         │
  │  Step 4: Confirm against block header                                   │
  │    Fetch block header at genesis entry's height                         │
  │    Block header's Merkle root must match computed root                  │
  │    ✗ Mismatch → REJECT                                                  │
  │                                                                         │
  │  ✓ ALL CHECKS PASS → token accepted into wallet as "active"             │
  │                                                                         │
  │  ┌───────────────────────────────────────────────────────────────┐      │
  │  │ NOTE: Only the genesis TX's block header is checked.          │      │
  │  │ Transfer TXs are validated by miners when spent — their       │      │
  │  │ block inclusion is an implicit guarantee.                     │      │
  │  └───────────────────────────────────────────────────────────────┘      │
  └────────────────────────────────┬────────────────────────────────────────┘
                                   │
                                   ▼
  ┌─────────────────────────────────────────────────────────────────────────┐
  │               6. TOKEN NOW ACTIVE IN RECIPIENT WALLET                   │
  │                                                                         │
  │  Stored with updated proof chain (includes all prior transfers)         │
  │  currentTxId points to the transfer TX (the spendable UTXO)             │
  │  genesisTxId still points to the original mint                          │
  │                                                                         │
  │  The recipient can now:                                                 │
  │    • Hold the token                                                     │
  │    • Transfer it to another wallet (cycle repeats from step 3)          │
  │    • Run "Verify" (retries genesis verification if incomplete)          │
  │    • Transfer it back to the original minter (return-to-sender)         │
  └────────────────────────────────┬────────────────────────────────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              │                    │                     │
              ▼                    ▼                     ▼
    ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
    │  Transfer onward │ │  Return to       │ │  Manual Verify   │
    │  → Wallet E      │ │  Wallet A        │ │  (retry)         │
    │  (repeat step 3) │ │  (repeat step 3) │ │                  │
    │                  │ │                  │ │  Refreshes the   │
    │  Proof chain     │ │  Wallet A sees   │ │  same genesis    │
    │  grows by one    │ │  token return,   │ │  verification    │
    │  entry per       │ │  re-verifies,    │ │  when auto-check │
    │  transfer        │ │  status → active │ │  was incomplete  │
    └──────────────────┘ └──────────────────┘ └──────────────────┘


  ┌─────────────────────────────────────────────────────────────────────────┐
  │                     PROOF CHAIN GROWTH                                  │
  │                                                                         │
  │  Genesis (mint)     chain: []  (empty, proof fetched on demand)         │
  │       │                                                                 │
  │       ▼                                                                 │
  │  Transfer A→B       chain: [{ txId_A, block, root, path }]              │
  │       │                                                                 │
  │       ▼                                                                 │
  │  Transfer B→C       chain: [{ txId_B, ... }, { txId_A, ... }]           │
  │       │                      ▲ newest-first                             │
  │       ▼                                                                 │
  │  Transfer C→D       chain: [{ txId_C }, { txId_B }, { txId_A }]         │
  │                                                                         │
  │  Each transfer adds one entry. The oldest entry (last in array)         │
  │  always corresponds to the genesis TX.                                  │
  │                                                                         │
  │  Important: The proof chain is embedded in each transfer TX's OP_RETURN │
  │  as binary data. This means the complete verification history travels  │
  │  with the token. The recipient can verify the entire chain from        │
  │  on-chain data alone without any indexer or API.                       │
  └─────────────────────────────────────────────────────────────────────────┘
```

**Network:** BSV Mainnet

***

### Token Design

An MPT token is a BSV transaction with a specific output structure. Ownership uses standard P2PKH locking scripts for token UTXOs. All token metadata lives in a separate OP\_RETURN output. When a `tokenScript` is defined, the consensus script bytes are stored in the OP\_RETURN and can be enforced by miners via techniques like OP\_PUSH\_TX; the P2PKH outputs themselves remain standard.

### Fungible Token Mode

MPT v05\_10 introduces a fungible token mode where satoshis represent token units. Unlike NFT mode where each 1-sat output is a distinct token, fungible mode uses any satoshi value where each sat equals one token unit.

#### Key Differences from NFT Mode

| Aspect        | NFT Mode                                        | Fungible Mode                          |
| ------------- | ----------------------------------------------- | -------------------------------------- |
| Token Unit    | 1-sat output = 1 unique token                   | 1 satoshi = 1 token unit               |
| Token ID      | Unique per output (based on genesisOutputIndex) | Shared (genesisOutputIndex always = 1) |
| UTXO Value    | Always 1 sat                                    | Any value > 1 sat                      |
| Storage Model | One token record per UTXO                       | One "basket" with multiple UTXOs       |
| Split/Merge   | Not applicable                                  | Supported via multiple outputs         |

#### Basket Model

Fungible tokens use a "basket" data structure that groups multiple UTXOs under a single Token ID:

```
FungibleToken {
  tokenId: string           // Shared by all UTXOs in basket
  genesisTxId: string       // Genesis transaction
  tokenName: string         // Token name
  tokenScript: string       // Consensus rules
  tokenRules: string        // Application rules
  tokenAttributes: string   // Mutable attributes
  utxos: [                  // The basket
    { txId, outputIndex, satoshis, status, stateData },
    { txId, outputIndex, satoshis, status, stateData },
    ...
  ]
}
```

Each UTXO in the basket can carry its own `stateData` (e.g., a text message), while sharing the token identity.

#### Per-UTXO State Data

In fungible mode, each UTXO in the basket can carry its own `stateData` field. This enables "message" functionality where tokens can be sent with attached text or data.

**Use Cases:**

* Send tokens with a text message (e.g., "Payment for invoice #123")
* Attach metadata to specific UTXOs
* Track provenance of individual token batches

**UI Distinction:**

* **Regular UTXOs**: No state data (or empty `00`). Displayed in "Available" balance.
* **Message UTXOs**: Non-empty state data. Displayed separately with the message content.

**Message Forwarding:** Message UTXOs can be forwarded to another address while preserving the state data. The "Forward" action sends the entire UTXO (all its satoshis) to the recipient with the same state data.

**Important:** State data is mutable and NOT part of the Token ID. Each transfer can set new state data for the recipient's UTXO.

#### Transaction Structure

**Genesis (Fungible):**

* Output 0: OP\_RETURN (metadata)
* Output 1: Token UTXO (P2PKH, sats = token units)
* Output 2+: Fee change (NOT token UTXOs)

**Transfer (Fungible):**

* Output 0: Recipient UTXO (P2PKH, transfer amount)
* Output 1: OP\_RETURN (metadata + proof chain)
* Output 2: Token change UTXO (P2PKH, remaining tokens)
* Output 3+: Fee change (NOT token UTXOs)

Critical: Only specific output indices carry token value. Fee change outputs (index 2+ for genesis, 3+ for transfer) must NOT be imported as token UTXOs.

#### Wallet UTXO Quarantine

To prevent accidental spending of token UTXOs as regular BSV, the wallet implements a quarantine mechanism:

**Safe UTXO Filter (`getSafeUtxos`):**

* UTXOs with `satoshis <= TOKEN_SATS` (currently 1 sat) are quarantined
* These UTXOs are excluded from the spendable balance
* Only "safe" UTXOs (satoshis > TOKEN\_SATS) can be used for fees or BSV sends

**Fungible Mode Consideration:** In fungible mode, token UTXOs have satoshis > 1, so the 1-sat quarantine doesn't protect them. Instead:

* The wallet tracks known fungible token UTXOs in the token store
* `getSpendableBalance()` excludes both NFT UTXOs (1-sat) and known fungible token UTXOs
* Users must explicitly use "Send" from the token card to spend fungible tokens

**Detection Flow:**

1. New UTXOs arrive at the wallet address
2. 1-sat UTXOs → quarantine zone (potential NFTs)
3. Larger UTXOs → check transaction history for MPT OP\_RETURN
4. If valid MPT found → import to appropriate token basket
5. If not MPT → available as regular BSV (unless already tracked as token)

### Token Verification Flow

A token is accepted into the wallet only after passing SPV verification. This applies to all incoming tokens — whether detected automatically from quarantined UTXOs, found during a manual scan, or returning from a previous transfer.

#### On Import (automatic gate)

When the wallet encounters an incoming token:

1. **Token identity check:** Recompute the Token ID from the claimed genesis transaction, output index, and immutable metadata fields (name, script, rules). If the computed ID doesn't match the claimed ID, the token is rejected. Note: tokenAttributes is **mutable** and not part of the Token ID check.
2. **Obtain the Merkle proof:** If the token arrives via a transfer, the proof chain is embedded in the OP\_RETURN data. If it's a genesis transaction with no proof chain yet, the wallet fetches the Merkle proof from the network. If no proof is available (transaction not yet confirmed), the token remains in quarantine until a future scan.
3. **Verify the genesis transaction was mined:** Using the Merkle proof for the genesis transaction, the wallet computes the Merkle root by hashing from the transaction ID up through the proof path using Bitcoin's double SHA-256. The computed root must match the root claimed in the proof entry.
4. **Confirm against the block header:** The wallet fetches the block header at the genesis transaction's block height and confirms that the block's Merkle root matches the one computed from the proof. This proves the genesis transaction was included in an actual mined block.

Only the genesis transaction's block header is required. Transfer transactions are already validated by miners when they are spent, so their inclusion in a block is an implicit guarantee.

#### On Manual Verify (retry)

When the user clicks "Verify," the wallet refreshes the same genesis-only verification procedure. This is used when the automated import could not complete verification (e.g. transaction not yet confirmed, network timeout, or time budget exceeded). The manual action queues the token for another verification pass using the same steps described above.

### Token Data Fields

#### Immutable Fields

All immutable fields are cryptographically bound to the Token ID. Tampering with any of them causes a Token ID mismatch -- instant verification failure. No additional checking logic needed for these fields; the existing `computeTokenId` check catches it.

**\[Token ID]**

* `SHA-256(genesisTxId || outputIndex LE || immutableChunkBytes)` where `immutableChunkBytes = tokenName + tokenScript + tokenRules`
* Deterministic, purely local computation. No network access required.
* `outputIndex` is the actual Bitcoin output index of the token's P2PKH in the genesis TX. Since Output 0 is the OP\_RETURN, token indices start at 1. Single mint = 1, batch mint = 1..N.
* `immutableChunkBytes` binds the shared collection identity (name, script, rules). **Important:** tokenAttributes is NOT included in this computation.

**\[Token Name]**

* UTF-8 text string.
* Shared across all tokens in the genesis transaction. Identifies the NFT set.
* Immutable after genesis. Included in Token ID computation.

**\[Token Script]** (default: empty)

* Optional field for additional miner-enforced consensus rules.
* Empty (zero-length pushdata) = no additional consensus rules; token ownership is enforced solely by the P2PKH output's standard 25-byte locking script (`OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG`).
* When non-empty, contains raw Bitcoin Script bytes defining extra validation constraints (e.g. issuer co-sign, Merkle whitelist, time locks, state mutation constraints).
* Immutable after genesis. Included in Token ID computation.

**\[Token Rules]**

* Arbitrary structured data defining token behaviour (application-level, wallet-enforced).
* The protocol does not prescribe a specific format — this field is flexible and can contain any binary data meaningful to the issuing application.
* Immutable after genesis. Included in Token ID computation.

**Example implementation (v05 prototype):** 8 bytes encoding supply, divisibility, restrictions, and version:

* **Supply** (uint16): Total number of whole tokens minted in this genesis transaction (max 65535).
* **Divisibility** (uint16): Number of fragments per whole token. 0 = NFT/indivisible. When > 0, the genesis TX mints supply × divisibility fragment UTXOs.
* **Transfer Restrictions** (uint16): Unrestricted, whitelist, time-lock, or custom wallet-enforced conditions.
* **Version** (uint16): Integer. Allows future rule extensions.

**Note on Divisible Tokens:** When divisibility > 0, each output index (1 through supply × divisibility) represents a fragment with its own unique Token ID. Example: supply=3, divisibility=2 creates 6 fragment UTXOs:

* Output 1 = NFT 1, piece 1/2
* Output 2 = NFT 1, piece 2/2
* Output 3 = NFT 2, piece 1/2
* Output 4 = NFT 2, piece 2/2
* Output 5 = NFT 3, piece 1/2
* Output 6 = NFT 3, piece 2/2

Each fragment can be transferred independently and is tracked with its own Token ID based on its genesisOutputIndex.

#### Mutable Fields

Checked by wallet application against Token Rules. Can be updated by the wallet app as well as on each transfer.

**\[Token Attributes]** (default: empty)

* **Mutable** data shared by all tokens in the NFT set. **NOT part of the Token ID computation**, allowing it to be updated on each transfer without affecting token identity.
* When unused, must still be present as zero-length pushdata (for positional parsing).
* All tokens within a single genesis TX share the same attributes value (though each transfer can update it independently).
* For tokens with different attributes (e.g. different rarity tiers), use separate genesis TXs (separate NFT sets).
* When a file is embedded, tokenAttributes contains the SHA-256 hash of the file (32 bytes). The full file data lives in a separate OP\_RETURN output in the genesis TX only. File verification: compute SHA-256(file bytes) and compare to stored hash. This design allows file size to not increase transfer TX costs.
* Examples: rarity tier, trait set, content hash, collection metadata, SHA-256 file hash.

**\[State Data]** (default: 0x00)

* Arbitrary bytes, minimum 1 byte. Usage defined by Token Rules.
* Always present (required for positional chunk parsing to distinguish genesis from transfer TXs).
* Examples: metadata hash, counter, status flag, IPFS CID.

#### Output Index Clarification

Understanding the distinction between genesisOutputIndex and currentOutputIndex is important for implementing transfers correctly:

**genesisOutputIndex**: The P2PKH output index in the genesis TX.

* **NFT Mode**: 1-based, never changes. For batch/divisible tokens: 1..N or 1..S×D (where S = supply, D = divisibility). This value is embedded in each transfer's OP\_RETURN to identify which specific fragment is being transferred.
* **Fungible Mode**: Always 1. All UTXOs share the same Token ID regardless of their actual output index.

**currentOutputIndex**: The current UTXO's output index.

* **NFT Mode**: In genesis TXs equals genesisOutputIndex. After first transfer: always 0 (P2PKH outputs in transfer TXs are always Output 0).
* **Fungible Mode**: In genesis TXs: 1. After transfers: 0 (recipient) or 2 (change). Fee change outputs at index 2+ (genesis) or 3+ (transfer) are NOT token UTXOs.

This distinction is critical for NFT mode because fragments from divisible tokens cannot be distinguished after the first transfer without carrying genesisOutputIndex in the OP\_RETURN. For fungible mode, the distinction ensures only valid token outputs are imported into the basket.

***

Open BSV License Version 5 – granted by BSV Association, Grafenauweg 6, 6300 Zug, Switzerland (CHE-427.008.338) ("Licensor"), to you as a user (henceforth "You", "User" or "Licensee").

For the purposes of this license, the definitions below have the following meanings:

"Bitcoin Protocol" means the protocol implementation, cryptographic rules, network protocols, and consensus mechanisms in the Bitcoin White Paper as described here <https://protocol.bsvblockchain.org>.

"Bitcoin White Paper" means the paper entitled 'Bitcoin: A Peer-to-Peer Electronic Cash System' published by 'Satoshi Nakamoto' in October 2008.

"BSV Blockchains" means: (a) the Bitcoin blockchain containing block height #556767 with the hash "000000000000000001d956714215d96ffc00e0afda4cd0a96c96f8d802b1662b" and that contains the longest honest persistent chain of blocks which has been produced in a manner which is consistent with the rules set forth in the Network Access Rules; and (b) the test blockchains that contain the longest honest persistent chains of blocks which has been produced in a manner which is consistent with the rules set forth in the Network Access Rules.

"Network Access Rules" or "Rules" means the set of rules regulating the relationship between BSV Association and the nodes on BSV based on the Bitcoin Protocol rules and those set out in the Bitcoin White Paper, and available here <https://bsvblockchain.org/network-access-rules>.

"Software" means the software the subject of this licence, including any/all intellectual property rights therein and associated documentation files.

BSV Association grants permission, free of charge and on a non-exclusive and revocable basis, to any person obtaining a copy of the Software to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to and conditioned upon the following conditions:

1 - The text "© BSV Association," and this license shall be included in all copies or substantial portions of the Software. 2 - The Software, and any software that is derived from the Software or parts thereof, must only be used on the BSV Blockchains.

For the avoidance of doubt, this license is granted subject to and conditioned upon your compliance with these terms only. In the event of non-compliance, the license shall extinguish and you can be enjoined from violating BSV's intellectual property rights (incl. damages and similar related claims).

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES REGARDING ENTITLEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS THEREOF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Version 0.1.1 of the Bitcoin SV software, and prior versions of software upon which it was based, were licensed under the MIT License, which is included below.

The MIT License (MIT)

Copyright (c) 2009-2010 Satoshi Nakamoto Copyright (c) 2009-2015 Bitcoin Developers Copyright (c) 2009-2017 The Bitcoin Core developers Copyright (c) 2017 The Bitcoin ABC developers Copyright (c) 2018 Bitcoin Association for BSV Copyright (c) 2023 BSV Association

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

***


# Identity-Linked Deterministic Token Verification Framework

**Author:**\
Jake Jones (<jake.jones@bsvassociation.org>)

**Status:** Draft\
**Version:** 1.0

**Required Dependencies:**

* BRC-0042 (Deterministic Key Derivation)
* BRC-0092 (Mandala Token Protocol)
* BRC-0107 (Commitment-Based Token Integrity)
* BRC-0108 (Identity-Linked Token Metadata)

**Optional Dependencies:**

* BRC-0043 (Purpose Identifiers)
* BRC-0067 (SPV Packaging Format)

***

## Abstract

BRC-115 defines a deterministic, SPV-verifiable framework for identity-linked token transfers on Bitcoin SV. It specifies how wallets verify compliance requirements using BRC-0042 key derivation, BRC-0107 commitments, and BRC-0108 identity bindings — without relying on overlay networks or external validators.

This standard ensures identity-restricted token transfers remain interoperable, decentralized, portable, and enforceable while enabling selective privacy, regulatory compliance, and recovery options within the original peer-to-peer Bitcoin model.

***

## Motivation

Existing BSV token standards provide token encoding (BRC-0092), verifiable state transitions (BRC-0107), and identity metadata (BRC-0108). However, none define:

* How identity-based constraints are enforced
* How signatures bind identities to transaction lineage
* How SPV wallets validate compliance without a trusted intermediary
* How field-level revelation is performed and verified consistently

BRC-115 introduces a unifying rule set that makes identity-restricted token usage deterministic, verifiable, and portable between software implementations.

***

## Architecture Overview

```mermaid
flowchart TB

    Wallet["Wallet / Client Logic"]
    Core["Protocol Enforcement Layer"]
    Ledger["SPV Proof / Bitcoin Ledger"]

    Wallet -->|Enforces BRC-115| Core
    Core --> Ledger

    subgraph Stack["Verification Stack"]
        D["BRC-0092:<br/>Base Token Encoding"]
        C["BRC-0107:<br/>Commitment Verification"]
        B["BRC-0042:<br/>Deterministic Key Derivation"]
        A["BRC-0108:<br/>Identity Certificate Link"]
    end

    Core --- Stack
```

***

## Terminology

| Term                            | Definition                                                                                 |
| ------------------------------- | ------------------------------------------------------------------------------------------ |
| **Identity-Linked Token (ILT)** | A token output containing a valid BRC-0108 certificate hash.                               |
| **Compliance Profile**          | A structured set of rules defining identity requirements for token usage.                  |
| **Derivation Scope**            | The deterministic key domain created using BRC-0042.                                       |
| **Selective Revelation Bundle** | A minimally sufficient encrypted proof package revealing only required certificate fields. |
| **SPV Proof Package**           | A format containing transaction data plus Merkle proof (optionally BRC-0067-formatted).    |

***

## Normative Rules

### 1. Identity Binding

A token SHALL be treated as identity-linked if:

1. The token output contains a BRC-0108 certificate hash, AND
2. The spending signature is produced using a key derived per BRC-0042 from the certificate root.

If either condition is not met, BRC-115 SHALL NOT apply.

### 2. Deterministic Key Derivation

All operational keys MUST be derived using:

`BRC-0042(master_certificate_entropy, assetId, purposeId)`

Use of BIP-32, SLIP-10, hardened derivation paths, or external hierarchical schemes is **NOT permitted.**

### 3. Signature Requirements

Identity-linked token transfers MUST be signed using:

`SIGHASH_SINGLE | SIGHASH_ANYONECANPAY`

Exceptions are permitted ONLY in:

* Custodial batch aggregation
* Minting
* Time-locked recovery workflows

Such exceptions MUST be explicitly signaled using BRC metadata.

### 4. Protocol Signaling

BRC-115 shall activate under a **hybrid signaling model**:

* **Implicit activation:** Any transaction using identity metadata (BRC-0108) and BRC-0042-derived signatures SHALL be treated as BRC-115-compliant.
* **Explicit activation (optional):** A protocol identifier MAY appear in metadata (e.g., `0115`, `0115.1`) for version governance.

Explicit signaling supersedes implicit interpretation.

### 5. Mandatory Verification Flow

A compliant verifying wallet MUST validate in the following order:

1. Token encoding validity (BRC-0092)
2. Commitment integrity (BRC-0107)
3. SPV validity (Merkle proof)
4. Identity certificate presence & authenticity (BRC-0108)
5. Deterministic derivation match (BRC-0042)
6. Compliance profile evaluation (rules + selective revelation)

Failure at any stage **MUST** reject the transfer.

***

## Compliance Profiles

| Profile              | Required Identity Level          | Typical Use                      |
| -------------------- | -------------------------------- | -------------------------------- |
| **Open**             | None                             | Standard fungible tokens         |
| **Identity-Present** | Certificate exists               | Membership, access tokens        |
| **Restricted**       | Field-level selective revelation | Age verification, region gating  |
| **Regulated**        | Certificate type + rules         | Securities and compliance tokens |
| **Recovery-Capable** | Secondary derivation             | Lost-key account recovery        |

***

## Verification Process

```mermaid
flowchart TD
  A[Parse Token] --> B[Commitment Check]
  B --> C[SPV Check]

  C --> D{Identity Linked?}

  D -->|No| H[ACCEPT]

  D -->|Yes| E[Certificate + Key Binding Check]
  E --> F[Compliance Profile Enforcement]
  F --> G{Accept?}
  G -->|Yes| H2[ACCEPT]
  G -->|No| I[REJECT]
```

***

## Reference Implementation Guidance (Non-Normative)

Developers SHOULD:

* Cache certificates to avoid repeated revelation requests
* Use temporary encryption keys for field-level exchanges
* Display meaningful compliance failure messages to improve UX
* Support offline verification when proof bundles are present

***

## Test Vectors

### Vector 1 — Valid Identity-Linked Transfer

**Expected Result:** Accept

### Vector 2 — Invalid Key Derivation

**Expected Result:** Reject

### Vector 3 — Identity Valid, Compliance Failed (e.g., age restriction)

**Expected Result:** Reject

Additional machine-readable vectors SHALL accompany the reference implementation.

***

## Backward Compatibility

| Case                                                       | Result                     |
| ---------------------------------------------------------- | -------------------------- |
| Non-identity tokens                                        | Unaffected                 |
| Legacy wallets                                             | MAY ignore identity fields |
| Identity-linked tokens spent without proper key derivation | MUST reject                |

***

## Security Considerations

* Commitment chaining prevents replay attacks
* SPV trust model maintained; no overlay dependency
* Selective revelation prevents unnecessary personal data exposure
* Recovery derivation prevents permanent loss without custodianship
* Prohibition of non-BRC-0042 HD schemes prevents fragmentation

***

## References

* BRC-0042: Deterministic Key Derivation
* BRC-0092: Mandala Token Protocol
* BRC-0107: Commitment-Based Token Integrity
* BRC-0108: Identity-Linked Token Metadata
* BRC-0067: (Optional) SPV Packaging Format


# Proof-of-Indexing Hash-to-Mint Tokens

Richard Boase (<richard@b0ase.com>)

## Abstract

This BRC defines a mechanism for minting BSV-21 tokens via Proof-of-Work where the hash preimage includes an **indexing work commitment** — a merkle root of verifiable overlay network activity. The token supply is locked in an sCrypt smart contract at deployment. The contract verifies the PoW solution on-chain and permanently records the work commitment, while an L2 gossip network verifies the honesty of the claimed work off-chain. This creates an economically incentivized, decentralized overlay network where nodes are rewarded for indexing BSV-21 token activity, serving content, and maintaining network connectivity.

## Motivation

### The Overlay Network Bootstrap Problem

BSV overlay networks ([BRC-22](https://github.com/bsv-blockchain/BRCs)) require nodes to index and serve specialized transaction data. Currently, overlay operation depends on centralized service providers (GorillaPool, WhatsOnChain) or volunteer node operators with no economic incentive. This creates single points of failure and limits decentralization.

The fundamental question for any overlay network is: **who runs the nodes, and why?**

### The Indexer Incentive Gap

BSV-21 tokens[<sup>1</sup>](#footnote-1) enable rich on-chain economies — content access tokens, domain tokens, creator tokens — but these economies require indexing infrastructure to function. Someone must watch the chain, track transfers, maintain state, and serve queries. Without economic incentives, this work falls to a small number of centralized operators.

### The PoW20 Limitation

The existing POW-20 protocol[<sup>2</sup>](#footnote-2) uses inscription-based minting where PoW solutions are embedded as JSON in transaction data. Validation depends entirely on off-chain indexers:

1. The BSV network does not verify that the PoW solution is valid
2. Different indexers can disagree on token state
3. A single corrupted indexer can produce an inconsistent ledger
4. No on-chain record of what work was claimed

### This Proposal

BRC-116 solves these problems by:

1. **Moving PoW verification on-chain** via an sCrypt Hash-to-Mint smart contract ([BSV-21](https://docs.1satordinals.com/fungible-tokens/bsv-21))
2. **Binding mining to indexing work** by including a work commitment in the hash preimage
3. **Creating a permanent on-chain audit trail** of all claimed network activity
4. **Using L2 gossip consensus** to verify work honesty, with economic penalties for dishonesty

The result is a self-sustaining overlay network where running a node is profitable, honesty is economically rational, and no centralized indexer is required.

## Specification

### 1. Token Standard

BRC-116 tokens are BSV-21 fungible tokens[<sup>1</sup>](#footnote-1) deployed via the `deploy+mint` operation. The entire token supply is locked in a single sCrypt smart contract UTXO at genesis. Tokens enter circulation only when a miner successfully calls the contract's `mint` method with a valid Proof-of-Work solution.

The token is identified by its genesis outpoint (`<txid>_<vout>`), not by a ticker string. This eliminates ticker squatting and ensures globally unique identification.

### 2. Hash-to-Mint Contract

#### 2.1 Contract State

The contract maintains two stateful properties that change with each mint:

| Property    | Type     | Description                                           |
| ----------- | -------- | ----------------------------------------------------- |
| `supply`    | `bigint` | Remaining unminted tokens. Decremented each mint.     |
| `mintCount` | `bigint` | Total successful mints. Used for halving calculation. |

The contract maintains four immutable properties set at deployment:

| Property          | Type     | Description                                    |
| ----------------- | -------- | ---------------------------------------------- |
| `lim`             | `bigint` | Base tokens per mint (before halving).         |
| `difficulty`      | `bigint` | PoW difficulty target. See Section 2.3.        |
| `halvingInterval` | `bigint` | Number of mints per halving era.               |
| `max`             | `bigint` | Maximum total supply (inherited from BSV20V2). |

#### 2.2 The `mint` Method

The contract exposes a single public method:

```
mint(dest: Addr, nonce: ByteString, workCommitment: ByteString)
```

**Parameters:**

| Parameter        | Size     | Description                                                  |
| ---------------- | -------- | ------------------------------------------------------------ |
| `dest`           | 20 bytes | Miner's P2PKH address. Receives minted tokens.               |
| `nonce`          | Variable | Miner's solution. Iterated until hash meets difficulty.      |
| `workCommitment` | 32 bytes | SHA-256 merkle root of claimed indexing work. See Section 3. |

**Execution flow:**

1. **Validate inputs.** Assert `len(workCommitment) == 32` and `len(nonce) > 0`.
2. **Build PoW challenge.** Concatenate:

   ```
   challenge = prevTxId || workCommitment || dest || nonce
   ```

   Where `prevTxId` is `this.ctx.utxo.outpoint.txid` (32 bytes) — the transaction ID of the current contract UTXO. This value changes with every mint, preventing pre-computation of solutions.

   Including `dest` prevents mempool front-running: a solution is only valid for the miner who computed it.

   Including `workCommitment` binds the PoW to specific claimed work.
3. **Verify PoW.** Compute `h = SHA256(SHA256(challenge))`. Assert that `h` meets the difficulty target (see Section 2.3).
4. **Calculate mint amount.** Compute the halving era: `era = mintCount / halvingInterval`. The mint amount is `lim / 2^era`. Assert `amount > 0`.
5. **Decrement supply.** `supply -= amount`. Assert `supply >= 0`.
6. **Increment mint count.** `mintCount += 1`.
7. **Build outputs.**
   * If `supply > 0`: Output 0 is the state continuation UTXO (contract with updated state).
   * Next output: P2PKH transfer of `amount` tokens to `dest`.
   * Final output: Change (satoshis for transaction fees).
8. **Enforce output integrity.** Assert `SHA256(SHA256(outputs)) == this.ctx.hashOutputs`. This is the standard sCrypt output enforcement that prevents any manipulation of the transaction outputs.

#### 2.3 Difficulty Representation

**OPEN QUESTION 1: Difficulty representation format.**

Two approaches are viable:

**Option A — Leading zero bytes.** The `difficulty` property is an integer representing the number of leading zero bytes required in the hash output. Simple to implement but coarse-grained (each increment is 256x harder).

```
difficulty = 3  →  hash must start with 0x000000...
difficulty = 4  →  hash must start with 0x00000000...
```

Verification: `slice(h, 0, difficulty) == bytes(0, difficulty)`

**Option B — Integer target comparison.** The `difficulty` property is a 256-bit integer target. The hash (interpreted as a big-endian unsigned integer) must be less than the target. This allows fine-grained difficulty adjustment (same as Bitcoin's nBits).

```
hashInt = bigEndianToInt(h)
assert(hashInt < target)
```

Option B is recommended for production deployments. Option A is acceptable for initial testnet deployments.

**We request community feedback on which representation to standardize.**

#### 2.4 Halving

The mint amount decreases by half every `halvingInterval` mints:

| Era | Mints                                       | Amount per Mint |
| --- | ------------------------------------------- | --------------- |
| 0   | 1 – `halvingInterval`                       | `lim`           |
| 1   | `halvingInterval+1` – `2*halvingInterval`   | `lim / 2`       |
| 2   | `2*halvingInterval+1` – `3*halvingInterval` | `lim / 4`       |
| N   | ...                                         | `lim / 2^N`     |

When `lim / 2^era` rounds to zero, no more tokens can be minted and the contract terminates.

**Implementation note:** sCrypt does not support variable bit-shift operations. The halving must be implemented as a chain of conditional divisions:

```
amount = lim
if (era >= 1) { amount = amount / 2 }
if (era >= 2) { amount = amount / 2 }
...
```

The number of conditional branches determines the maximum number of halving eras the contract supports. Eight branches covers >99.6% of supply for typical configurations.

#### 2.5 Contract Deployment

A BRC-116 token is deployed as a single BSV-21 `deploy+mint` transaction. The contract constructor receives all immutable parameters. The `id` field is empty at deployment and auto-initialized to `<txid>_<vout>` on first mint via the BSV20V2 base class.

The deployment transaction creates one output: the contract UTXO carrying the full token supply. This is the genesis of the token.

**OPEN QUESTION 2: Parallel contract UTXOs.**

Since the contract is a single UTXO chain, only one mint can succeed per BSV block for a given contract instance. Under high competition, most miners' transactions will fail (double-spend of the same contract UTXO).

Possible mitigations:

* **Option A — Single UTXO.** Accept contention as a natural rate limiter. Failed miners retry on the next UTXO. Simple but limits throughput to one mint per contract UTXO step (i.e., one per block at most).
* **Option B — Parallel UTXOs at genesis.** Deploy N contract instances in the genesis transaction, each with `max/N` supply. Miners target different instances. Increases throughput N-fold but complicates supply tracking.
* **Option C — Splittable UTXOs.** Add a `split` method to the contract that divides a single UTXO into two child UTXOs with proportional supply. Allows dynamic scaling but adds contract complexity.

**We request community feedback on which approach to standardize.**

### 3. Proof of Indexing

The `workCommitment` parameter in the `mint` method is a 32-byte SHA-256 merkle root representing indexing work the miner claims to have performed. The contract records this value permanently on-chain but does not interpret it. Interpretation and verification happen at L2 (Section 4).

#### Enforcement Boundaries

It is important to state the precise security guarantees of this system. The title "Proof-of-Indexing" refers to a two-layer verification model, not a single on-chain truth:

* **L1 (on-chain) proves:** (a) that sufficient Proof-of-Work was performed, and (b) that a specific work commitment (merkle root) was permanently and immutably recorded at the time of mining. The contract does not and cannot verify that the committed data represents honest indexing.
* **L2 (off-chain) proves:** that the work commitment represents genuine overlay network activity, verified probabilistically via peer gossip, challenge-response protocols, and reputation scoring.

"Proof-of-Indexing" is therefore an **economic and reputational guarantee**, not a deterministic on-chain truth. The on-chain record provides an immutable audit trail; the off-chain network provides the honesty verification. This is analogous to how Bitcoin's Proof-of-Work proves energy expenditure on-chain while the economic incentive structure (block rewards, fees) makes honest mining rational off-chain.

#### 3.1 Work Items

A work item represents a discrete unit of overlay network activity:

```typescript
interface WorkItem {
    id: string        // SHA-256(type || data || timestamp), hex-encoded
    type: WorkType    // Category of work performed
    data: string      // JSON-encoded type-specific proof data
    timestamp: number // Unix milliseconds when work was performed
}
```

#### 3.2 Work Types

| Type              | Trigger                                                           | Proof Data                                                                     |
| ----------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `tx_indexed`      | Client observes and records a BSV-21 token transfer               | `{ txid, tokenId, from, to, amount }`                                          |
| `content_served`  | Client serves content to a peer who presented a valid ticket      | `{ contentHash, requesterPeerId, bytesServed, ticketTokenId, stampSignature }` |
| `stamp_validated` | Client validates a ticket's stamp chain (all signatures)          | `{ ticketUtxo, chainLength, isValid, tokenId }`                                |
| `market_indexed`  | Client indexes a listing or sale on a BSV-21 marketplace          | `{ listingTxid, tokenId, action, priceSats }`                                  |
| `x402_settled`    | Client indexes an x402 payment settlement (HTTP 402 micropayment) | `{ challengeHash, paymentTxid, amountSats, domain, path, scheme }`             |
| `peer_relayed`    | Client forwards a valid gossip message to peers                   | `{ messageHash, messageType, relayedTo }`                                      |

**OPEN QUESTION 3: Work type extensibility.**

Should the set of valid work types be fixed in this specification, or should it be extensible? Extensibility allows future overlay networks to define new work types, but fixed types are simpler to verify across implementations.

**We request community feedback.**

#### 3.3 Work Commitment Construction

1. Collect work items from an in-memory mempool (minimum 5, maximum configurable).
2. Sort items by `id` field (lexicographic, ascending). This ensures deterministic ordering across implementations.
3. Compute a binary Merkle tree:
   * Leaf nodes: `SHA-256(workItem.id || workItem.type || workItem.data || workItem.timestamp)`
   * Internal nodes: `SHA-256(leftChild || rightChild)`
   * Odd leaf count: duplicate the last leaf.
4. The 32-byte root hash is the `workCommitment`.

**OPEN QUESTION 4: Minimum work quality.**

Should the specification define a minimum ratio of "substantive" work items (e.g., `tx_indexed`, `content_served`) versus "passive" items (e.g., `peer_relayed`)? Without this, a node could fill its mempool with trivial relay events and mine with minimal useful work.

Options:

* **Option A — No minimum.** Any valid work items are acceptable. The market (peer reputation) handles quality.
* **Option B — Type quotas.** At least 50% of items must be `tx_indexed` or `content_served`.
* **Option C — Weighted items.** Different types have different "weights" and the commitment must reach a minimum total weight.

**We request community feedback.**

### 4. L2 Work Verification

The on-chain contract guarantees that PoW was performed and records the work commitment. The L2 gossip network verifies that the work commitment represents honest indexing.

#### 4.1 Gossip Topic

Work verification uses a dedicated GossipSub topic:

```
$402/mining/v1
```

This topic carries three message types: `MINING_ANNOUNCEMENT`, `WORK_CHALLENGE`, and `WORK_RESPONSE`.

**Authentication:** Gossip messages on this topic are explicitly **unauthenticated** — any node may publish and subscribe without identity verification. Implementations that wish to run the mining gossip over authenticated channels SHOULD use BRC-103[<sup>12</sup>](#footnote-12) mutual authentication semantics over HTTP transport (BRC-104[<sup>3</sup>](#footnote-3)). Because the default mode is unauthenticated, reputation rules (Section 4.3) MUST assume Sybil-heavy conditions: a single entity may operate multiple peer identities.

#### 4.2 Mining Announcement

When a node successfully mints tokens, it broadcasts a `MINING_ANNOUNCEMENT`:

```typescript
interface MiningAnnouncement {
    mint_txid: string          // On-chain HTM contract spend txid
    contract_utxo: string      // The contract UTXO that was spent (<txid>:<vout>)
    nonce: string              // The winning nonce (hex)
    work_commitment: string    // The 32-byte merkle root (hex)
    miner_address: string      // Reward recipient (base58check)
    work_items: WorkItem[]     // The items behind the merkle root
    merkle_proof: string[]     // Intermediate hashes for merkle verification
    era: number                // Current halving era
    amount: number             // Tokens minted
    timestamp: number          // Unix ms
    signature: string          // ECDSA signature by miner's key
}
```

Nodes MUST broadcast `MINING_ANNOUNCEMENT` within 60 seconds of a successful mint. Nodes that consistently fail to announce lose reputation but are not penalized on-chain.

#### 4.3 Peer Verification Protocol

Receiving nodes MUST perform the following verification steps:

1. **Merkle verification.** Recompute the merkle root from the provided `work_items`. If the computed root does not match `work_commitment`, the miner is flagged as **dishonest**. This check is deterministic and cannot produce false positives.
2. **On-chain verification.** Verify that `mint_txid` exists on-chain and spends the claimed `contract_utxo`. This confirms the PoW was accepted by the network.
3. **Work item plausibility.** For each work item, verify against local state:

   * `tx_indexed`: Does the claimed `txid` exist in the verifier's local index? Does the data match?
   * `content_served`: Is the `stampSignature` valid for the claimed content and requester?
   * `market_indexed`: Does the claimed listing exist?
   * `peer_relayed`: Was the claimed `messageHash` observed by the verifier?

   Compute a **plausibility score**: `verifiedItems / totalItems`. A score below 0.5 flags the miner as **suspicious**.

   **Note:** Plausibility verification is probabilistic. Nodes see different subsets of network activity. An honest miner should achieve \~60-90% plausibility across diverse verifiers, not necessarily 100%.

   **Sybil resistance:** Because the gossip layer is unauthenticated (Section 4.1), reputation scores are **local to each node**, not globally consistent. A node MUST NOT assume that its ban list or reputation scores are shared by or authoritative for other nodes. Strong penalties (reputation set to 0, ban) SHOULD only be applied after corroboration from at least **3 independent verifiers** to reduce the impact of Sybil peers submitting false challenges or false verification results. Implementations MAY additionally require identity costs (e.g., a minimum on-chain history of successful mints, or a staking deposit per BRC-116 Open Question 5) to raise the cost of Sybil identities.
4. **Reputation update.** Adjust the miner's reputation score:
   * Merkle match + plausibility >= 0.7: increase reputation
   * Merkle match + plausibility 0.5-0.7: no change
   * Merkle match + plausibility < 0.5: decrease reputation
   * Merkle mismatch: set reputation to 0, add to ban list

#### 4.4 Work Challenge / Response

Nodes MAY issue targeted challenges to verify specific work claims after the fact:

**WORK\_CHALLENGE:**

```typescript
interface WorkChallenge {
    challenge_id: string         // Unique ID for this challenge
    target_mint_txid: string     // Which mint to challenge
    requested_item_ids: string[] // Specific work item IDs to prove
}
```

**WORK\_RESPONSE:**

```typescript
interface WorkResponse {
    challenge_id: string
    proofs: {
        item_id: string
        merkle_path: string[]   // Merkle path from item to root
        item_data: string       // Full work item data
    }[]
}
```

A node that fails to respond to a `WORK_CHALLENGE` within 30 seconds loses reputation. A node that responds with data that does not verify against the on-chain `workCommitment` is flagged as dishonest.

#### 4.5 Economic Security Analysis

| Miner Behavior                 | On-Chain Outcome                               | L2 Outcome           | Net Result                                                  |
| ------------------------------ | ---------------------------------------------- | -------------------- | ----------------------------------------------------------- |
| Valid PoW + honest work        | Tokens minted                                  | Full peer trust      | Profitable: mining income + marketplace access              |
| Valid PoW + fake work          | Tokens minted                                  | Peers reject, banned | Unprofitable: tokens minted but cannot sell, no marketplace |
| Valid PoW + empty/trivial work | Tokens minted                                  | Reduced reputation   | Marginal: tokens minted but deprioritized by peers          |
| Invalid PoW                    | Transaction rejected by Transaction Processors | N/A                  | No tokens, wasted computation                               |

**Terminology note:** In BSV, Transaction Processors (TPs) validate scripts and enforce consensus rules, rejecting invalid or double-spend attempts from the mempool. Miners package TP-validated transactions into blocks via Proof-of-Work. BRC-116's L1 security depends on both: TPs enforce the contract's spending conditions (PoW validity, supply checks, output integrity), and miners provide finality by including the valid transaction in a block.

The key economic insight: **$402 tokens are only valuable if you can trade them.** Trading happens on the L2 overlay network, where your reputation determines your access to trading partners. A miner who cheats on work commitments can mint tokens but has no market for them.

**OPEN QUESTION 5: Can the economic penalty for fake work be strengthened?**

In the current design, a dishonest miner still receives tokens on-chain — they just can't sell them easily on the overlay. If $402 has exchange listings outside the overlay (e.g., on a centralized exchange), the penalty is weaker. Possible mitigations:

* **Option A — Accept the current model.** Overlay reputation is sufficient for early-stage networks.
* **Option B — Staking requirement.** Require miners to lock BSV satoshis in a separate contract before mining. Dishonest miners lose their stake via a fraud proof.
* **Option C — Coinbase maturity.** Minted tokens are unspendable for N blocks (analogous to Bitcoin's 100-block coinbase maturity rule). During the maturity window, L2 peers verify the work commitment. Dishonest miners receive tokens but suffer reputation damage before tokens become liquid. No burning or fraud proofs required — the delay provides a verification window while the penalty remains economic (market exclusion).

**We request community feedback on whether strengthened penalties are necessary.**

### 5. Difficulty Adjustment

#### 5.1 Target Rate

The recommended target is **1 successful mint per minute** (1,440 per day). This balances token distribution speed with network activity.

#### 5.2 Adjustment Period

Difficulty is evaluated every **144 successful mints** (\~2.4 hours at target rate).

#### 5.3 Adjustment Algorithm

```
Let elapsed = time between mint (mintCount - 144) and mint (mintCount)
Let target  = 144 * 60 seconds = 8,640 seconds
Let ratio   = elapsed / target

If ratio < 0.5:  difficulty increases (solutions coming too fast)
If ratio > 2.0:  difficulty decreases (solutions coming too slow)
Otherwise:        difficulty unchanged
```

**Time source:** If on-chain difficulty adjustment is used (Option B below), the contract requires a reliable measure of elapsed time. Viable approaches include: (a) using `nLockTime` constraints to embed block height or timestamp in the spending transaction, allowing the contract to compute elapsed time between adjustment epochs; (b) reading block header timestamps via sCrypt's transaction context if available; or (c) tracking only `mintCount` (already in state) and relying on the off-chain network to supply wall-clock time as a signed oracle input. Approach (a) is recommended as it requires no external oracle and is enforceable by Transaction Processors.

#### 5.4 Difficulty Bounds

Implementations MUST define a **floor** (easiest possible target) to prevent trivially easy mining. The recommended floor is 2 leading hex zeros (`00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`).

Implementations SHOULD NOT impose an artificial **ceiling** (hardest possible target). Difficulty must be allowed to scale without bound as network hashrate grows. The natural ceiling is determined by physical limits (thermodynamics, chip fabrication, energy cost) — not by software. This is consistent with the design goal that PoW nodes scale into large commercial operations: an artificial ceiling would cap the incentive to invest in more powerful hardware, undermining the accountability that difficulty-driven scaling provides.

**OPEN QUESTION 6: On-chain vs off-chain difficulty adjustment.**

* **Option A — Fixed on-chain difficulty.** The contract's difficulty never changes. Simple and trustless but cannot adapt to changing hashrate.
* **Option B — Stateful on-chain adjustment.** The contract tracks `mintCount` and block timestamps, adjusting `difficulty` (a `@prop(true)`) according to the algorithm above. More trustless but adds significant contract complexity.
* **Option C — Off-chain adjustment with contract migration.** The overlay network reaches gossip consensus on new difficulty. A new contract is deployed with the adjusted difficulty, and remaining supply is migrated via a `migrate` method on the old contract. Flexible but requires a migration protocol.

**We request community feedback on which approach to standardize.**

### 6. Content Token Integration

BRC-116 is designed to incentivize overlay networks that index BSV-21 content tokens. Content tokens are separate BSV-21 tokens deployed by content issuers and are NOT part of this specification. However, this section describes the intended interaction pattern.

#### 6.1 Token Separation

| Concern           | Token          | Standard          | Deployed By                | Regulation                |
| ----------------- | -------------- | ----------------- | -------------------------- | ------------------------- |
| Overlay incentive | $402 (BRC-116) | BSV-21 HTM        | Smart contract (no issuer) | Commodity-like (pure PoW) |
| Content access    | $EXAMPLE       | BSV-21 (standard) | Content issuer             | Issuer's responsibility   |

BRC-116 tokens are not required to access content. Users purchase content tokens directly with BSV. BRC-116 tokens incentivize the overlay infrastructure that makes content token economies possible.

#### 6.2 Ticket Stamp Chains

Content tokens function as tickets with cryptographic provenance (see BRC-104[<sup>3</sup>](#footnote-3) for the authentication substrate). When a node serves content in exchange for a ticket, it creates a stamp — a signed record of the serve event. These stamps form a chain of provenance that is both a quality signal and a source of `content_served` work items for BRC-116 mining.

Where HTTP endpoints are monetized (e.g., paid content serving that generates `content_served` work items), implementations SHOULD use the x402 Stateless Settlement-Gated HTTP Protocol[<sup>14</sup>](#footnote-14) to define the payment challenge/response flow. x402 defines a deterministic use of HTTP status code 402 where clients prove BSV payment via `X402-Challenge` and `X402-Proof` headers, with replay protection achieved through UTXO nonce spending. This is the RECOMMENDED approach for BRC-116 content serving monetization, as it provides stateless, account-free payment authorization that aligns with the overlay network's UTXO-based architecture. Implementations MAY also use BRC-105[<sup>4</sup>](#footnote-4) as an alternative HTTP monetization framework.

Each x402 settlement observed on the network generates an `x402_settled` work item (Section 3.2), creating a virtuous cycle: the payment layer feeds the indexing layer, and the indexing layer serves the payment layer.

#### 6.3 Relationship to BRC-24 Lookup Services

"Proof-of-Indexing" fundamentally incentivizes the provision of lookup capability as described in BRC-24[<sup>13</sup>](#footnote-13). The `tx_indexed` and `market_indexed` work types correspond directly to BRC-24 query handling — a node that indexes token transfers and marketplace activity is providing the data layer that BRC-24 lookup services query. While BRC-116 overlay nodes are not required to expose a BRC-24 interface, the work they perform (indexing, serving, validating) is the same work that underpins BRC-24 lookup availability.

### 7. Reference Parameters

The following parameters are recommended for the initial deployment of a BRC-116 token:

| Parameter         | Recommended Value      | Rationale                                                                                                                                                      |
| ----------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `max`             | 21,000,000             | Familiar supply cap, sufficient for large networks                                                                                                             |
| `lim`             | 1,000                  | Balanced: not so large that early miners dominate, not so small that mining is unrewarding                                                                     |
| `difficulty`      | 5 (leading zero bytes) | Initial difficulty only. \~1 in 1,048,576 per hash. CPU-mineable on commodity hardware at launch. Scales upward without bound as hashrate grows (Section 5.4). |
| `halvingInterval` | 10,500                 | With 1 mint/minute target: first halving after \~7.3 days. Fast enough to create scarcity.                                                                     |
| `decimals`        | 0                      | Whole tokens. Simplifies contract logic.                                                                                                                       |

These values are recommendations. Deployers MAY choose different parameters.

### 8. BRC-100 Wallet Separation

A compliant BRC-116 mining implementation MUST allow the miner to use any BRC-100[<sup>11</sup>](#footnote-11) compliant wallet for key management, signing, encryption, and transaction submission. The mining client is an **application** that requests operations from a wallet, not a wallet itself.

Specifically, a conforming mining client MUST be able to:

1. **Request a destination address** (`dest`) from the wallet for receiving minted tokens.
2. **Construct the contract-spend transaction template** (the unsigned mint transaction with PoW solution, work commitment, and outputs).
3. **Request the wallet to sign** any required inputs (funding inputs for transaction fees, change outputs).
4. **Submit the signed transaction** for broadcast via the wallet's broadcast interface, or via any standard broadcast endpoint.

A reference implementation MAY include an integrated wallet for testing and development purposes, but a production deployment MUST NOT require any specific wallet implementation. This ensures that BRC-116 mining remains vendor-neutral and interoperable with the broader BSV wallet ecosystem.

### 9. sCrypt Implementation Notes

#### 9.1 Dependencies

* `scrypt-ts` >= 1.0.0 — sCrypt TypeScript SDK
* `scrypt-ord` >= 1.0.0 — BSV-21/Ordinals contract base classes

#### 9.2 Base Class

BRC-116 contracts MUST extend `BSV20V2` from `scrypt-ord`. This base class provides:

* `buildStateOutputFT(amt)` — Builds the state continuation output with updated token amount
* `BSV20V2.buildTransferOutput(addr, id, amt)` — Builds a P2PKH transfer output with BSV-20 inscription
* `buildChangeOutput()` — Builds the change output for transaction fees
* `isGenesis()` / `initId()` — Genesis detection and token ID initialization

#### 9.3 Hash Functions

* `hash256(x)` — Double SHA-256: `SHA256(SHA256(x))`. Used for PoW verification.
* `sha256(x)` — Single SHA-256. Used for individual work item hashing.

Both are available as Bitcoin Script opcodes and execute natively in the BSV VM.

#### 9.4 Output Enforcement

Every `mint` call MUST include:

```
assert(hash256(outputs) == this.ctx.hashOutputs)
```

This ensures the transaction outputs exactly match what the contract specifies, preventing any output manipulation.

#### 9.5 Script Size Considerations

**RESOLVED — Contract script size.**

The reference contract has been compiled using sCrypt compiler v1.20.0. Results:

| Component                                         | Script Size  |
| ------------------------------------------------- | ------------ |
| BSV20V2 base class (Ordinal + Shift10 libraries)  | 22.81 KB     |
| BRC-116 additions (PoW, halving, work commitment) | 1.10 KB      |
| **Total compiled script**                         | **23.91 KB** |

For comparison, a minimal `anyonecanmint` BSV20V2 contract (no PoW, no custom logic) compiles to 22.81 KB. The 23 KB base cost is inherent to the BSV-21 Ordinal inscription library and is shared by all deployed BSV-21 HTM tokens on mainnet.

At BSV's standard fee rate of 0.5 sat/byte, each mint transaction costs approximately 15,000 sats (\~$0.01 USD) including inputs, outputs, and fees. This is economically viable for CPU mining at any non-zero $402 token price.

The 24 KB script is well within BSV's miner relay limits (\~10 MB typical) and poses no propagation or validation concerns.

## Open Questions Summary

This specification contains six open questions requiring community input before finalization, plus one resolved item:

| # | Question                               | Section | Status       | Options                                                  |
| - | -------------------------------------- | ------- | ------------ | -------------------------------------------------------- |
| 1 | Difficulty representation format       | 2.3     | **Open**     | A: Leading zero bytes, B: Integer target comparison      |
| 2 | Parallel contract UTXOs for throughput | 2.5     | **Open**     | A: Single UTXO, B: Parallel at genesis, C: Splittable    |
| 3 | Work type extensibility                | 3.2     | **Open**     | Fixed set vs extensible registry                         |
| 4 | Minimum work quality requirements      | 3.3     | **Open**     | A: No minimum, B: Type quotas, C: Weighted items         |
| 5 | Strengthened penalties for fake work   | 4.5     | **Open**     | A: Reputation only, B: Staking, C: Coinbase maturity     |
| 6 | Difficulty adjustment mechanism        | 5.3     | **Open**     | A: Fixed, B: Stateful on-chain, C: Off-chain + migration |
| 7 | Contract script size feasibility       | 9.5     | **Resolved** | 23.91 KB compiled — within BSV limits, \~$0.01/mint      |

Feedback may be submitted as issues on the [BRC repository](https://github.com/bsv-blockchain/BRCs) or discussed in the BSV developer community channels.

## Implementations

1. **path402 client** — Reference implementation (in development). Repository: [github.com/b0ase/path402](https://github.com/b0ase/path402). Includes mining engine, gossip network, content store, and web GUI.
2. **$402 HTM contract** — sCrypt smart contract (pending deployment). Will be deployed to BSV testnet for community testing before mainnet launch.

## References

1. BSV-21 Token Standard. [docs.1satordinals.com/fungible-tokens/bsv-21](https://docs.1satordinals.com/fungible-tokens/bsv-21)
2. POW-20 Protocol. [protocol.pow20.io](https://protocol.pow20.io/)
3. BRC-104: HTTP Transport for Mutual Authentication. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
4. BRC-105: HTTP Service Monetization Framework. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
5. BRC-22: Overlay Network Topics. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
6. sCrypt BSV20V2 Base Class. [github.com/sCrypt-Inc/scrypt-ord](https://github.com/sCrypt-Inc/scrypt-ord)
7. Lock-to-Mint Pattern (msinkec). [gist.github.com/msinkec/6389a7943ed054fa5c74ba8f79bf730e](https://gist.github.com/msinkec/6389a7943ed054fa5c74ba8f79bf730e)
8. POW20 Miner (Rust). [github.com/yours-org/pow20-miner](https://github.com/yours-org/pow20-miner)
9. BSV-21 Overlay. [github.com/b-open-io/bsv21-overlay](https://github.com/b-open-io/bsv21-overlay)
10. BRC-62: Background Evaluation Extended Format (BEEF). [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
11. BRC-100: Wallet-to-Application Interaction Substrate. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
12. BRC-103: Peer-to-Peer Mutual Authentication. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
13. BRC-24: Overlay Lookup Services. [github.com/bsv-blockchain/BRCs](https://github.com/bsv-blockchain/BRCs)
14. x402: Stateless Settlement-Gated HTTP Protocol. Rui Da Silva / Merkle Works, 2026. [github.com/ruidasilva/merkleworks-x402-spec](https://github.com/ruidasilva/merkleworks-x402-spec)


# 1Sat Ordinals Basket Profile for BRC-46 / BRC-100

Brandon Cryderman / HandCash (<brandongcryderman@gmail.com>)

## Abstract

This BRC defines the **application basket profile** for 1Sat Ordinals under [BRC-46](/wallet/0046) / [BRC-100](/wallet/0100). It reserves the basket name `1sat`, specifies eligibility of outputs placed in that basket, documents the tag vocabulary and [BRC-37](/outpoints/0037) `customInstructions` schema used for display and remittance, and describes normative hold / list / transfer / import flows via existing wallet interface methods.

It does **not** redefine 1Sat Ordinals origin theory. Offline tip→origin proof is specified in the companion [BRC-150](/tokens/0150).

## Motivation

[BRC-37](/outpoints/0037) deliberately leaves `customInstructions` semantics to the named basket. [BRC-46](/wallet/0046) enables token-like tracking in baskets but does not define a 1Sat profile. Without a shared basket name and metadata contract, wallets cannot reliably:

1. Hold the same inscription UTXOs in a common basket.
2. List and filter collectables for apps and UI.
3. Transfer a 1-sat tip while preserving origin claims for the receiver.
4. Import historical ordinals via `internalizeAction` with interoperable remittance.

Existing deployments (including HandCash Desktop) already use basket `1sat` with overlapping tags and JSON instructions. This BRC records that convention so independent wallets can interoperate without trusting a single vendor.

Derivative / reference tips make that contract incomplete without a named `content` field. Many children share one on-chain media outpoint (BRC-160 field 3, `text/uri-list`, `ord://`, OrdFS). Wallets that rebuild `customInstructions` from `origin` / `name` / `provenance` alone drop the shared pointer, and the receiver cannot load media peer-to-peer. Indexer fallback is not enough at collection scale: large 1Sat collections (e.g. Pixel Foxes at \~10M items) already find collection-filtered holder queries and related indexer endpoints unusable in practice, while per-origin `/content/{origin}` remains the workable path. Forwarding `content` in remittance is what lets a receiver fetch that body without waiting on an indexer to know the child tip.

## Specification

### Basket identifier

* The basket name is the UTF-8 string `1sat`.
* Per [BRC-46](/wallet/0046), wallet implementations normalize basket identifiers by trimming whitespace and lowercasing. After normalization, conforming use of this profile MUST use exactly `1sat`.
* This identifier does **not** use the [BRC-99](/wallet/0099) reserved `p` prefix. It is a standard application basket under BRC-46 permissioning (grant / deny access to the basket as a whole, unless a future `p` -scheme supersedes that for assets).

### Eligibility

An output SHOULD be placed in basket `1sat` only when **all** of the following hold:

1. `satoshis === 1`.
2. The output is intended as a 1Sat Ordinals tip (a 1-satoshi UTXO in an inscription ownership chain), per [1Sat Ordinals](https://docs.1satordinals.com).
3. The sender or importer reasonably believes the tip belongs to a 1Sat *origin* (the first 1-sat outpoint carrying a valid first `ord` envelope on that sat).

Wallets MUST NOT place ordinary payment change or multi-sat outputs into `1sat` merely to attach ordinal-looking tags.

Outputs that fail eligibility MAY still appear if a buggy sender used the basket; receivers SHOULD treat such outputs as malformed for this profile and MUST NOT present them as verified inscriptions solely because of basket membership.

### Outpoint encoding

* BRC-100 wire `outpoint` fields use **dot** form: `txid.vout` (see BRC-100 / [BRC-36](/outpoints/0036) practice).
* 1Sat Ordinals / OrdFS commonly use **underscore** form: `txid_vout`.
* Conforming implementations MUST treat `txid.vout` and `txid_vout` as the same outpoint when `txid` is 64 hex chars and `vout` is a non-negative decimal integer.
* Inside this profile’s `customInstructions` object, `origin` SHOULD use underscore form for consistency with 1Sat indexers. Tags MAY use either form after the `origin:` prefix; readers MUST normalize before comparison.

### Tags

Tags are optional BRC-46 / BRC-100 output tags used for filtering and display hints. They are **non-authoritative for asset identity**.

| Tag                                     | Requirement                                                 | Meaning                                                                                                                                                                                                                                 |
| --------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ordinal`                               | SHOULD on conforming transfers and imports                  | Marks the output as an ordinal/inscription tip under this profile.                                                                                                                                                                      |
| `origin` or `origin:<outpoint>`         | SHOULD when origin is known                                 | Claimed inscription origin. Two forms — see rules below.                                                                                                                                                                                |
| `content:<outpoint>`                    | SHOULD when the tip is a derivative / reference inscription | Shared media outpoint for display (see `content` below).                                                                                                                                                                                |
| `type:<type>/<subtype>`                 | SHOULD when the content type is known                       | IANA media type of the **origin** inscription (envelope content-type), with parameters removed — the substring before the first `;` (e.g. `type:image/png` from `image/png; charset=binary`).                                           |
| `name:<string>`                         | MAY (legacy)                                                | Short display name in a tag. New writers SHOULD put display name in `customInstructions` instead (see [Tags vs customInstructions](#tags-vs-custominstructions)). If written, implementations SHOULD truncate to ≤ 80 UTF-8 code units. |
| `app:<string>`                          | MAY                                                         | Application / creator id for filtering (SHOULD truncate to ≤ 40).                                                                                                                                                                       |
| `collection:<id>` / `collectionId:<id>` | MAY                                                         | Collection filter keys (synonyms).                                                                                                                                                                                                      |
| `creator:<id>` / `author:<id>`          | MAY                                                         | Creator filter keys (synonyms of / complements to `app:`).                                                                                                                                                                              |

Rules:

* **Origin tag** — One tag, two forms:
  * bare `origin` — this output **is** the origin (mint / chain start).
  * `origin:<outpoint>` — this output is a later tip; `<outpoint>` is the chain origin (`dot` or `underscore` form; readers MUST treat both as the same outpoint per [Outpoint encoding](#outpoint-encoding)).
  * On self-keep transfer or re-file: if the source has `origin:<outpoint>`, copy that tag unchanged; if the source has bare `origin`, the spent outpoint **is** the origin — write `origin:<spent outpoint>` on the new output.
  * The origin tag is a wallet **claim**, not proof of chain membership ([BRC-150](/tokens/0150)).
* **`type:`** — Describes the origin inscription’s content type, not whatever the current tip’s locking script contains. Self-keep transfers SHOULD copy `type:…` forward with the origin claim rather than re-deriving it from a bare transfer output. Tag matching is exact; there is no prefix or wildcard form.
* **Wallet-local list keys** — Ordinary `id:<key>` tags (stable per-row handles for `listOutputs`) are defined by **BRC-164**, not by this profile. Conforming `1sat` writers MAY stamp `id:` when they adopt that convention. This profile MUST NOT treat `id:` as origin, media, collection, or global asset identity. Receivers MUST ignore a counterparty-supplied `id:` and stamp their own if they adopt BRC-164.
* Unknown tags MUST be preserved when transporting the output ([BRC-37](/outpoints/0037) / BRC-46 spirit). Tag query via `listOutputs` uses existing `tags` / `tagQueryMode` fields.

### Tags vs customInstructions

Both tags and `customInstructions` travel with the output under [BRC-46](/wallet/0046) / [BRC-37](/outpoints/0037). They serve different jobs:

|           | **Tags**                                                        | **`customInstructions`**                                                                                                                   |
| --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Role      | Query and filter keys (`listOutputs` + `tags` / `tagQueryMode`) | Single remittance object: display, optional spend metadata, provenance                                                                     |
| Case      | Wallet storage commonly lowercases tags                         | JSON string values can preserve case                                                                                                       |
| Authority | Non-authoritative claims / local metadata                       | Same for display fields; spend fields only describe how *this* wallet unlocks the lock; provenance is verified per [BRC-150](/tokens/0150) |

**Writers SHOULD:**

* Put **filterable** facts in tags: `ordinal`, `origin` / `origin:…`, `type:…`, `content:…` when useful, and when useful `app:`, `collection:…`, `creator:…` (plus `id:…` only per BRC-164).
* Put the human **display name** in `customInstructions.name` when known (case-preserving).
* Put shared media outpoints in `customInstructions.content` (and MAY mirror with tag `content:…`) for derivative / reference tips.

**Writers SHOULD NOT** rely on a `name:` tag as the primary display name. A `name:` tag remains **MAY** so existing deployments stay conforming; new implementations SHOULD prefer CI `name` and MAY omit the tag.

Readers resolving a display name SHOULD prefer `customInstructions.name` when present, and MAY fall back to a `name:` tag.

### Custom instructions ([BRC-37](/outpoints/0037))

When present for basket `1sat`, `customInstructions` MUST be a **UTF-8 JSON object serialized as a string** (matching BRC-37 examples). Conforming writers SHOULD emit:

```json
{
  "origin": "<txid_vout>",
  "content": "<optional shared media txid_vout>",
  "name": "<display name>",
  "app": "<optional application id>",
  "provenance": { }
}
```

| Field        | Type   | Requirement                       | Meaning                                                                                                                                                                                                                                                                                          |
| ------------ | ------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `origin`     | string | SHOULD                            | Claimed origin outpoint (underscore form preferred).                                                                                                                                                                                                                                             |
| `content`    | string | SHOULD when known for derivatives | Shared **media** outpoint (underscore form preferred). Used when this tip’s own origin envelope is a reference (e.g. [BRC-160](/tokens/0160) field 3 parent, or a `text/uri-list` / `ord://` / OrdFS `/content/<outpoint>` body) so receivers can load the one on-chain body without an indexer. |
| `name`       | string | SHOULD when known                 | Display name (case-preserving). Preferred over a `name:` tag.                                                                                                                                                                                                                                    |
| `app`        | string | MAY                               | Application / creator id (may mirror an `app:` tag for display).                                                                                                                                                                                                                                 |
| `provenance` | object | SHOULD on transfer when available | Provenance remittance; v2 in [BRC-150](/tokens/0150).                                                                                                                                                                                                                                            |

Additional JSON keys are permitted and MUST be ignored by readers that do not understand them. Readers that do not understand `provenance` or `content` MUST still store and forward the entire string unchanged ([BRC-37](/outpoints/0037)). Spend-derivation fields a wallet uses only for its own unlock (`protocolID`, `keyID`, `counterparty`, …) MAY appear in the same object; they are not part of this profile’s display / provenance contract and MUST NOT be required of receivers.

#### Derivative / reference content

Some tips share one inscription body (many children point at one parent media outpoint). For interop:

1. Senders SHOULD set `content` (and MAY set tag `content:<outpoint>`) when they can resolve the shared media outpoint from the tip’s origin envelope ([BRC-160](/tokens/0160) field 3, or a reference body as above), or when a prior remittance already carried `content`.
2. On transfer, senders that rebuild `customInstructions` MUST preserve an existing `content` value when they do not replace it with a freshly resolved one.
3. `content` is a **display claim**. It does not change tip→origin authenticity ([BRC-150](/tokens/0150) still proves the child origin). Receivers SHOULD prefer `/content/<content>` (or OrdFS equivalent) for media when `content` is present and well-formed; otherwise fall back to `/content/<origin>`.

#### Claims vs proof

* `origin`, `content`, `name`, `app`, `type:`, and all tags are **claims**.
* A wallet MUST NOT treat a claimed `origin` as proven solely because it appears in tags or `customInstructions`.
* Proven tip→origin binding is defined by [BRC-150](/tokens/0150) (v2). When remittance is absent or fails verification, the wallet MUST treat identity as **unproven** and SHOULD avoid presenting sender-supplied `name` / `app` as authoritative for that tip.

### Hold and list

Applications list held tips with BRC-100 `listOutputs`:

```json
{
  "basket": "1sat",
  "includeTags": true,
  "includeCustomInstructions": true
}
```

Optional `tags` / `tagQueryMode` filters apply as in BRC-46. Wallets MAY require basket permission per BRC-46 / BRC-100 before returning outputs.

### Transfer (send)

A conforming transfer of a held `1sat` tip SHOULD use BRC-100 `createAction` with:

1. An **input** spending the current tip outpoint (`satoshis === 1`).
2. An **output** with `satoshis: 1`, `basket: "1sat"`, tags per this profile, and `customInstructions` as above.
3. Prefer supplying `inputBEEF` / AtomicBEEF for the spent tip when available ([BRC-62](/transactions/0062), [BRC-95](/transactions/0095)) so the new owner can validate the spend graph.
4. Action `labels` MAY include `1sat` for activity filtering; labels are non-normative for asset identity.

Example (non-normative shape):

```json
{
  "description": "Send Collectable",
  "labels": ["1sat"],
  "inputs": [{ "outpoint": "<tip txid.vout>", "inputDescription": "1sat collectable" }],
  "outputs": [{
    "lockingScript": "<recipient P2PKH or other 1-sat lock>",
    "satoshis": 1,
    "outputDescription": "Collectable transfer",
    "basket": "1sat",
    "tags": ["ordinal", "origin:<txid.vout>", "type:image/png"],
    "customInstructions": "{\"origin\":\"<txid_vout>\",\"name\":\"Example\",\"provenance\":{}}"
  }]
}
```

Senders SHOULD attach [BRC-150](/tokens/0150) provenance remittance when they can build a valid package. Senders that cannot prove origin MUST NOT present the transfer as a verified inscription transfer; they MAY still move the 1-sat UTXO, but receivers will treat identity as unproven.

### Import / receive (`internalizeAction`)

To place an existing tip into basket `1sat`, use BRC-100 `internalizeAction` with protocol `basket insertion` ([BRC-46](/wallet/0046)):

```json
{
  "tx": "<AtomicBEEF bytes for the tip transaction>",
  "description": "Import 1Sat ordinal",
  "labels": ["1sat"],
  "outputs": [{
    "outputIndex": 0,
    "protocol": "basket insertion",
    "insertionRemittance": {
      "basket": "1sat",
      "tags": ["ordinal", "origin:<txid.vout>", "type:image/png"],
      "customInstructions": "{\"origin\":\"<txid_vout>\",\"name\":\"Example\"}"
    }
  }]
}
```

`insertionRemittance.customInstructions` follows the same schema as transfer. Provenance MAY be omitted on import when the importer verifies origin by other means before insertion; once transferred onward, senders SHOULD attach provenance for the new tip.

### Payment separation (wallet policy guidance)

Spending or revealing `1sat` basket outputs is **not** a [BRC-29](/payments/0029) / default-basket payment. Conforming wallets SHOULD:

* Require distinct user authorization (or a dedicated item/basket grant) before `createAction` / `relinquishOutput` that spends `1sat` outputs.
* Not treat a general “pay” or auto-pay grant as authorization to spend `1sat` tips.
* Not fund ordinary payment outputs from `1sat` basket UTXOs.

Normative fine-grained asset permission schemes remain [BRC-99](/wallet/0099) territory and are out of scope for this profile.

### Compatibility

* Wallets that do not implement this profile MUST still store and forward unknown baskets’ `customInstructions` and tags unchanged ([BRC-37](/outpoints/0037)).
* This profile does not conflict with BRC-99 `p` baskets; future permission-wrapped variants MAY wrap `1sat` semantics under a `p <scheme> …` name without invalidating this profile for the plain `1sat` basket.
* Marketplaces (e.g. OrdLock), BSV-20/21, and mint APIs are out of scope.

## Security considerations

* **Tag / metadata spoofing** — Without provenance verification, a sender can attach another inscription’s `origin` and `name`. See [BRC-150](/tokens/0150). A forged `content` only mis-points display media; it does not prove tip→origin.
* **Basket pollution** — Placing non-1-sat or non-ordinal outputs in `1sat` confuses list UIs; receivers should re-check `satoshis` and inscription rules.
* **Burn** — Per 1Sat Ordinals, packing a sat into a multi-sat output ends that origin trail. Do not continue `origin:` claims across a burn.
* **Indexer trust** — Display media URLs and collection metadata often come from indexers; this profile does not make indexers authoritative for tip→origin binding. Forwarding `content` reduces indexer dependence for derivative media.

## Implementations

* **HandCash Desktop** (reference): basket `1sat` list / send / import; tags and `customInstructions` as above; payment grants never cover item spends.\
  Source: `src/wallet/collectables.ts`, `src/wallet/oneSatImport.ts`, `src/wallet/itemAccess.ts` in [HandCash/HANDCASH-DESKTOP](https://github.com/HandCash/HANDCASH-DESKTOP).

## References

1. 1Sat Ordinals — <https://docs.1satordinals.com>
2. [BRC-37](/outpoints/0037) — Basket and Custom Instructions Extension for Bitcoin Outpoints
3. [BRC-45](/tokens/0045) — Outputs are Tokens
4. [BRC-46](/wallet/0046) — Wallet Transaction Output Tracking (Output Baskets)
5. [BRC-62](/transactions/0062) — BEEF Transactions
6. [BRC-95](/transactions/0095) — Atomic BEEF Transactions
7. [BRC-99](/wallet/0099) — P Baskets (reserved permission schemes)
8. [BRC-100](/wallet/0100) — Unified Open BSV Wallet-to-Application Interface
9. [BRC-150](/tokens/0150) — 1Sat Provenance Remittance for Basket `1sat`
10. [BRC-160](/tokens/0160) — 1Sat Ordinals — Inscription Envelopes
11. BRC-164 — Output Identity Tags for BRC-100 Wallets (`id:` list keys; not defined by this profile)
12. 1Sat reference inscriptions — <https://docs.1satordinals.com/reference-inscriptions>


# 1Sat Provenance Remittance for Basket 1sat

Brandon Cryderman / HandCash (<brandongcryderman@gmail.com>)

## Abstract

This BRC defines the **provenance remittance** carried in [BRC-37](/outpoints/0037) `customInstructions` for outputs in the companion basket profile `1sat` ([BRC-147](/tokens/0147)). It specifies an offline-verifiable package that binds a current 1-sat outpoint (*tip*) to a 1Sat Ordinals *origin* ([BRC-159](/tokens/0159)), using BEEF (or AtomicBEEF when it still covers the hop), **BRC-159 sat ordering on every path hop**, and a valid first `ord` envelope on the origin ([BRC-160](/tokens/0160)).

Together with [BRC-147](/tokens/0147), this enables interoperable 1Sat hold/transfer while preventing forgeable `origin:` tags from being treated as proof. Verification does not require a global ordinals indexer.

## Motivation

[BRC-147](/tokens/0147) documents how wallets hold and transfer 1Sat tips and how they attach display metadata. Those metadata fields remain **claims**: a malicious or buggy sender can attach another inscription’s origin to an unrelated 1-sat UTXO. Indexer-only checks help UX but introduce a trust dependency when the indexer is wrong, lagging, or unavailable.

[BRC-62](/transactions/0062) / [BRC-95](/transactions/0095) already provide SPV transaction ancestry packages. This BRC applies those formats to the 1Sat tip→origin path ([BRC-159](/tokens/0159)) and adds the local script rule that the origin locking script contains a valid first `ord` envelope ([BRC-160](/tokens/0160), aligned with [BRC-67](/transactions/0067) SPV principles).

## Specification

### Scope

This remittance applies when:

* The output is associated with basket `1sat` as defined in [BRC-147](/tokens/0147) (after BRC-46 name normalization), and
* The tip being transferred or internalized is a **1-satoshi** output.

Out of scope: OrdLock marketplace contracts, BSV-20/21, minting APIs, redefinition of 1Sat origin theory ([BRC-159](/tokens/0159)), inscription envelope format ([BRC-160](/tokens/0160)), and basket naming/tags (see [BRC-147](/tokens/0147)).

### Transport

Provenance is embedded as the `provenance` field of the basket profile’s `customInstructions` JSON object ([BRC-37](/outpoints/0037)). Display fields (`origin`, `name`, `app`) remain as defined by [BRC-147](/tokens/0147) and MUST NOT be treated as proven unless this remittance verifies.

Wallets that do not implement this BRC MUST still store and forward unknown `customInstructions` unchanged ([BRC-37](/outpoints/0037)).

### Provenance object (v2)

```json
{
  "v": 2,
  "origin": "<txid_vout>",
  "tip": "<txid_vout>",
  "path": ["<tip>", "…", "<origin>"],
  "beefB64": "<base64>",
  "contentType": "<optional string>"
}
```

| Field         | Type      | Requirement | Description                                                                                                                                                                                                                                                               |
| ------------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `v`           | number    | MUST        | Schema version. This BRC defines `2`.                                                                                                                                                                                                                                     |
| `origin`      | string    | MUST        | Inscription origin outpoint in underscore form `txid_vout`.                                                                                                                                                                                                               |
| `tip`         | string    | MUST        | Current outpoint being proven, underscore form. MUST equal `path[0]`.                                                                                                                                                                                                     |
| `path`        | string\[] | MUST        | Ordered outpoints tip → … → origin, inclusive. Length ≥ 1.                                                                                                                                                                                                                |
| `beefB64`     | string    | MUST        | Base64 encoding of **BEEF** ([BRC-62](/transactions/0062)) covering every `path` transaction **and** every preceding input source required for BRC-159 sat ordering on each hop. AtomicBEEF ([BRC-95](/transactions/0095)) is allowed only when it still covers that set. |
| `contentType` | string    | OPTIONAL    | Hint from the origin `ord` envelope content-type field when known.                                                                                                                                                                                                        |

Outpoint normalization (dot ↔ underscore) follows [BRC-147](/tokens/0147).

#### Non-conforming versions

Provenance objects with `v !== 2`, or path-only packages without `beefB64`, are **not** defined by this BRC and MUST NOT be considered conforming remittances. Implementations MAY use private fallbacks for local UX; they MUST NOT advertise those fallbacks as this BRC.

### Sender requirements

A conforming sender transferring a `1sat` tip under [BRC-147](/tokens/0147) SHOULD:

1. Determine the true `origin` of the tip (indexer-assisted discovery is allowed for *building* the package).
2. **Prefer extending a prior remittance** when the tip (or its immediate parent tip) already carries a verified v2 package: prepend the tip being proven to `path` and merge that tip’s transaction **plus preceding input sources needed for BRC-159 sat ordering** into `beefB64`. Do **not** re-walk full tip→origin lineage when an inductive extension verifies.
3. Otherwise construct `path` tip→origin such that each consecutive pair with different transaction ids is a **BRC-159 1→1 transfer hop**: the parent 1-sat outpoint is the child input `vin` whose satoshis land on the child `vout` under sat ordering (`sum(input sats before vin) === sum(output sats before vout)`, and that input is exactly 1 satoshi). A mere spend of the parent as *some* input is not sufficient — funding inputs that precede the ordinal `vin` can assign a different sat to the claimed vout.
4. Assemble `beefB64` covering every transaction id appearing in `path`, **and** the source transaction of every input that precedes (and including) the ordinal-mapping `vin` on each hop, so a verifier can calculate the ordinal. Canonical transfers (`i0` 1-sat → `o0` 1-sat) add nothing beyond the path transactions. Complex hops (funding before the ordinal input, ordinal paid to `o1`+, etc.) MUST include those preceding input sources. Missing sats → the hop cannot be proven; omit provenance rather than guessing.
5. Prefer a **full** BEEF serialization ([BRC-62](/transactions/0062)) for remittance ancestry. AtomicBEEF ([BRC-95](/transactions/0095)) keeps only the subject and its recursive SPV dependencies; mined path parents and **non-ancestor preceding input sources** (funding spent alongside the ordinal) are dropped. AtomicBEEF remains allowed only when it still covers the full `path` *and* every preceding input source required for sat ordering.
6. Self-verify using the Receiver rules below before broadcast.
7. If a valid v2 package cannot be produced, the sender MUST NOT claim a verified origin via tags alone. The sender MAY omit `provenance`; receivers MUST then treat identity as unverified per [BRC-147](/tokens/0147).

### Receiver requirements

Given a candidate tip outpoint `T` and `customInstructions.provenance` with `v === 2`, a conforming receiver MUST verify as follows. On any failure, the receiver MUST treat origin identity as **unproven** and MUST NOT adopt sender `name` / `app` / `origin:` tags as authoritative for that tip ([BRC-147](/tokens/0147) claims rule).

1. **Parse** — Decode `beefB64` as BEEF ([BRC-62](/transactions/0062)) or AtomicBEEF ([BRC-95](/transactions/0095)). Failure → unproven.
2. **Structure** — The BEEF MUST pass structural validation (e.g. `Beef.verifyValid`). Use of txid-only entries ([BRC-96](/transactions/0096)) is allowed only when the receiver already trusts those transactions by other means.
3. **Atomic subject (when AtomicBEEF)** — If the bytes are AtomicBEEF, the subject txid MUST equal the tip’s transaction id (the tip named by `provenance.tip` after normalization).
4. **Tip binding (direct)** — If `provenance.tip` and `path[0]` equal tip outpoint `T` after normalization, continue with steps 5–9 for that package.
5. **Tip binding (parent remittance — settle-style)** — If step 4 fails, receivers **SHOULD** accept the package when all of the following hold (soft-latch / pre-broadcast remittance that inherits origin onto held tip `T`):
   * `provenance.tip` and `path[0]` name a parent outpoint `P` (not `T`);
   * the package verifies under steps 5–9 with held outpoint `P` (direct tip binding to `P`);
   * transaction `T` spends `P` as an input `vin`;
   * output `T` is 1 satoshi;
   * BRC-159 sat ordering maps that `vin` onto `T`’s vout. Preceding input source sats for `T` MUST be known (in `beefB64`, linked on `T`, or otherwise available with `T`) — fail closed if missing. This inductive hop exists because settle-style transfers often embed remittance for the *spent* tip before the new tip’s txid is known. Receivers that omit this step remain conforming for direct tip-bound packages but will treat settle-style remittances as unproven unless they rebuild lineage themselves. Receivers that only check “`T` spends `P`” without sat ordering MUST NOT treat origin as proven for `T`.
6. **Origin binding** — `path[path.length - 1]` MUST equal `provenance.origin`.
7. **Ordinal continuity** — For each consecutive pair `(child, parent)` in `path` with different transaction ids:
   * the child transaction in the BEEF MUST spend `parent` as some input `vin`;
   * that `vin` MUST be the BRC-159 ordinal input for the child vout (`sum(input sats before vin) === sum(output sats before vout)`, input satoshis === 1). If any preceding input’s satoshi value is unknown (source tx missing from the BEEF and not otherwise known), verification MUST fail. For parent-remittance acceptance (step 5), the spend **and** sat-ordering check `T → P` are applied to transaction `T` (which MAY be obtained outside `beefB64`, but preceding input sources for `T` still MUST be available). An informative outpoint→origin walk is in [BRC-159](/tokens/0159) (*Finding origin from an outpoint*).
8. **One-sat** — For each outpoint in `path` present in the BEEF with a known satoshi value, that value MUST be `1`.
9. **Inscription** — The locking script of `origin` MUST contain a valid first `ord` envelope as defined by [BRC-160](/tokens/0160) on a 1-sat output. Subsequent envelopes on the same sat MUST be ignored for origin identity ([BRC-159](/tokens/0159) / [BRC-160](/tokens/0160)).
10. **Headers (RECOMMENDED)** — Receivers SHOULD confirm BEEF merkle roots against a header source / ChainTracker per [BRC-67](/transactions/0067) when available.

On success, the receiver MAY treat `provenance.origin` as the proven origin for tip `T` and MAY use indexer metadata keyed by that origin for display.

### Hop examples (informative)

These examples show why **spend-as-input ≠ sat continuity**, and why **AtomicBEEF / SPV ancestry ≠ ordinality**. Full sat-ordering walks are in [BRC-159](/tokens/0159) (*Examples*).

**Pass (canonical — no extra input sources):** token input first, tip at `o0`.

```
inputs:   i0 = 1-sat parent P    i1 = funding …
outputs:  o0 = 1-sat tip T       change …
```

Sats before `i0` = 0, sats before `o0` = 0, and `i0` is 1 sat → **P lands on T**. Path edge `T → P` needs only the path txs in `beefB64`.

**Fail (funding-first — spend alone is not enough):**

```
inputs:   i0 = funding (5_000)   i1 = 1-sat parent P
outputs:  o0 = 1 sat             o1 = change …
```

The tx still *spends* P as `i1`, so a spend-only check passes. Sat ordering does not: funding fills `o0` first; P’s sat is `#5001` and lands in `o1` (or as fee), not in `o0`. A remittance that claims path `o0 → P` MUST fail — `o0` is a funding sat wearing the wrong origin.

If the real tip is the output that received P, `beefB64` MUST include the funding source so the verifier can compute the map. AtomicBEEF often drops that funding tx (it is not a tip ancestor); the bag can still look SPV-valid and still cannot prove ordinality → fail closed.

**Settle inherit:** soft-latch often proves parent `P`, then the receiver holds new tip `T`. Checking only “`T` spends `P`” accepts the bad `o0` above. Step 5 therefore also requires sat ordering of that vin onto `T`’s vout (preceding sats known).

### Relationship to companion BRCs

| Concern                                                                                        | Defined by                                                    |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| Origin, sat ordering, transfer, tip→origin walk                                                | [BRC-159](/tokens/0159)                                       |
| `ord` inscription envelopes                                                                    | [BRC-160](/tokens/0160)                                       |
| Basket name `1sat`, tags, display `customInstructions`, createAction / internalizeAction flows | [BRC-147](/tokens/0147)                                       |
| `provenance` object schema + verify/build rules                                                | **This BRC**                                                  |
| Whether unproven tips may be shown with sender `name`/`app`                                    | [BRC-147](/tokens/0147) (claims); this BRC (when proof fails) |

### Compatibility

* This BRC does not alter BRC-62/95 encodings; it only constrains how they are used inside `customInstructions.provenance`. BEEF/AtomicBEEF prove SPV ancestry, not BRC-159 sat ordering by themselves.
* Deep histories may produce large remittances. Implementations SHOULD prefer full BEEF when AtomicBEEF would drop path parents or preceding input sources, and MAY refuse to embed packages above an implementation-defined size, falling back to “unproven” rather than truncated proofs.
* Partial BEEF (structurally valid but missing a path transaction or a preceding input source needed for sat ordering) MUST fail verification.
* [BRC-158](/transactions/0158) **Outpoint BEEF** is a binary wire format for the same tip→origin proof (outpoint-subject BEEF envelope). It is an alternative to this JSON remittance, not a `beefB64` encoding option inside it. An Outpoint BEEF used for this remittance MUST still carry the preceding input sources required by BRC-159.

### Scalability (informative)

Remittance correctness needs path txs plus preceding input sources for sat ordering ([BRC-159](/tokens/0159)). Bundle size therefore grows with tip depth. The hot path is **one new hop**, not a full tip→origin rebuild.

**Prefer**

* **Extend a prior verified remittance** (sender step 2) — prepend the new tip, merge that tip’s tx and any preceding input sources needed for that hop. Cost stays roughly O(1) per send.
* **Remember a verified package** for the held tip and reuse it on the next send.
* **Omit provenance** when the package would exceed an implementation size budget (see Compatibility). Unproven is better than a truncated proof.
* **Pin a local verdict** after a successful cold verify / genesis hydrate, then discard the large assembled BEEF if it will not travel on the wire.

**Avoid**

* Re-walking tip→origin and re-fetching every hop on every send or every inventory refresh when a prior remittance already verifies.
* Preferring AtomicBEEF (or nesting [BRC-158](/transactions/0158) inside `beefB64`) to “save bytes” when that drops funding sources needed for sat ordering — the bag looks smaller and still cannot prove the hop.
* Blocking the UI on an unbounded genesis hydrate — cap hops, allow cancellation, and treat hydrate as cold-start only.

**Settle / soft-latch:** when the remittance still names spent tip `P` and the receiver holds new tip `T`, include (or link) the preceding input sources for `T` with the package or with `T` so inherit does not force an extra network hunt to run sat ordering on `T → P`.

[BRC-159](/tokens/0159) / [BRC-160](/tokens/0160) are not the bottleneck — sat math and envelope parse are cheap. Depth × full BEEF is. Offline proof packaging and size policy live in this BRC (and optionally [BRC-158](/transactions/0158) as a parallel wire format).

## Security considerations

* **Tag spoofing** — Without this remittance, `origin:` tags are forgeable. Receivers that skip verification reintroduce the attack.
* **Sat ordering vs spend-only** — Spending a 1-sat parent does not prove that output received *that* sat. Verifiers that skip BRC-159 sat ordering can be convinced an unrelated 1-sat output (e.g. a funding slice paid as 1 sat before the ordinal lands) carries the origin — including on settle-style inherit from parent remittance to tip `T`.
* **AtomicBEEF omission** — AtomicBEEF is an SPV package, not an ordinality package. Do not treat a valid AtomicBEEF as proof that preceding input sources were unnecessary.
* **Indexer divergence** — Indexers may lag or err. A verified remittance is authoritative for tip↔origin binding; indexers remain useful for discovery and media URLs.
* **Burn / re-origin** — If a sat is packed into a multi-sat output, 1Sat origin tracking ends. Remittances MUST NOT invent a continuous path across a burn.
* **Oversized remittance** — Truncating `path` or `beefB64` to fit a size budget invalidates the proof; omit provenance instead.
* **Unspent status** — This remittance proves tip→origin identity. Proving the tip remains unspent requires the same network / UTXO checks as accepting any BSV payment.

## Implementations

* **HandCash Desktop** (reference): builds and verifies provenance v2 on collectable send / list.\
  Source: `src/wallet/oneSatProvenance.ts`, `src/wallet/oneSatInscription.ts` in [HandCash/HANDCASH-DESKTOP](https://github.com/HandCash/HANDCASH-DESKTOP).\
  **≥ 1.2.130** enforces the rules in this revision: BRC-159 sat ordering on every path hop and on settle-style inherit (`T` from parent remittance `P`), preceding input sources in full BEEF, fail closed when sats are missing. Hot path extends or reuses a prior remittance; full lineage hydrate is cold-start only, with a size budget that omits oversized packages rather than truncating.

## References

1. [BRC-159](/tokens/0159) — 1Sat Ordinals — Single-Satoshi Tokens and Origin Tracking
2. [BRC-160](/tokens/0160) — 1Sat Ordinals — Inscription Envelopes
3. [BRC-147](/tokens/0147) — 1Sat Ordinals Basket Profile for BRC-46 / BRC-100
4. 1Sat Ordinals protocol — <https://docs.1satordinals.com>
5. [BRC-37](/outpoints/0037) — Basket and Custom Instructions Extension for Bitcoin Outpoints
6. [BRC-46](/wallet/0046) — Wallet Transaction Output Tracking (Output Baskets)
7. [BRC-62](/transactions/0062) — Background Evaluation Extended Format (BEEF) Transactions
8. [BRC-67](/transactions/0067) — Simplified Payment Verification
9. [BRC-95](/transactions/0095) — Atomic BEEF Transactions
10. [BRC-96](/transactions/0096) — BEEF V2 Txid Only Extension
11. [BRC-158](/transactions/0158) — Outpoint BEEF
12. [BRC-100](/wallet/0100) — Unified Open BSV Wallet-to-Application Interface


# 1Sat Ordinals — Single-Satoshi Tokens and Origin Tracking

Open Protocol Labs (<info@opl.dev>)

**Authors:** David Case (<dcase@opl.dev>), Luke Rohenaz (<luke@opl.dev>)

**Contributors:** Kurt Wuckert Jr. (<kurt@opl.dev>), Michael Boyd (<root@opl.dev>), Dan Wagner (<dan@opl.dev>)

## Abstract

**1Sat Ordinals** treats a **1-satoshi output** as a non-fungible token and tracks it through the chain.

Each token has a stable id — its **origin** — the outpoint where the 1-satoshi chain started. Spending that sat into a new 1-satoshi output under sat ordering moves the token; the origin does not change.

## Motivation

Bitcoin SV allows outputs of exactly one satoshi. A single satoshi is already discrete and transferable; 1Sat Ordinals just names it and tracks it.

This document standardizes four ideas:

1. A **token** is a chain of 1-satoshi outputs.
2. **Origin** is the permanent name of that chain (an outpoint).
3. The token's **current outpoint** is whichever 1-satoshi output in the chain is still unspent.
4. A **transfer** is a spend that puts that satoshi into a new 1-satoshi output under sat ordering.

Everything else — inscriptions, metadata, names, wallet baskets, marketplaces — can build on this.

## Ordinal Theory

1Sat uses the sat-assignment rule from **Ordinal Theory**: satoshis move from inputs to outputs in order (inputs in order, sats within each input; outputs in order, sats within each output). The *n*th satoshi spent is the *n*th satoshi created.

1Sat does **not** use global sat serial numbers from coinbase. Identity is the **origin** of a 1-satoshi chain. Background: <https://docs.ordinals.com/>.

## Specification

### Outpoints

An **outpoint** is transaction id + output index.

String forms in common use:

* **underscore** — `txid_vout` (common in 1Sat indexers and OrdFS-style paths)
* **dot** — `txid.vout` (common in BRC-100 / overlay APIs; see [BRC-36](/outpoints/0036) practice)

Both denote the same outpoint when `txid` is 64 hex characters and `vout` is a non-negative decimal integer. Implementations that accept external references MUST treat the two forms as equal after normalization.

### Token

A **1Sat token** is a chain of **1-satoshi outputs** linked by transfers.

* Only outputs with exactly **1** satoshi are part of a chain.
* Any locking script is fine. This BRC does not care how the coin is locked.
* Script data (inscriptions, metadata) is optional; it does not create or name the token.

While held, exactly one output in the chain is unspent — the token's current location.

### Origin

The **origin** is the outpoint of the first 1-satoshi output in the chain. It is the stable id for APIs, indexes, and UIs. Transfers never change it.

Every 1-satoshi output is either the next link after a transfer, or a **new origin**.

### Sat ordering and transfer

Satoshis flow input→output in order. Counts matter, not input/output indexes: a 1-sat input at any index can land at any 1-sat output if the running totals align.

**Forward (spend):** For a 1-satoshi input, let `S_in` = (sum of earlier input values) + 1. Find the output where the running output total first reaches `S_in`.

* That output value is **1** → **transfer** (new location; origin unchanged).
* That output value is **> 1** → **no transfer** (chain ends; a fresh 1-sat output that did not receive a transferred sat is a new origin).
* No output reaches `S_in` → **burn** (paid as fee).

### Finding origin from an outpoint

Start at any 1-satoshi outpoint in a chain and walk **backward**. When the walk cannot continue as a 1→1 hop, **the current outpoint is the origin**.

```
function proveOrigin(outpoint, txs):
    current = outpoint

    loop:
        tx = load current's transaction from txs
        if missing: fail

        if tx.outputs[current.vout].satoshis != 1: fail

        outAcc = 0
        for j in 0 .. current.vout-1:
            outAcc += tx.outputs[j].satoshis

        inAcc = 0
        for each input of tx:
            sourceTx = load input's source transaction from txs
            if missing: fail
            inSats = sourceTx.outputs[input.vout].satoshis

            // Input entirely before our sat — skip.
            if inAcc + inSats <= outAcc:
                inAcc += inSats
                continue

            // This input covers our sat.
            if inSats == 1 and inAcc == outAcc:
                current = input's source outpoint
                continue outer loop

            break   // not a 1→1 hop

        // Stopped walking — this outpoint is the origin.
        return origin = current
```

### Examples

**Simple transfer** (token input first):

```
inputs:
  i0  1-sat token      1 sat     ← global sat #1
  i1  funding     10_000 sats     ← sats #2 … #10001

outputs:
  o0  1-sat token      1 sat     ← sat #1          origin unchanged
  o1  payment      4_000 sats     ← sats #2 … #4001
  o2  change       5_998 sats     ← sats #4002 … #10001
```

**Counts, not indexes** — funding can come *before* the token on both sides:

```
inputs:
  i0  funding A    3_000 sats     ← sats #1 … #3000
  i1  1-sat token      1 sat     ← sat  #3001        (input index 1, not 0)
  i2  funding B    5_000 sats     ← sats #3002 … #8001

outputs:
  o0  payment      2_000 sats     ← sats #1 … #2000
  o1  payment      1_000 sats     ← sats #2001 … #3000
  o2  1-sat token      1 sat     ← sat  #3001        origin unchanged
  o3  change       4_999 sats     ← sats #3002 … #8001
```

Sats before the token input = `3000` → global sat `#3001`. Sats before `o2` = `3000`, `o2` value 1 → transfer. If `#3001` landed inside a larger output, the chain would not continue.

**Two tokens in one transaction:**

```
inputs:
  i0  token A          1 sat     ← sat #1
  i1  token B          1 sat     ← sat #2
  i2  funding     10_000 sats     ← sats #3 …

outputs:
  o0  token A          1 sat     ← sat #1   origin A unchanged
  o1  token B          1 sat     ← sat #2   origin B unchanged
  o2  change       9_998 sats     ← from funding
```

### What this document does not cover

* Inscription envelopes — [BRC-160](/tokens/0160)
* Metadata, wallets, baskets, tags
* Offline or wire proof packages
* Content delivery and application rules (collectables, names, markets, locks, …)

## Security considerations

* **Coin selection** — Do not spend 1Sat token outputs in ordinary payments unless a transfer is intended.
* **Output order** — Builders must place each token satoshi in the intended 1-satoshi output.
* **Claimed origins** — A string `origin: …` is not proof.
* **Proof packages** — Offline tip→origin proofs, bundle size, and send hot paths are specified in [BRC-150](/tokens/0150) (and optionally [BRC-158](/transactions/0158)). Sat ordering itself is O(inputs+outputs) per hop and is not the scalability bottleneck.

## Implementations

* [docs.1satordinals.com](https://docs.1satordinals.com)
* [b-open-io/1sat-stack](https://github.com/b-open-io/1sat-stack)
* [b-open-io/1sat-sdk](https://github.com/b-open-io/1sat-sdk)

## References

1. 1Sat Ordinals — <https://docs.1satordinals.com>
2. Ordinal Theory — <https://docs.ordinals.com/>
3. [BRC-36](/outpoints/0036) — Format for Bitcoin Outpoints
4. [BRC-45](/tokens/0045) — Definition of UTXOs as Bitcoin Tokens
5. [BRC-160](/tokens/0160) — 1Sat Ordinals — Inscription Envelopes
6. [BRC-150](/tokens/0150) — 1Sat Provenance Remittance for Basket `1sat`
7. [BRC-158](/transactions/0158) — Outpoint BEEF


# 1Sat Ordinals — Inscription Envelopes

Open Protocol Labs (<info@opl.dev>)

**Authors:** David Case (<dcase@opl.dev>), Luke Rohenaz (<luke@opl.dev>)

**Contributors:** Kurt Wuckert Jr. (<kurt@opl.dev>), Michael Boyd (<root@opl.dev>), Dan Wagner (<dan@opl.dev>)

## Abstract

This BRC defines how **inscriptions** attach content to a [BRC-159](/tokens/0159) token: an `ord` **envelope** embedded in the locking script of a **1-satoshi** output.

Together with origin tracking, inscriptions are how 1Sat attaches on-chain files and media to a 1-satoshi token — collectables, documents, app data, and other content-bearing uses.

## Motivation

A 1Sat token is a 1-satoshi chain identified by origin. Many applications also want **data** bound to that token — an image, a document, app JSON, and so on.

Inscriptions put that data in the output script using a small, recognizable envelope. Wallets and indexers can find the envelope, read content type and body, and still follow the token with ordinary 1Sat transfer rules.

This document standardizes the envelope format and the 1Sat rules for when an inscription is valid and how it relates to origin.

## Relationship to the token model

See [BRC-159](/tokens/0159) for origin, sat ordering, and transfer.

Inscriptions do **not** replace that model:

* The token is still the 1-satoshi chain.
* Transfer is still sat ordering into a new 1-satoshi output.
* The envelope is content carried on outputs in that chain (typically at origin, sometimes re-inscribed on later outputs in the chain).

## Specification

### Envelope format

An inscription is an `ord` envelope inside an output script. The usual shape includes a content type and a body:

```
OP_FALSE OP_IF
  <"ord">                      // UTF-8 bytes 6f7264
  OP_1 <content-type>          // field 1: MIME type (UTF-8), e.g. image/png
  OP_0 <content>               // field 0: body bytes
OP_ENDIF
```

Rules:

* After the `ord` tag, the envelope is a sequence of **field / value pairs**. Field **0** (content body) ends the field list; it is followed by `OP_ENDIF`.
* Each pair is a field tag, then a value (normally one push of bytes).
* **Tag aliases:** a single-byte push of `0x00` or `0x01` is equivalent to `OP_0` or `OP_1` (same field number). Minters typically emit the `OP_*` form; parsers MUST accept both.
* In advanced use-cases, a field MAY appear more than once before the body; that does not invalidate the inscription. When resolving a field's value, the **last** occurrence wins.

#### Fields

| Field        | Tag | Value                                 | Requirement                      |
| ------------ | --- | ------------------------------------- | -------------------------------- |
| Content type | `1` | MIME string, UTF-8 (e.g. `image/png`) | MUST when creating inscriptions  |
| Content body | `0` | Body bytes (MAY be empty)             | MUST (terminates the field list) |

#### Parent (field 3)

Field **3**, when present, marks a **parent / child** relationship between inscriptions. This follows the same idea as [Ordinals provenance](https://docs.ordinals.com/inscriptions/provenance.html): the owner of a parent inscription can create **child** inscriptions on-chain in a way others cannot fake, by **spending the parent** when minting the child.

That pattern is used for collections and other hierarchies (e.g. issuer → collection → items). Children may themselves have children. Burning or otherwise destroying the parent can be used by applications to stop further children that would need to spend it.

**On 1Sat**, the parent value is the parent's **outpoint** (the 1-sat output that carries the parent inscription), not a BTC-style `txid…iN` inscription id string:

* **36 bytes** — 32-byte txid + 4-byte little-endian vout (preferred).
* **32 bytes** — txid only; treated as that transaction's output **0**.

**Same-transaction spend (proof):**\
If field 3 is set, the parent outpoint MUST appear as an **input** of the transaction that creates this child inscription output. The spend is the proof: anyone can push arbitrary bytes as field 3, but only someone who can unlock the parent output can spend it into the child mint.

#### Other fields

Additional field/value pairs MAY appear before the body. They do not invalidate the envelope. Conforming 1Sat readers MAY ignore unknown fields. Application meaning (e.g. collections) is defined elsewhere.

#### Placement in the script

The envelope sits **in the output locking script**. Typical patterns:

```
<locking script> <envelope>
<envelope> <locking script>
<envelope> OP_CODESEPARATOR <locking script>
```

The spend condition MAY be any locking script: P2PKH is common, but covenants, multi-sig, marketplace locks, and other contracts are all valid. This BRC only requires that the envelope is present and well-formed on a 1-satoshi output; it does not constrain how that output is locked.

### Push data on BSV

BSV does not impose the historical 520-byte push cap used on some other networks. A field value or content body MUST be a **single** push. Implementations MUST NOT split one logical value across multiple pushes and concatenate them.

### Valid 1Sat inscription

An output is a **valid 1Sat inscription** when all of the following hold:

1. `satoshis === 1` (it is a 1Sat token output under the core model).
2. The locking script contains a well-formed `ord` envelope as defined above.

Inscriptions are defined for 1Sat Ordinals: the envelope applies to **1-satoshi** outputs that participate in origin tracking. Multi-satoshi outputs with similar script patterns are outside this BRC.

### Inscriptions on a 1Sat chain

An envelope may appear on the **origin** output, on a later output in the same chain, or on both (re-inscription when transferring).

* Chain identity (origin and transfers) is unchanged by envelopes — see [BRC-159](/tokens/0159).
* This BRC does **not** say which envelope is "the" content for an application. Collectable NFTs, updatable assets, feeds, and other profiles may select differently; those rules belong in application or content-resolution specs.

### Inscription id

Because envelopes live in **outputs** (not input witnesses), an inscription is identified by the outpoint of the output that carries it. Outpoint string forms follow [BRC-159](/tokens/0159) (underscore or dot; both denote the same outpoint).

A mint often creates origin and first envelope on the same outpoint; after transfers, the token's current outpoint moves while content may still be addressed by origin or by a specific inscription outpoint, depending on the application.

### Mint (informative)

A typical mint creates the origin and inscription together:

```
inputs:   funding …
outputs:  o0 = 1 sat, lock + ord envelope   ← new origin + inscription
          change …
```

Transfers afterward follow [BRC-159](/tokens/0159) (1-satoshi input → 1-satoshi output under sat ordering). The envelope does not need to be repeated on every output in the chain unless the application re-inscribes.

### Metadata (MAP)

Optional application metadata MAY follow the inscription on the same output after `OP_RETURN`, using the **Magic Attribute Protocol** (Bitcom protocol id `1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5`):

```
<ord envelope> <lock> OP_RETURN <MAP id> SET type ord name "…" …
```

Do not use `OP_FALSE OP_RETURN` if the output must remain spendable in the usual lock+envelope patterns.

MAP data is a `SET` command followed by string key/value pushes. For ordinal display metadata, common keys are:

| Key    | Requirement                       | Meaning                               |
| ------ | --------------------------------- | ------------------------------------- |
| `app`  | MAY                               | App that produced the ordinal         |
| `type` | SHOULD                            | `ord` when using this schema          |
| `name` | SHOULD when a display name exists | Human-readable name (case-preserving) |

Further keys and other ways to carry MAP (e.g. as an inscription field) are defined by application profiles such as 1Sat collections (specified separately). Unknown keys MUST be ignored by readers that do not implement them.

Wallets that surface a display name (e.g. in BRC-100 `customInstructions`) SHOULD take it from MAP `name` when present.

## What this document does not cover

* Full MAP / Bitcom opcode encoding — specified separately
* Collection schemas and in-envelope MAP field layout — specified separately (1Sat Collections)
* Issuer authenticity (e.g. Sigma) — specified separately
* Large content via multi-transaction streams / OrdFS
* Wallet baskets, tags, or provenance proofs — [BRC-147](/tokens/0147), [BRC-150](/tokens/0150)
* Marketplaces and application-specific locks

Those may require [BRC-159](/tokens/0159), this envelope BRC, or both.

## Security considerations

* **Content authenticity** — An envelope proves data was in a script at an outpoint; it does not by itself prove the token's current outpoint still descends from that origin (see [BRC-159](/tokens/0159) and [BRC-150](/tokens/0150)).
* **Parent without spend** — Field 3 alone is not proof of parentage; only spending the claimed parent in the same transaction establishes the link.
* **Large bodies** — Very large pushes affect relay and storage (including [BRC-150](/tokens/0150) bags that cover the origin tx). Application limits are out of scope here.
* **Content selection** — Showing the wrong envelope for a given app (e.g. latest vs origin) is an application concern, not fixed by this BRC.

## Implementations

* [docs.1satordinals.com](https://docs.1satordinals.com)
* `@1sat/templates` `Inscription` in [b-open-io/1sat-sdk](https://github.com/b-open-io/1sat-sdk)
* [b-open-io/1sat-stack](https://github.com/b-open-io/1sat-stack)

## References

1. [BRC-159: 1Sat Ordinals — Single-Satoshi Tokens and Origin Tracking](/tokens/0159)
2. [BRC-147](/tokens/0147) — 1Sat Ordinals Basket Profile for BRC-46 / BRC-100
3. [BRC-150](/tokens/0150) — 1Sat Provenance Remittance for Basket `1sat`
4. 1Sat Ordinals — <https://docs.1satordinals.com>
5. Ordinals inscriptions — <https://docs.ordinals.com/inscriptions.html>




---

[Next Page](/llms-full.txt/1)

