> This is part 1 of 3 of the full documentation (pages 1–100 of 227).
> The content is paginated: fetch every part to see all of it.
> Next part: https://bsv.brc.dev/llms-full.txt/1
> Page index: https://bsv.brc.dev/llms.txt

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://bsv.brc.dev/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# 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.

For protocol, basket and certificate descriptions in wallet registries, see the [registry submission and maintenance guide](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md). Start with the BRC process, then request inclusion from the desired operator. Registry metadata is optional and is not permission to use a protocol.

## 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)                                                    |
| 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)                                             |
| 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)                                                                      |
| 161 | [BSV-21 Fungible Tokens (JSON / Legacy)](/tokens/0161)                                                                     |
| 162 | [1Color](/tokens/0162)                                                                                                     |
| 163 | [BSV-21 Basket Profile for BRC-46 / BRC-100](/tokens/0163)                                                                 |
| 164 | [Output Identity Tags for BRC-100 Wallets](/wallet/0164)                                                                   |
| 165 | [P1Sat Permission Scheme for Basket `1sat`](/tokens/0165)                                                                  |
| 166 | [P2PKH Payments for HTTP 402](/payments/0166)                                                                              |
| 167 | [Chunked, Hashed, Interleaved Resolution Protocol (CHIRP)](/overlays/0167)                                                 |
| 168 | [Verifiable Time Allocation](/apps/0168)                                                                                   |
| 169 | [Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)                                           |
| 170 | [Locked Content Header (LCH) — Licensed, Micropayment-Enabled Content](/apps/0170)                                         |
| 171 | [Fixed-Width State in a Covenant's Own Locking Script](/state-machines/0171)                                               |
| 172 | [Giving an AI Agent Control of a Delivery Vehicle Without Giving It a Wallet](/state-machines/0172)                        |
| 173 | [Bitcoin Script to and from Bitcoin BASIC — a compiler and a decompiler](/scripts/0173)                                    |
| 174 | [Consensus-Unique Name Tokens — Identity Binding and Verified Resolution](/tokens/0174)                                    |
| 175 | [1Color Offline Peer-to-Peer Payments](/payments/0175)                                                                     |
| 176 | [BSV-21 — Validity Proofs](/tokens/0176)                                                                                   |
| 177 | [Wallet-Enforced Expiry for `noSend` Actions](/wallet/0177)                                                                |
| 178 | [Race-Settled Collection Markets for Overlay Lookups and Message Boxes](/overlays/0178)                                    |
| 179 | [Signed Capability Manifest for Wallet and Tool-Server Method Exposure](/wallet/0179)                                      |
| 180 | [Overlay Service Discovery at an Internet Domain](/overlays/0180)                                                          |
| 181 | [Wallet-Enforced Autonomous-Agent Spend Policy](/wallet/0181)                                                              |
| 182 | [Pairwise Authentication](/peer-to-peer/0182)                                                                              |
| 183 | [A Framework for Strict and Federated Overlays](/overlays/0183)                                                            |
| 184 | [Optional Metadata Registries and Their Stewardship](/wallet/0184)                                                         |
| 185 | [Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections](/wallet/0185)                                    |
| 186 | [Thoughts on UTXO-Driven Applications and Private Overlays](/opinions/0186)                                                |
| 187 | [Thoughts on Declarative Stewardship](/opinions/0187)                                                                      |
| 188 | [User Management Protocol (UMP)](/wallet/0188)                                                                             |
| 189 | [Identity, Certificates, Discovery, and Personal Trust in Applications](/wallet/0189)                                      |
| 190 | [Access Gates for Metanet Rooms](/apps/0190)                                                                               |
| 191 | [Thoughts on Identity, Privacy and Recovery on the Metanet](/opinions/0191)                                                |
| 192 | [Application Output Knowledge and Source Adapters](/apps/0192)                                                             |
| 193 | [Incremental Lookup and Resumable Live Results](/overlays/0193)                                                            |
| 194 | [Overlay Capabilities and Non-Final Proposal Sessions](/overlays/0194)                                                     |
| 195 | [Private Overlay Data and Recoverable Paid Lookup](/overlays/0195)                                                         |
| 196 | [STEAK with POTATOES](/overlays/0196)                                                                                      |
| 197 | [Accumulating Right-of-Sale Listings and Purchase Orders](/tokens/0197)                                                    |
| 198 | [LCH Acquisition through Private Overlays and Listing Covenants](/apps/0198)                                               |
| 199 | [SHIP and SLAP Root-Host Output Eviction Coordination](/overlays/0199)                                                     |
| 200 | [Operating Practices for Identity Certifiers](/peer-to-peer/0200)                                                          |
| 201 | [Social Account Certificate Types and Issuance Profile](/peer-to-peer/0201)                                                |
| 202 | [Identity-Key Decentralized Identifiers](/peer-to-peer/0202)                                                               |
| 203 | [BRC-52 Encrypted Certificate Profile for Verifiable Credential Interoperability](/peer-to-peer/0203)                      |
| 204 | [Agent Allowances](/wallet/0204)                                                                                           |
| 205 | [Extensible Wallet Version Strings and Account Labels](/wallet/0205)                                                       |
| 207 | [1Color Wallet Module](/wallet/0207)                                                                                       |
| 208 | [Vault Sub-Accounts](/wallet/0208)                                                                                         |
| 209 | [1Color Minimal Resource Profile](/wallet/0209)                                                                            |
| 210 | [Derived Collectibles](/apps/0210)                                                                                         |
| 211 | [Signed On-Chain Reachability Manifest](/overlays/0211)                                                                    |
| 215 | [Block-Height Deadline Covenant Templates (OP\_PUSH\_TX)](/scripts/0215)                                                   |
| 216 | [Dead-Man Timed Reveal of a Sealed Secret](/apps/0216)                                                                     |
| 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)                    |
| 222 | [Consent Envelope: Verifiable Multi-Party Document Signing with On-Chain Signing Order](/apps/0222)                        |
| 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)                                                          |
| 229 | [Wallet-Native Elliptic Curve Point Multiplication as a BRC-98 Module](/wallet/0229)                                       |
| 231 | [Binary Encoding for the Message Relay Interface](/peer-to-peer/0231)                                                      |
| 233 | [Subject BEEF](/transactions/0233)                                                                                         |
| 248 | [Offline Issuer Identity Packages](/peer-to-peer/0248)                                                                     |
| 325 | [User-Initiated Browser Permission Requests for Wallet Discovery](/wallet/0325)                                            |
| 326 | [Wallet Failure Presentation](/wallet/0326)                                                                                |
| 369 | [Keyed Content and Conditional Key Release](/peer-to-peer/0369)                                                            |

## 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 **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: `4a5ad2f63bea2a4133ec1c821a441651817b613391455004e000a6c25512b11f` (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 7b2274797065223a2245504f43485f4f50454e227d
```

* `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 1-of-1 (1Sat) code-provenance ordinal carrying the seal of a released build's anchor, which any client re-verifies against the build it is executing, published beside a per-adopter tamper fingerprint over the same bytes; signed statements are published as immutable on-chain data records. 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 specification 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 specification 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); text containing a code point that the normalizer's Unicode database leaves unassigned is rejected, not normalized (§2). `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.

**Typed input is text.** What a person types or pastes is text; only a file or a binary payload is raw octets. An implementation that takes `data` from a person (a command-line argument, a form field, the string argument of an API or tool call) MUST pass it as text, so that it is normalized and the rule below applies. It passes raw octets only when it reads a file, or an input the person has explicitly marked as binary (for example hex or base64). The two readings give different anchors for what looks like the same input: `café` typed with a separate combining accent (`e` followed by U+0301) is `ik17288300319e48…` as text and `ik191d9326afef7a…` as octets (TV4).

**Unassigned code points MUST be rejected.** A text input (`data` when it is text, the `kind`, or an adopter name, §6) that contains a code point which is unassigned (General\_Category `Cn`) in the Unicode Character Database the implementation's NFC normalizer uses, other than the 66 noncharacters (U+FDD0 to U+FDEF, and the last two code points of each of the 17 planes, U+*n*FFFE and U+*n*FFFF), MUST be rejected. It is never normalized and never hashed. Each implementation checks against the same database, at the same Unicode version, that its normalizer uses, and its self-test reports that Unicode version. No minimum Unicode version is required. Raw octets are never checked.

NFC on its own does not make the anchor deterministic, because each Unicode version assigns new characters and normalizers ship with the tables of one version. A normalizer that does not know a code point treats it as a starter with no decomposition, so it can block a reordering or a composition that a newer normalizer performs, and two conforming implementations on different Unicode versions derive different anchors for the same text. Data `e` U+0897 U+0327 normalizes to U+0229 U+0897 under Unicode 16.0 or later, where U+0897 is assigned, and is left unchanged by an earlier database: two anchors for one input (TV4). This happens between current runtimes, and between two releases of the same runtime. Under Unicode's normalization stability policy, a string whose code points are all assigned in some version has the same NFC form in that version and in every later one, so any two implementations that accept a string derive the same anchor for it, now and later. An implementation whose database does not know a code point rejects the string instead of returning a different answer. Noncharacters are permanently unassigned and normalize to themselves in every version, so they are accepted.

**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.

A string that contains a code point unassigned in the implementation's Unicode database (§2) is likewise rejected as a kind, by the anchor and by the derivation in 4.2 alike.

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. The unassigned-code-point check of §2 does apply here: it rejects the kind, and it never alters the string that is hashed.

**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 pixel-level construction is not reproduced here. It is the Seal v3 construction of the Inkan Standard (§3 of that document), driven by one data file, the Seal v3 tables, which holds every table and constant the renderer uses: the palette and its selection weights, the core patterns, the region maps, and the reserved-kind table and derivation constants of §4. The tables file is published under CC0 as `seal-v3-tables.json` and is identified by its SHA-256:

```
441881915ddf6c213dadac507d714b57068e0f6bb0a6f6070acd3fb924dcdf4b
```

The file is one JSON object with exactly fourteen members: the tables and constants under the names the Inkan Standard gives them (`CANVAS`, `SEAL_VERSION`, `STREAM_DOMAIN_HEX`, `KIND_DOMAIN_HEX`, `GOLAY_G`, `TIER_CELLS`, `KMAX`, `ORBIT_REPS`, `KINDS`, `PALETTE`, `SELECT_WEIGHTS`, `CORE_PATTERNS` and `REGION_RLE`), and `FORMAT`, whose value is the string `"inkan-seal-v3-tables"`. `FORMAT` names the file and is not read by the renderer, but it is part of the encoded object and therefore of the digest.

**The Seal v3 tables are normative.** An implementation that renders seals MUST use tables whose canonical encoding is exactly the file with this digest, and MUST reproduce the seal RGB digests in TV1, TV2 and TV8 and in the conformance vectors. The canonical encoding lets an implementation rebuild the exact bytes from its own copy of the tables and compare digests: one JSON object; members sorted by key in code-point order; no insignificant whitespace (separators `,` and `:`); integers in shortest decimal form; every string pure ASCII and written without escapes; the whole followed by a single line feed (`0x0A`). Matching a sample of vectors is not enough on its own. An engine with one palette value off by one unit can pass a sampled vector set and still draw different seals for ordinary inputs, which is why the tables themselves are pinned.

**RGB digest.** The canonical seal is 224 × 224 pixels. Pixel `(x, y)`, with column `x` and row `y` counted from 0 at the top-left corner, is pixel number `i = 224·y + x`. A seal's RGB888 string is, for `i = 0 … 50175` in order, the three bytes `R, G, B` of pixel `i`: 150,528 bytes in all. The seal's RGB digest, `rgb_sha256`, is the SHA-256 of that string, and it is the value every seal test vector pins.

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.
* **Pixel-deterministic PNG.** The canonical seal is an 8-bit indexed PNG whose chunks are exactly `IHDR`, `PLTE`, `IDAT`, `IEND`, with **no ancillary chunks**. Every conforming encoder writes a valid PNG that decodes to exactly the seal's pixels. The cross-language invariant is the decoded RGB pixel content, not the compressed bytes: deflate output depends on the implementation and its settings, so compressed PNG bytes are not part of this standard, and no test vector, comparison or on-chain record may pin them (an inscribed seal is verified by its pixels, §7.4). 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. The keyword `inkan-seal` is reserved: a tagged PNG of a later `SEAL_VERSION` names its version in an `iTXt` chunk with that keyword (§9). A Seal v3 writer never emits it, and a seal without it is Seal v3.

`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 file to native RGB under the verifiable-PNG profile below; 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. A list of forbidden chunks cannot keep up with PNG, which keeps adding chunk types that change rendering: a genuine seal carrying `cICP` (a colour space) or `eXIf` (an orientation) is drawn in other colours, or rotated, by a current browser while its stored pixels still compare equal. The profile below therefore lists the chunks that are allowed and rejects all others.

**Verifiable-PNG profile.** A verifier accepts a presented file only if it is a PNG that meets every rule below, and rejects it otherwise. The profile accepts every honest re-encoding of a seal (any deflate settings, any scanline filters, indexed or truecolour, benign metadata) and nothing whose appearance can differ from its decoded pixels. The verifier MUST decide on its own decode under these rules. It MAY use a platform image decoder to display the image, never to judge it: a browser's decoder will, for example, open an animated GIF named `.png` and show its frames.

1. **Signature and chunks.** The file begins with the PNG signature `89 50 4E 47 0D 0A 1A 0A` and continues as a sequence of chunks: a 4-byte big-endian length of at most 2^31 − 1, a 4-byte type, the data, and a CRC-32 over the type and the data as PNG defines it. Every CRC MUST be correct. The file ends exactly where the `IEND` chunk ends.
2. **Allowed chunks.** Only `IHDR`, `PLTE`, `IDAT`, `IEND`, `tEXt`, `zTXt`, `iTXt`, `pHYs` and `tIME` may appear. Any other chunk, critical or ancillary, known or unknown, MUST be rejected: among them every chunk named in the previous paragraph, and `hIST`, `sPLT`, `mDCV` and `cLLI`.
3. **Order.** `IHDR` comes first and appears once. `IEND` comes last and has no data. `PLTE` appears at most once, before the first `IDAT`. The `IDAT` chunks are consecutive. `pHYs` (before the first `IDAT`) and `tIME` appear at most once each. Text chunks may appear anywhere between `IHDR` and `IEND`.
4. **Header.** Bit depth 8; colour type 2 (truecolour) or 3 (indexed-colour); compression method 0; filter method 0; interlace method 0. Width and height are from 1 to 2^31 − 1.
5. **Palette.** Colour type 3 requires a `PLTE` whose length is a multiple of 3, with 1 to 256 entries, and every pixel's index MUST be less than the number of entries. With colour type 2, a `PLTE` (a suggested palette) is allowed and ignored.
6. **Image data.** The data of all `IDAT` chunks, concatenated, is one zlib stream (RFC 1950: compression method 8, no preset dictionary, a correct Adler-32) that ends exactly at the end of the last `IDAT` and inflates to exactly `height · (1 + width · channels)` bytes, `channels` being 3 for truecolour and 1 for indexed-colour. Each scanline begins with a filter type from 0 to 4 and is unfiltered as PNG defines. A verifier SHOULD stop inflating as soon as the output would exceed that size.
7. **Metadata.** `pHYs` has 9 bytes of data and `tIME` 7. `tEXt`, `zTXt`, `iTXt`, `pHYs` and `tIME` do not change the pixels and are not interpreted, except that a verifier MAY read the `inkan-code` and `inkan-kind` tags as an untrusted cross-check.

### 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`. `dec(version)` is the version written in ASCII decimal digits, with no sign and no leading zeros. The `payload` is raw octets and is never normalized (§2). `fingerprint_hex` is the lower-case hex of the 32-byte digest.

**Adopter-name profile.** An adopter name MUST be printable UTF-8, MUST NOT contain a NUL byte, and MUST NOT begin with `nkan/`. The last constraint closes a cross-context parse. `u8(len(adopter_bytes))` is a single length byte, and for an adopter name of the right length beginning `nkan/` that byte can equal `0x69`, ASCII `i`, so the fingerprint preimage would open with the exact nine bytes of `INKAN_DOMAIN` and would also parse as a glyph seed under section 2. One digest would then be simultaneously a valid fingerprint and a valid anchor.

Printable means exactly this: the name is well-formed Unicode text (as a kind must be, §4), it contains no code point unassigned in the implementation's Unicode database (§2), and it contains no control character, that is, no code point in U+0000 to U+001F or U+007F to U+009F (NUL included). The `nkan/` test applies to the name after NFC. These rules are fixed by code-point ranges rather than by Unicode general categories, so they do not change as Unicode assigns new characters. An implementation MUST reject a name that breaks any of them, before hashing.

**Fingerprint versions.** `version` is a positive integer: 1, 2, 3, and so on. Anything else (zero, a negative number, a fraction, a string, a boolean) MUST be rejected; a fraction such as `1.5`, left to the implementation, is truncated by one and written out in full by another, giving two digests for one input. Version *n* is SHA-256 over exactly this construction with `dec(n)` in the suffix, forever: no version ever changes its hash or its layout. A fingerprint over a different hash function is not a new version of this scheme. It gets its own domain and a text form that names its hash, so it can never be mistaken for, or compared with, a fingerprint defined here. A verifier takes the version as an explicit input and never infers it; the reference default is `1`.

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 a build's per-adopter digest for **software/build provenance**. It is published beside the code-provenance ordinal of §7.4, never inscribed in it.

### 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                    |

**Canonical bytes, exactly.** `canonical_record_bytes` is the record written as one JSON object with no insignificant whitespace, encoded in UTF-8: `{`, then the members in ascending order of key (for the reference record: `block_hash`, `block_height`, `date`, `domain`, `seq`, `statement`, `v`) separated by `,`, each written `"key":value`, then `}`. An integer is written in decimal digits with no sign, fraction, exponent or leading zero. A string is written between `"` characters with exactly these escapes: `"` as `\"`, `\` as `\\`, U+0008 as `\b`, U+000C as `\f`, U+000A as `\n`, U+000D as `\r`, U+0009 as `\t`, and every other code point below U+0020 as `\u00xx` with two lower-case hexadecimal digits. Every other code point, U+007F, U+2028, U+2029 and all non-ASCII characters included, is written as itself. This is the output of Python's `json.dumps(record, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`, and of JavaScript's `JSON.stringify` over the record with its keys sorted, for every record that meets the input rules of 7.3.

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 whose `S` is greater than half the group order `n` (the high-S twin `(r, n − S)` of a valid signature) is not one this scheme produces, and a verifier MUST report it `INVALID`, even where a generic ECDSA library would accept it. A verifier built on such a library would otherwise accept a second, different signature for every genuine one (TV7). 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:

* `MALFORMED`, the record, the public key, or the signature did not parse, so no signature check was performed. A verifier MUST report `MALFORMED` whenever its inputs did not parse, and MUST NOT report `INVALID` in that case. `INVALID` asserts that a check ran and the signature was wrong, which is an accusation of forgery; a truncated key or a DER blob that will not decode is a handling error, and reporting it at maximum severity trains an operator to discount the verdict that matters.
* `UNSUPPORTED`, the record parsed and carries a record version `v` this verifier does not implement. A verifier MUST check `v`. It MUST NOT report `OK` for an unknown version, which would assert a validity it is not able to judge, and MUST NOT report `INVALID`, which would assert a failure it did not observe. A well-formed, correctly signed, fresh `v=2` record read by a `v=1` verifier is the case this rule exists for.
* `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.

A record whose shape does not match the 7.2 table, exactly those fields with those types, is `MALFORMED`, and a verifier MUST validate the shape before it reports `OK`. The signature covers whatever fields are present, so a record of the wrong shape is not a forgery. It can still be published by mistake, and without this rule every verifier reports `OK` over it while downstream readers disagree about what they have read.

**Input rules.** After the version check (a record whose `v` is not the integer `1` is `UNSUPPORTED`), a verifier checks the record against these rules before it checks the signature, and reports `MALFORMED` for a record that breaks any of them:

1. `seq` and `block_height` are integers from 0 to 2^53 − 1 inclusive.
2. `date` is exactly 20 ASCII characters, `YYYY-MM-DDTHH:MM:SSZ`, each letter other than `T` and `Z` standing for an ASCII digit `0` to `9`, with `-`, `T`, `:` and an upper-case `Z` appearing literally, and it names a real UTC time: year 0001 to 9999, month 01 to 12, a day that exists in that month of that year (Gregorian calendar), hour 00 to 23, minute 00 to 59, second 00 to 59.
3. Every string is well-formed Unicode text: a JSON escape that leaves an unpaired surrogate is malformed.
4. A record read from bytes (for example from the §7.1 output) is malformed unless those bytes are exactly its canonical bytes (7.2).

These rules make every conforming verifier give the same verdict for every record. Without them, verifiers disagree over correctly signed records. A `seq` above 2^53 that a double cannot hold exactly (2^53 + 1, or a nanosecond timestamp such as 1760112000123456789) is read exactly by one JSON parser and rounded by another, so one verifier reports `OK` and the other `INVALID`. A date written `2026-6-30T0:0:0Z`, or one with hour 24 or second 60, is accepted by one date parser and refused by another. Rule 1 ends at 2^53 − 1, the largest integer that no other integer rounds onto in a double (2^53 + 1 rounds to 2^53). A `seq` or `block_height` of 2^53 or more is therefore `MALFORMED` even where a double holds it exactly, as it holds 2^53 and 10^18, and so is a negative one.

**Key and signature form.** The public key and the signature are each given to a verifier as hexadecimal text: an even number, at least two, of the ASCII digits `0` to `9`, `a` to `f` and `A` to `F`, with no prefix such as `0x` and nothing between the digits. Only these characters are ignored before and after the digits: U+0009 to U+000D, U+0020, U+00A0, U+1680, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. A key or signature in any other form, or one that is not text, is `MALFORMED`. A reader of a §7.1 output gives the verifier the pushed key and signature as their hexadecimal text (`hex()`, §1). Without this rule, one verifier removes a `0x` prefix that another refuses, and the second skips spaces between digit pairs that the first refuses, so a correctly signed record reads `OK` in one verifier and `MALFORMED` in the other.

For a canary, absence, `STALE`, `INVALID`, `MALFORMED`, or `UNSUPPORTED` 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. The provenance anchor of a released file (a build, an archive, a page) is computed from the file's bytes alone:

```
file_hash = SHA-256(file_bytes) # 32 octets
anchor_data = hex(file_hash) # text: 64 lower-case hex ASCII characters
anchor = SHA-256(INKAN_DOMAIN || u8(4) || "code" || anchor_data)
 # the §2 anchor of (data = anchor_data, kind = "code")
code = CODE_PREFIX || hex(anchor)
```

The inscription content is the canonical **tagged** seal PNG (§5) of `anchor` under kind `code`, which resolves to `(cells=24, k=7, motif=42)`: its `inkan-code` tag carries `code`, and its `inkan-kind` tag carries `code`. Any integer scale verifies (§5); the reference writes it at scale 8. The 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).

The publisher's tamper fingerprint (§6) of the same `file_bytes`, under its adopter name and fingerprint version, is published beside the ordinal as a second, independent check of the same bytes. It is **not the inscribed seal**, it is not rendered as one, and no `ik1` code is formed from it: an `ik1` code labels only an anchor (§3).

A client verifies a release by computing `file_hash` from the bytes it holds, recomputing `anchor`, `code` and the canonical seal, and comparing the inscribed image with that seal pixel for pixel (§5). Where the publisher's fingerprint is given, the client recomputes it as well and compares all 32 bytes. The displayed seal *is* the on-chain-committed mark, so a tampered build cannot reproduce it. TV8 is a worked example.

**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).
* Text that contains a code point unassigned in the implementation's Unicode database MUST be rejected, never normalized or hashed, whether it is `data`, a kind or an adopter name (§2).
* A canonical seal MUST be rendered from the Seal v3 tables whose canonical encoding has the SHA-256 given in §5; reproducing a sample of vectors does not establish that (§5).
* 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).
* A presented image MUST be a verifiable PNG under the profile of §5, judged on the verifier's own decode; a platform decoder may display it, never judge it (§5).
* An adopter name MUST satisfy the §6 profile, and a fingerprint version MUST be a positive integer, before anything is hashed (§6).
* An on-chain record MUST be re-verified offline (signature + freshness) after parsing; the chain is trusted only for immutability and timestamp ordering (§7).
* A record that breaks the 7.3 input rules, or a key or signature that is not hexadecimal text in the form 7.3 fixes, is `MALFORMED`, and a high-S signature is `INVALID` (§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`): versioned and additive. A new seal design is a new `SEAL_VERSION` added beside version 3, never a replacement: version 3 is never redefined, every version 3 seal keeps its pixels, and every conforming verifier keeps version 3. Adding 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`, and a product that can show more than one labels which one it shows.
* **Fingerprint domain** (`FP_SUFFIX` version): per-adopter and independently versioned. Version *n* is fixed forever (§6).
* **Signed record** (`v`): version 1 is the format of §7.2. A new signature scheme is a new record version whose algorithm is named inside the signed bytes.

**Permanence.** For every input it accepts, this scheme has exactly one result, forever: the anchor and its `ik1` code; kind resolution under `SEAL_VERSION` 3 (the 14 reserved kinds and the suffixes `@t1`, `@t2`, `@t3`); the Seal v3 pixels, fixed by the tables file of §5 and the conformance vectors; each fingerprint version; and the version 1 record bytes and signature rule. A correction to this document MAY clarify wording, add test vectors, or make implementations reject an input that was malformed or on which conforming implementations already gave different results. It MUST NOT change the result of any input that the reference implementation accepts and on which conforming implementations agree. One correction is an exception, made in revision 1.0.1 of the Inkan Standard before any signed record had been published: under the input rules of §7.3, signed records on which the reference and the JavaScript verifier had agreed now read `MALFORMED`. These are a record whose `seq` or `block_height` is negative, or is 2^53 or more and held exactly by a double, and a record read from bytes that are not its canonical bytes, all of which both read `OK`; and a record whose `date` is not a real time in the §7.2 form, which both read `STALE`. Every later correction is bound by this paragraph without exception.

Every result belongs to a version, and no version name is ever reused or redefined. Where no marker is present, the result is `ik1`, `SEAL_VERSION` 3, the SHA-256 fingerprint and record version 1. New versions are added beside the old ones. A new hash is a new anchor version with its own domain. A new seal design is a new `SEAL_VERSION` whose tagged PNG names it under the reserved `inkan-seal` keyword (§5). The suffixes `@t0` and `@t4` to `@t9`, and any new reserved kind, can take effect only in a new `SEAL_VERSION`; under version 3 they are ordinary open kinds (§4.2). An implementation that adds a version keeps generating, verifying and self-testing every earlier version it claims. A verifier treats the version as an explicit part of what it checks, never infers it from an untrusted tag, and reports a version it does not implement as unsupported (for a code, the rejection of §3; for a record, `UNSUPPORTED` in §7.3), never as a pass or a failure. If SHA-256 or secp256k1 weakens, no version 1 result changes: verifiers keep computing version 1 results and add a warning, and a migration adds the new version beside the old one.

## Test Vectors

All values below are produced by the reference implementation and are reproducible with a SHA-256 and UTF-8 alone, apart from these: the ECDSA signatures in TV7 need secp256k1, the TV4 results need NFC and a Unicode database, and the seal RGB digests in TV1, TV2 and TV8 need the Seal v3 construction and its tables (§5).

### 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]
seal rgb_sha256 = 84d8d92167a91a139b00e804519d8e5ea3d0ad720394fc3aa5559ce3733a8a6b (§5)
```

### 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]
seal rgb_sha256 = e41a237845f172634c2a59846557aa45cfcff8c7d142cd5b2ac36d54b7e48596 (§5)
```

### 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)

data entered as "café" with e + U+0301, passed as text, kind = ""
 anchor = ik17288300319e48fa900f4c3f938e938fa4a8c6966f33a8cc8cde58ddd498b3bd1
 (equal to "café" entered with U+00E9)
the same input passed as raw octets 63 61 66 65 cc 81 (not normalized)
 anchor = ik191d9326afef7acdb46684756ccb7db2ec0329cafd74a03a95c567a4aeba3dbe6
 (the anchor of those octets, as for a file; never the anchor of typed text, §2)

data = U+0065 U+0897 U+0327 (text), kind = "" (U+0897 is assigned from Unicode 16.0)
 database in which U+0897 is assigned: NFC = U+0229 U+0897
 anchor = ik174047d56c5a5fae88e083d5c5cd6d2d52c07991d79f10070a88381a492d86351
 database in which U+0897 is unassigned: rejected (§2)
 never: ik1883baf1d9618d2adecec5e4d8b5641edc22ff92568faa9050e00a6e109f282ef
 (the input left unchanged, as a normalizer without U+0897 would leave it)
```

An implementation returns the pinned anchor or rejects the input, according to its Unicode database, and never returns the value marked `never`.

### 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)

high-S twin of der_sig (S replaced by n − S, n the secp256k1 group order):
 3046022100d336085483c2dea86794ef3404d7dc7beef56368fd380000e4d0d8f3ac3fe50c
 022100f5b000b645ee8c2e499072b5cca34a76f88d016d873aadfbfd1dc8dc4e6fe330
status(record, high-S twin, pubkey) = INVALID (§7.2), although it satisfies the ECDSA equation

key and signature form (§7.3):
verify(record, der_sig in upper-case hex, pubkey in upper-case hex) = true
status(record, der_sig, "0x" || pubkey) = MALFORMED
status(record, der_sig with a space between each pair of digits, pubkey) = MALFORMED

status(record with block_height = 9007199254740992 (2^53), any signature, pubkey)
 = MALFORMED (§7.3 rule 1), before any signature check

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 key in this vector is a deliberately public **test** key (private key `0x01` repeated 32 times) and must never sign a real record; live records are published by each publisher and never appear in test material.

### TV8, release provenance (code-provenance ordinal, §7.4)

```
file_bytes = "build-bytes-v1" (14 octets, the payload of TV6)
file_hash = 192b667e91b33c6b0a4477aae3daf7ff2586be329b8b0c36a2d03343ae993199
anchor_data = "192b667e91b33c6b0a4477aae3daf7ff2586be329b8b0c36a2d03343ae993199" (text)
kind = "code"
seed (hex) = 696e6b616e2f76310004636f6465
 3139326236363765393162333363366230613434373761616533646166376666
 3235383662653332396238623063333661326430333334336165393933313939
 = INKAN_DOMAIN || 04 (len "code") || "code" || anchor_data
anchor = 549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
code = ik1549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
resolve_kind("code") = (cells=24, k=7, motif=42) [reserved]
seal rgb_sha256 = 2ce37811263654414954449618db5c4fd6787c3320437ba53c3bd00cb6561184
inscription = tagged seal PNG of anchor under kind "code"
 iTXt inkan-code = ik1549f98f358b1ca9aa486a3fcbda7bd75bc19cf398b84028f71b25d8a3c797923
 iTXt inkan-kind = code

published beside the ordinal (not inscribed, no ik1 code formed from it):
fingerprint("build-bytes-v1", adopter="acme", v=1) = d88657e80e283d5a44bd29b83cf8a93ec60729ec635e642b5454b8829bd3e3c6 (TV6)
```

A client holding the file recomputes every line above from its bytes. A seal drawn from the fingerprint, or an `ik1` code formed from the fingerprint or directly from `file_hash`, is not the provenance mark.

## 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/release-provenance vector set, a Seal v3 vector set (sampled fields, RGB SHA-256s, and constructed minimum-distance pairs), and a Seal v3 coverage set that reaches every entry of every table and the second block of the field stream, published with the Seal v3 tables file (§5). An implementation conforms iff it reproduces these sets for the layers it implements and, if it renders seals, its Seal v3 tables encode to the file §5 pins. Further sets pin the outcome of each rejection rule (text inputs and the Unicode database, §2; adopter names and fingerprint versions, §6; signed records, §7.3; presented PNG files, §5), and each binds every implementation that performs that check.
* **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, the full conformance-vector suite and a single-file web verifier that runs offline in a browser are published at <https://github.com/RexStarBSV/Inkan> (release `inkan-standard-v1.0`); 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 Seal v3 construction of the Inkan Standard and its tables file, which the SHA-256 in §5 identifies exactly; 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** (figures are 2026 classical compute; see the erosion note at the end of this list). 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.
* **Unicode version skew.** Text normalization depends on the Unicode database each runtime ships, and the database grows every year. Without the rule in §2, two conforming implementations, or two releases of one runtime, derive different anchors for the same text containing a recently assigned character, and neither can tell. The rule turns that silent divergence into a visible rejection: an implementation on an older database may refuse an input that a newer one accepts, but no two conforming implementations derive different anchors for an input both accept.
* **Presented images.** A verifier that hands the file to a platform image decoder judges whatever that decoder chooses to draw. An animated GIF named `.png` can carry a genuine seal in the first frame, which is what a canvas reads, while the frames a viewer mostly sees show another mark; a PNG with a colour-space or orientation chunk can look different from the pixels compared. The verifiable-PNG profile (§5) closes this by requiring the verifier to decide on its own decode of a narrow, fixed set of PNG files.
* **Erosion of the forgery figures.** Every visual-forgery cost above is stated in 2026 classical compute and decays. Ordinary hardware growth lowers the classical cost over a decade, and Grover search takes a birthday bound of `2^n` renders toward `2^(n/2)` for an adversary holding a cryptographically relevant quantum computer, which would reduce the `t1` figure of `2^40` toward the order of `2^20`. These figures bound how hard a picture is to counterfeit; they are not the security of the standard. The 32-byte anchor is the trust root, a verifier re-derives it, and none of the guarantees in this section rest on the seal being hard to draw.

## 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).
* Unicode Standard Annex #44, Unicode Character Database (General\_Category, including `Cn`).
* Unicode Character Encoding Stability Policies (normalization stability).
* RFC 6979, Deterministic ECDSA.
* SEC 2 / secp256k1, the ECDSA curve used for on-chain signed records.
* Portable Network Graphics (PNG) Specification, W3C Recommendation (the verifiable-PNG profile, §5).
* RFC 1950, ZLIB Compressed Data Format Specification.
* 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; text revision 1.0.1, 2026-10-10, SHA-256 `5ef0baafc83b1c6e5d80664c889df06a4b22d4e4d6450e81e24bd36983d5e53c`), by RexStarBSV, published at <https://github.com/RexStarBSV/Inkan>: its Seal v3 construction (§3 of that document), its Seal v3 tables file (SHA-256 in §5 here) and its conformance vectors, from which this BRC is drawn.


# 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 version 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-190](/apps/0190) 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-190: Access Gates for Metanet Rooms](/apps/0190)
* [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"


# Locked Content Header (LCH) — Licensed, Micropayment-Enabled Content

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This document specifies the Locked Content Header (LCH), a BSV-native container and licensing protocol for encrypted digital content. An LCH package identifies encrypted bytes, their media type and encryption method, the original rights holders, one or more ways to acquire licensed use, and the evidence needed to enforce or later demonstrate the resulting agreement.

LCH is content-type independent. A conforming application can associate the `.lch` extension with a general LCH handler, inspect the declared inner media type, choose an acquisition profile it fully supports, make the required BSV payment, receive a signed buyer license and decryption capability, and perform only the actions that license permits. The same mechanism applies to audio, video, fonts, documents, software, datasets, model inputs, and formats defined elsewhere.

The protocol also defines metered range acquisition and composition. A derivative LCH can identify source works through C2PA ingredient assertions, retain their downstream policies, and settle a single BSV transaction containing outputs for multiple rights participants. The same Atomic BEEF transaction is delivered directly to every Payee system with single-use BRC-29 derivation material, or durably retained for a Payee that explicitly selected the authorized-output settlement profile; conforming implementations do not depend on chain scanning to discover payment.

LCH adopts the W3C ODRL 2.2 Information Model and Vocabulary as its normative rights-expression system. It does not invent a parallel license language. This BRC defines an LCH ODRL Profile only for the small number of actions, constraints, and duties not already covered by ODRL.

## Status and scope

This BRC defines one extensible protocol with separately declared acquisition profiles. An application need not implement every profile. It can safely use any profile for which it understands every critical mechanism, ODRL term, action, constraint, and duty required for the requested operation, and it must reject that profile otherwise.

The initial profiles cover:

1. fixed-price rendering or use;
2. quantity- or range-metered use;
3. event-metered access such as a page or play;
4. time-bounded rental;
5. composition into a derivative work; and
6. use as AI or machine-learning training input.

BSV payment is the normative payment scope. BRC-105 is the mandatory single-payee HTTP payment mechanism for a full core implementation. BRC-121, multilateral BSV settlement, raw-key delivery, content-decryption modules, stablecoin settlement, or other mechanisms can be selected only by an acquisition profile that identifies them explicitly.

This BRC does not claim that DRM can prevent every copy after plaintext has been rendered. It standardizes confidentiality before acquisition, license evidence, key delivery, behavior by conforming applications, optional hardened enforcement, and the propagation or settlement of agreed downstream duties.

## Motivation

BSV applications already sell fine-grained access to digital content. [Tempo](https://github.com/p2ppsr/tempo) encrypts master audio, stores the ciphertext through UHRP, and releases a key after a BSV payment. [PaperTrade](https://github.com/p2ppsr/PaperTrade) sells authenticated access to individual pages and records an expiring entitlement. Similar systems sell video, data, application interactions, or other resources.

Those applications currently decide independently:

* how content and offers are identified;
* whether terms are embedded or linked;
* how the seller proves authority from the rights holder;
* whether a payment buys a permanent key, a rental, or one event;
* how a buyer license is represented and recovered;
* how seeking and partial acquisition interact with encryption;
* which actions are permitted after decryption;
* how a protected file is opened by an operating system or another application;
* how source licenses propagate into a derivative work; and
* how one transaction pays several rights participants without requiring them to scan the chain.

The historical Locked Content Header document placed terms, price, a payment URL, and a whole-file AES key-release flow in one binary prefix. That establishes the central idea but couples immutable content to one seller and one price, gives no portable buyer license, does not identify seller authority, and cannot support independently authenticated range reads or composition.

LCH separates these concerns while keeping the original user experience. A file named `song.mp3.lch` can open in an LCH-aware player. The player sees that the inner type is `audio/mpeg`, chooses a supported offer, obtains a license, and plays it. It exposes a raw `.mp3` only if the ODRL Agreement permits reproduction or the LCH `unwrap` action. A DAW can instead acquire a composition profile, record the source as a C2PA ingredient, and export a derivative LCH carrying any required downstream policy.

## Design principles

1. **An LCH is actionable by itself.** The header contains at least one complete signed offer or enough signed seller contact information to obtain one. It also contains the encrypted payload or one or more exact locators for it.
2. **Storage is not licensing.** UHRP identifies and locates one byte string. CHIRP adds verified chunking and range retrieval. LCH defines what encrypted bytes represent and how licensed use is obtained.
3. **An offer is not a license.** An ODRL `Offer` proposes use. Payment and acceptance produce a signed ODRL `Agreement` in a buyer license.
4. **Terms and mechanisms are independent axes.** The requested use, payment protocol, offer placement, encryption method, key-delivery method, and enforcement class are named separately inside an acquisition profile.
5. **Unknown means unsupported.** A client never guesses the meaning of an unknown critical extension, ODRL profile, action, constraint, duty, payment method, encryption method, or key-delivery method.
6. **Partial use does not imply whole-content access.** The segmented encryption profile supports independently authenticated records and separately releasable key periods.
7. **Composition preserves provenance and obligations.** C2PA supplies the provenance graph. ODRL supplies permissions and downstream policy. LCH binds those records to licenses, selections, payments, and keys.
8. **Every Payee controls its settlement evidence.** Multilateral payment uses one transaction with multiple outputs. Each Atomic BEEF and remittance is submitted to its Payee system, or to the exact availability and evidence providers that Payee signed before payment.
9. **Wallet consent remains a wallet decision.** Applications describe and label payments. The wallet controls authorization on its side of the BRC-100 boundary.

## Prior art and relationship to other standards

### BSV standards

* [BRC-26](/overlays/0026) defines UHRP content addressing and host discovery.
* [BRC-167](/overlays/0167) defines CHIRP, a chunked Merkle-object layer above UHRP. LCH does not change its integrity, resolution, or hosting rules.
* [BRC-29](/payments/0029) defines the BRC-42-derived P2PKH outputs and remittance material used by the BSV payment profiles.
* [BRC-77](/peer-to-peer/0077) supplies portable identity-key signatures for LCH objects.
* [BRC-78](/peer-to-peer/0078) supplies the first buyer-bound content-key delivery method.
* [BRC-95](/transactions/0095) supplies Atomic BEEF transaction transport.
* [BRC-100](/wallet/0100) supplies the application-to-wallet boundary.
* [BRC-103](/peer-to-peer/0103) and [BRC-104](/peer-to-peer/0104) supply mutually authenticated HTTP.
* [BRC-105](/payments/0105) supplies the primary BSV HTTP monetization framework.
* [BRC-116](/wallet/0116) defines wallet permission behavior, including spending authorization. LCH does not override it.
* [BRC-118](/payments/0118) can carry large BRC-105 payment bodies.
* [BRC-121](/payments/0121) is an optional stateless HTTP 402 mechanism.
* [BRC-224](/apps/0224) defines a composable media recipe. A BMF asset or component may itself be the plaintext content of an LCH; BMF timing and LCH licensing are orthogonal.

The companion specification [Keyed Content and Conditional Key Release](/peer-to-peer/0369), covers generic encrypt-once conditional key release and key commitments. It does not define LCH offers, ODRL licenses, quantity selection, operating-system containers, C2PA composition, or the multilateral receipt workflow. LCH does not depend normatively on that specification; its encryption or key-delivery mechanisms can be incorporated by a future LCH profile.

### External standards

* [ODRL 2.2](https://www.w3.org/TR/odrl-model/) defines Policies, Offers, Agreements, Permissions, Prohibitions, Duties, Constraints, Parties, Assets, and profile processing. Its common vocabulary already includes `play`, `display`, `read`, `execute`, `archive`, `reproduce`, `extract`, `aggregate`, `modify`, `derive`, `distribute`, `present`, `compensate`, `nextPolicy`, `reviewPolicy`, and `watermark`.
* [Readium LCP](https://readium.org/lcp-specs/releases/lcp/latest) demonstrates the separation of encrypted resources from a provider-issued license carrying an encrypted content key and usage restrictions.
* [Encrypted Media Extensions](https://www.w3.org/TR/encrypted-media-2/) and [Common Encryption initialization data](https://www.w3.org/TR/eme-initdata-cenc/) define browser/CDM integration for protected media. They can be registered as LCH enforcement and encryption mechanisms but are not generic payment or license formats.
* [C2PA 2.4](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html) defines signed provenance, current `c2pa.ingredient.v3` ingredient assertions, ingredient relationships, region-of-interest metadata, and composition actions including `c2pa.placed`, `c2pa.mixed`, and `c2pa.remixed`.
* [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html) defines CBOR and deterministic encoding.
* [NIST SP 800-38D](https://csrc.nist.gov/pubs/sp/800/38/d/final) defines AES-GCM authenticated encryption.
* [RFC 8188](https://www.rfc-editor.org/rfc/rfc8188.html) and Tink Streaming AEAD provide prior examples of independently authenticated encrypted records.
* [W3C Media Fragments](https://www.w3.org/TR/media-frags/) supplies optional semantic selectors for temporal and spatial media ranges.

## Terminology

* **Asset**: The protected work and one encrypted representation described by `asset` in an LCH header.
* **Asset ID**: The LCH identifier computed from the canonical Asset Body.
* **Header**: The canonical CBOR LCH Header Object, whether carried in an `.lch` container or separately.
* **Acquisition Profile**: One offered combination of use policy, offer mode, payment mechanism, key-delivery mechanism, encryption mechanism, and enforcement class.
* **Offer**: A signed LCH Offer Object containing an ODRL `Offer` Policy and commercial mechanisms. It does not grant use.
* **License**: A signed buyer-specific LCH License Object containing an ODRL `Agreement`, fulfilled duties, validity, and any keys or DRM capabilities.
* **Rights interest**: A copyright, neighboring right, contractual right, or other identified interest in the Asset.
* **Controller**: The BSV identity key authorized to act for a rights interest.
* **Seller**: The identity that signs an Offer, either as a Controller or under an Authority Grant.
* **License issuer**: The identity that signs and returns a License.
* **Payee**: An identity receiving one output in a purchase transaction. A Seller, issuer, Controller, and Payee can be different parties.
* **Settlement profile**: The Demand-selected rule that defines which signed evidence satisfies that Demand for License issuance.
* **Delivery provider**: A Payee-authorized service that durably retains an exact signed Payment Delivery for later authenticated retrieval by that Payee.
* **Transaction evidence provider**: A Payee-authorized service that evaluates the exact transaction under an identified policy and signs its evidenced lifecycle state.
* **Content-encryption key (CEK)**: A uniformly random secret used by an encryption mechanism.
* **Key period**: A contiguous range of encrypted segments opened by one CEK.
* **Selection**: The whole Asset or normalized ranges requested for an action.
* **Composition Record**: The LCH record that binds C2PA ingredients to source Asset IDs, License IDs, selections, downstream policies, and settlement receipts.
* **Conforming application boundary**: The software or protected module inside which plaintext may exist transiently while only licensed actions are exposed.

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as described by RFC 2119 and RFC 8174.

## 1. Wire format

### 1.1 LCH file framing

An LCH file consists of:

| Offset    |   Length | Value                                                       |
| --------- | -------: | ----------------------------------------------------------- |
| 0         |        4 | `4c 43 48 01` (`LCH` followed by major version `1`)         |
| 4         |        8 | Header length as an unsigned 64-bit big-endian integer      |
| 12        | variable | Exactly that many bytes of deterministic CBOR Header Object |
| following | variable | Optional ciphertext payload                                 |

The optional trailing payload is the exact ciphertext described by `asset.representation`. If it is present, its length and SHA-256 digest MUST match the representation. If it is absent, the representation MUST contain at least one locator. A Header Object can therefore be distributed unchanged either with the payload appended or by itself.

There is exactly one encrypted representation per version 1 Header Object. Publishers that need alternative encryption or packaging create another Header Object over the alternative representation and may use an optional plaintext digest or external work identifier to associate them.

The conventional filename is `<original-name>.<original-extension>.lch`. Operating systems associate the final `.lch` extension with an LCH handler. The original filename and authoritative media type are carried in the Asset Body and MUST NOT be inferred solely from the preceding filename extension.

The provisional media type is `application/vnd.bsv.lch`. It is not asserted to be IANA-registered by this BRC. Protocol messages containing a bare Header Object or another LCH CBOR object use the provisional media type `application/vnd.bsv.lch+cbor` with a `type` parameter where useful.

### 1.2 Deterministic CBOR profile

Every LCH CBOR object MUST satisfy the core deterministic encoding requirements of RFC 8949 section 4.2.1, with these additional restrictions:

1. map keys MUST be text strings;
2. duplicate map keys, indefinite-length items, CBOR tags, floating-point values, `undefined`, and non-shortest integer or length encodings are prohibited;
3. integers MUST be in the range `0` through `2^64-1` unless a field states a smaller range;
4. text MUST be valid UTF-8 and Unicode Normalization Form C;
5. map keys MUST be ordered by the bytewise lexical order of their deterministic CBOR encodings; and
6. a receiver MUST reject an LCH object whose received bytes differ from re-encoding its data model under these rules.

These rules keep identifiers and signatures independent of implementation map order and avoid the ambiguities of floating-point prices or time values.

### 1.3 Diagnostic JSON

Diagnostic JSON is non-normative and MUST NOT be signed or used to compute an identifier. It represents CBOR maps and arrays as JSON objects and arrays. A CBOR byte string is represented as `{ "$bytes": "<unpadded-base64url>" }`. An unsigned integer greater than `2^53-1` is represented as `{ "$uint": "<decimal>" }`. All other allowed values use their ordinary JSON representation.

An implementation producing diagnostic JSON SHOULD preserve the field names and array order from the decoded data model. Converting diagnostic JSON back to CBOR requires the schema; strings beginning with a particular prefix have no special meaning.

### 1.4 Extension processing

Keys in an `extensions` map and entries in a `critical` array MUST be absolute IRIs and MUST NOT repeat. A `critical` entry identifies a profile, mechanism, term, or extension on which correct processing of that object depends. A receiver MUST reject the object or skip the containing acquisition profile if it does not implement every listed critical identifier. It MUST NOT infer semantics from an IRI's spelling.

An unknown extension not listed as critical MAY be ignored for behavior, but its exact bytes remain covered by the enclosing identifier and signature. A non-critical extension MUST NOT weaken or replace a core requirement. New fields outside `extensions` require a new object version unless the applicable map explicitly allows them.

### 1.5 Time values and boundaries

Every LCH integer time field is an unsigned Unix timestamp in whole UTC seconds, ignoring leap seconds. ODRL `dateTime` operands remain XML Schema date-times and are evaluated under ODRL rather than converted in the wire object.

Validity intervals are half-open. `notBefore` is inclusive and `notAfter` is exclusive: an object is active exactly when `notBefore <= now < notAfter`, with a missing upper bound treated as unbounded. `expiresAt` and `recoveryUntil` are also exclusive upper bounds. A new transaction can be created only while `now < expiresAt`; idempotent delivery or recovery can continue only while `now < recoveryUntil`. An interval whose upper bound is not greater than its lower bound is invalid. Implementations MUST use checked unsigned arithmetic and MUST reject timestamps they cannot represent exactly.

### 1.6 Endpoint and locator trust

Every operational endpoint and every HTTPS content, policy, terms, or provenance locator MUST be an absolute HTTPS URL without user information or a fragment. HTTP locations returned while resolving UHRP or CHIRP MUST pass the same checks before use. A general-purpose client MUST reject loopback, unspecified, link-local, private-use, carrier-grade NAT, documentation, benchmark, multicast, and otherwise non-public destination addresses after every DNS resolution. It MUST defend against DNS rebinding and impose finite connection, response, body-size, and redirect limits.

Every redirect target MUST be revalidated. Authentication credentials, payment headers, cookies, BRC-103/104 session material, and other bearer material MUST NOT be forwarded across origins. An endpoint whose peer identity has protocol meaning, including a Seller, Payee, License issuer, or key releaser endpoint, MAY follow only same-origin `307` or `308` redirects. A content or detached-document locator MAY follow at most five redirects after applying these rules.

The authenticated remote identity MUST equal the identity required for its protocol role or be backed by the Authority Grant explicitly permitted for that role. TLS authentication alone does not establish an LCH identity role.

Controlled development deployments MAY opt in to explicitly enumerated local origins. Such an exception MUST be disabled by default, MUST NOT weaken checks for any other destination, and MUST NOT be inferred from a hostname suffix or DNS result.

## 2. Identifiers and signatures

### 2.1 Object preimages and IDs

Each identifiable LCH body has a type from this registry:

`asset`, `header`, `authority`, `offer`, `selection`, `license-request`, `quote`, `payment-demand`, `payment-readiness`, `payment-authorization`, `payment-delivery`, `payment-delivery-retrieval`, `transaction-evidence`, `payment-delivery-ack`, `payment-receipt`, `license`, or `composition-record`.

For type `T`, version 1 body `B`, and deterministic encoding `CBOR(B)`, define:

```
preimage(T, B) = UTF8("LCH/" || T || "/1") || 0x00 || CBOR(B)
id(T, B)       = SHA-256(preimage(T, B))
```

The display IRI is:

```
lch:<type>:sha256:<64 lowercase hexadecimal digest characters>
```

The 32-byte digest, not its display IRI, is carried in CBOR fields ending in `Id` unless a field explicitly requires an IRI.

The Asset ID is computed from the `asset` map alone. Acquisition Profiles can consequently change without changing the Asset ID, while any change to the encrypted representation, declared rights interests, provenance, or composition changes it.

### 2.2 Signed objects

A Signed Object has this shape:

```cddl
signed-object = {
  "body": { * tstr => any },
  "signatures": [1* bstr]
}
```

Each `signatures` item is a complete BRC-77 serialized signature over `preimage(T, body)`. Header, Authority, Offer, Quote, Payment Demand, Payment Receipt, and License signatures MUST use BRC-77's `anyone` verifier. The multilateral profile also requires public BRC-77 `anyone` signatures for its License Request and Payment Deliveries so that every Payee can independently bind the buyer, request, and transaction. A License Request in another profile MAY use the intended issuer as the BRC-77 verifier when that profile requires privately verifiable buyer acceptance.

The signer identity encoded by BRC-77 MUST equal the identity occupying the signer's role in the body. Merely obtaining a valid signature is insufficient: the verifier MUST also validate the authority rules for that object.

An object ID excludes the `signatures` array. Adding another valid signature therefore does not change the object ID.

### 2.3 BSV identity Party IRIs and time

A 33-byte compressed BSV identity key `K`, rendered as 66 lowercase hexadecimal characters, is represented in ODRL as:

```
lch:identity:secp256k1:<K>
```

An ODRL Party corresponds to an LCH identity only when this exact IRI decodes to the same valid compressed secp256k1 public key. Display names, certificate attributes, DNS names, and endpoint origins do not substitute for that comparison.

Unless a field explicitly states otherwise, every LCH integer time is the number of whole seconds since `1970-01-01T00:00:00Z`, ignoring leap seconds. Implementations MUST reject a time outside the range they can process safely and MUST NOT narrow a CBOR `uint` through a floating-point representation.

## 3. Header and Asset Body

### 3.1 Header shape

```cddl
lch-header = {
  "lch": 1,
  "asset": asset-body,
  "acquisition": [1* acquisition-entry],
  ? "authority": [* signed-object],
  ? "extensions": { * tstr => any },
  ? "critical": [* tstr],
  "signatures": [1* bstr]
}

acquisition-entry = inline-entry / discovery-entry

inline-entry = {
  "mode": "inline",
  "offer": signed-object
}

discovery-entry = {
  "mode": "discover",
  "usageProfile": tstr,
  "seller": bstr .size 33,
  "endpoint": tstr,
  ? "authorityIds": [* bstr .size 32],
  ? "critical": [* tstr]
}
```

Header signatures are computed as type `header` over the Header map with `signatures` removed. At least one valid signer MUST be a Controller named in the Asset Body or an identity with an unbroken Authority Grant chain permitting `publishHeader` for this Asset.

Every Header MUST provide at least one acquisition entry. An inline entry carries a complete signed Offer. A discovery entry carries enough immutable information to authenticate and contact the Seller and obtain a signed Offer for the same Asset.

### 3.2 Asset Body

```cddl
asset-body = {
  "mediaType": tstr,
  "name": tstr,
  ? "workId": tstr,
  "representation": representation,
  "rights": [1* rights-interest],
  ? "provenance": provenance-reference,
  ? "composition": composition-record-body,
  ? "metadata": { * tstr => any },
  ? "extensions": { * tstr => any },
  ? "critical": [* tstr]
}

representation = {
  "ciphertextDigest": bstr .size 32,
  "ciphertextLength": uint,
  ? "plaintextDigest": bstr .size 32,
  "encryption": { * tstr => any },
  "locators": [* tstr]
}

rights-interest = {
  "interest": tstr,
  "holder": {
    "name": tstr,
    ? "identifier": tstr
  },
  "controller": bstr .size 33,
  ? "metadata": { * tstr => any }
}

provenance-reference = {
  "mediaType": "application/c2pa",
  "digest": bstr .size 32,
  ? "inline": bstr,
  ? "locator": tstr
}
```

`mediaType` is the media type after decryption and removal of any encryption-profile framing. `name` is a safe suggested filename and MUST NOT contain a path separator, NUL, `.` or `..` as a complete path component, or an absolute path. Applications MUST apply their own filename sanitization and MUST NOT write to a path selected by the header without user or application policy.

`workId` can contain an ISRC, ISWC, DOI, on-chain identifier, publisher identifier, or other IRI. It is descriptive and is not an integrity substitute for the Asset ID.

`plaintextDigest` is optional. Its presence enables cross-representation matching and post-decryption verification but can reveal that ciphertext contains a known file. Publishers omit it when that disclosure is undesirable.

Every original rights holder known to the publisher MUST be named in `rights`. Distinct interests SHOULD be separate entries. Music can, for example, name sound-recording, musical-work, performance, and sample interests independently. `controller` identifies the BSV identity from which seller or issuer authority is verified; it is not by itself a legal adjudication of ownership.

A locator MUST identify the exact ciphertext bytes committed by `ciphertextDigest`. `uhrp://`, `chirp://`, and HTTPS locators are initially recognized. A client MUST verify the digest regardless of transport integrity. Unknown locator schemes are unsupported, not reinterpreted.

## 4. Authority Grants

An identity not directly named as a Controller proves authority with one or more signed Authority Objects.

```cddl
authority-body = {
  "version": 1,
  "assetId": bstr .size 32,
  "grantor": bstr .size 33,
  "grantee": bstr .size 33,
  "interests": [1* tstr],
  "capabilities": [1* tstr],
  ? "policyActions": [1* tstr],
  ? "usageProfiles": [1* tstr],
  "notBefore": uint,
  ? "notAfter": uint,
  "mayDelegate": bool,
  ? "remainingDepth": uint,
  ? "revocationOutpoint": tstr,
  ? "revocationMaxAgeSeconds": uint .gt 0,
  "nonce": bstr .size 16,
  ? "critical": [* tstr]
}
```

The initial capability IRIs are:

| Fragment          | Meaning                                                        |
| ----------------- | -------------------------------------------------------------- |
| `#publishHeader`  | Sign and distribute Headers for the Asset                      |
| `#issueOffer`     | Sign Offers within the stated interests, actions, and profiles |
| `#issueLicense`   | Issue buyer Licenses under an authorized Offer                 |
| `#releaseKey`     | Deliver CEKs or DRM capabilities for an authorized License     |
| `#receivePayment` | Be named as a Payee for the stated interests                   |

The full IRI for fragments defined by this BRC is `https://bsv.brc.dev/apps/0170<fragment>`.

The Authority Object MUST be signed by `grantor`. An authority chain is valid only if:

1. its first grantor is the Controller of every covered interest;
2. every following grantor equals the preceding grantee;
3. every link covers the Asset ID, capability, policy action, and usage profile being exercised;
4. the current time is within every validity interval;
5. every non-final link permits delegation and respects `remainingDepth`; and
6. each `revocationOutpoint`, if present, is unspent under the revocation semantics used by [BRC-52](/peer-to-peer/0052) identity certificates.

An implementation MUST reject cycles, repeated Authority IDs, chains longer than 16 links, and any attempt to widen a capability, action, profile, interest, or validity interval in a delegated grant.

`revocationOutpoint` and `revocationMaxAgeSeconds` MUST either both be present or both be absent. `revocationMaxAgeSeconds` MUST NOT exceed 86,400. The BRC-52 all-zero disabled sentinel MUST NOT be used; an Authority that is not revocable omits both fields.

For each revocable Authority, the verifier MUST query a configured BSV transaction-status source and bind the result to the selected BSV network, the observed chain tip or block height, and an observation time. The source MUST be either a locally validated chain tracker or an explicitly configured trusted service whose authenticated response is retained as verification evidence. Where the source returns BEEF, Merkle paths, headers, or equivalent proof material, the verifier MUST validate it rather than trusting descriptive status text. The result is one of `unspent`, `spent-mempool`, `spent-confirmed`, or `unknown`. Only an `unspent` observation whose age is no greater than `revocationMaxAgeSeconds` is acceptable. The other states fail closed. A mempool spend makes the Authority temporarily unusable; if that spend disappears, a fresh `unspent` result can make it usable again. A reorganization invalidates cached observations affected by the changed chain. An offline verifier MAY rely on a cached result only while it remains fresh by these rules.

Spending an Authority's revocation outpoint prevents future exercise of that Authority. It does not revoke Licenses already validly issued under it; License revocation remains governed by the Agreement and its enforcement mechanism.

An Offer involving several independently controlled interests MUST provide a valid chain for each required interest. Naming a rights holder without proving the relevant seller authority is not sufficient.

## 5. Normative ODRL rights model

### 5.1 Adoption of ODRL

LCH Policies MUST conform to the [ODRL 2.2 Information Model](https://www.w3.org/TR/odrl-model/) and [ODRL 2.2 Vocabulary and Expression](https://www.w3.org/TR/odrl-vocab/). The LCH ODRL Profile identifier is:

```
https://bsv.brc.dev/apps/0170#odrl-profile
```

An Offer Policy MUST have `@type` equal to `Offer`, identify its Seller as `assigner`, target the LCH Asset IRI, and include the LCH profile identifier. A License Policy MUST have `@type` equal to `Agreement`, identify the License issuer or authorized assigner, identify the buyer as `assignee`, target the same Asset, and include the same profile identifier.

ODRL's default `invalid` conflict strategy applies. An LCH policy MUST NOT select `perm` or `prohibit`; an explicit conflict or a conflict produced by inheritance makes the acquisition profile unusable.

### 5.2 Policy bytes

An LCH Policy Reference has this shape:

```cddl
policy-reference = {
  "mediaType": "application/ld+json",
  "digest": bstr .size 32,
  ? "inline": bstr,
  ? "locator": tstr
}
```

At least one of `inline` and `locator` MUST be present. When both are present, they MUST resolve to identical bytes. The bytes MUST be UTF-8 JSON-LD, without a byte-order mark, and their SHA-256 digest MUST equal `digest` before JSON or JSON-LD processing.

The policy SHOULD use `http://www.w3.org/ns/odrl.jsonld` as its ODRL context followed by this LCH context when compact terms are used:

```json
{
  "lchv": "https://bsv.brc.dev/apps/0170#",
  "render": "lchv:render",
  "unwrap": "lchv:unwrap",
  "train": "lchv:train",
  "retainLch": "lchv:retainLch",
  "presentTerms": "lchv:presentTerms",
  "selection": { "@id": "lchv:selection", "@type": "@id" },
  "connectivity": { "@id": "lchv:connectivity", "@type": "@id" },
  "enforcementClass": { "@id": "lchv:enforcementClass", "@type": "@id" },
  "commercialPurpose": "lchv:commercialPurpose",
  "wrapperRequired": "lchv:wrapperRequired",
  "satoshi": "lchv:satoshi"
}
```

The distinct `lchv` prefix is intentional. It MUST NOT be changed to `lch`, because `lch:` is the opaque identifier scheme used by Asset, Offer, License, Selection, and Party IRIs; declaring it as a JSON-LD prefix would rewrite those identifiers during expansion.

Conforming applications MUST ship the exact ODRL 2.2 and LCH profile contexts they support. They MUST NOT fetch and trust a remote context merely because a policy names its URL. An additional or changed context is an unsupported critical semantic dependency unless its exact digest and semantics are supported by the application.

The policy's transmitted bytes are evidence and are never normalized for hashing or signatures. JSON object order has no policy meaning, but changing it changes the Policy Reference digest and therefore creates a different offer or agreement record.

### 5.3 Actions

LCH uses existing ODRL actions wherever their semantics fit:

| Intended operation                       | Normative ODRL action    |
| ---------------------------------------- | ------------------------ |
| transient audio/video rendition          | `odrl:play`              |
| static visual rendition                  | `odrl:display`           |
| reading data or a page                   | `odrl:read`              |
| running software                         | `odrl:execute`           |
| non-transient retention or cache         | `odrl:archive`           |
| copying                                  | `odrl:reproduce`         |
| making a new asset from part of a source | `odrl:extract`           |
| including an asset in a collection       | `odrl:aggregate`         |
| modifying the same asset                 | `odrl:modify`            |
| creating a derivative asset              | `odrl:derive`            |
| supplying an asset to third parties      | `odrl:distribute`        |
| public performance                       | `odrl:present`           |
| payment or royalty                       | `odrl:compensate` duty   |
| downstream policy                        | `odrl:nextPolicy` duty   |
| showing terms to a recipient             | `odrl:reviewPolicy` duty |
| watermarking                             | `odrl:watermark` duty    |

The LCH ODRL Profile adds these Actions. `#render` and `#train` are `odrl:includedIn` `odrl:use`; `#unwrap` is `odrl:includedIn` `odrl:reproduce`, which is itself included in `odrl:use`:

| IRI fragment    | Definition                                                                                                                                         |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `#render`       | Create a transient rendition when no more specific ODRL action describes the media type                                                            |
| `#unwrap`       | Disclose or save raw plaintext outside the conforming application boundary; included in `odrl:reproduce`                                           |
| `#train`        | Use the Asset or selected part as input to training, fine-tuning, evaluation, or adaptation of an AI or machine-learning system                    |
| `#retainLch`    | Duty to distribute the affected asset only in an LCH wrapper carrying the required policy and Composition Record                                   |
| `#presentTerms` | Duty to present identified human-readable terms and obtain the acceptance required by the policy before transferring or rendering to another party |

An application SHOULD request the most specific action. “Export” is not one action: exporting unchanged plaintext exercises `unwrap` and normally `reproduce`; exporting a new work exercises `derive`; giving it to another party also exercises `distribute`.

### 5.4 Constraints and enforcement terms

The LCH Profile adds these left operands:

| IRI fragment         | Right operand                                                |
| -------------------- | ------------------------------------------------------------ |
| `#selection`         | An LCH Selection or its immutable ID                         |
| `#connectivity`      | `#online`, `#offline`, or `#either`                          |
| `#enforcementClass`  | `#advisory`, `#conformingApplication`, or `#protectedModule` |
| `#commercialPurpose` | Boolean                                                      |
| `#wrapperRequired`   | Boolean                                                      |

ODRL's existing `count`, `dateTime`, `elapsedTime`, `meteredTime`, `percentage`, `purpose`, `recipient`, `systemDevice`, `spatial`, and other constraints SHOULD be used rather than equivalent LCH terms.

An ODRL compensation Duty for BSV MUST have a `uid`, `compensatedParty`, and the `odrl:compensate` Action with an Action refinement whose left operand is `odrl:payAmount`, operator is `odrl:eq`, right operand is an `xsd:integer`, and unit is `https://bsv.brc.dev/apps/0170#satoshi`. Fractional, negative, fiat-denominated, or floating-point values are invalid in the initial profiles. The compensated Party uses the identity IRI from section 2.3.

Enforcement classes mean:

* `#advisory`: terms and evidence are displayed or recorded, but technical restriction is not claimed;
* `#conformingApplication`: plaintext remains within an ordinary application boundary and the application exposes only permitted actions; and
* `#protectedModule`: key use or rendering is performed by a declared CDM, secure enclave, trusted execution environment, or equivalent protected path.

Watermarking, online checks, device binding, and usage reporting are orthogonal ODRL duties or mechanism extensions, not additional points in this ordering.

### 5.5 Fail-closed policy processing

For a requested action, a client MUST:

1. resolve and digest-check the exact Policy bytes;
2. validate the ODRL Policy and all named ODRL Profiles;
3. expand any compact Policy properties into their normative atomic Rules;
4. reject conflicts;
5. verify that a Permission covers the requested action, Asset, buyer, Selection, and current constraints;
6. verify every Prohibition and precondition Duty relevant to that Permission;
7. verify that it implements every action, constraint, operator, Party role, Asset relation, and Duty that can affect the result; and
8. deny the action if no fully understood Permission grants it.

An unknown acquisition profile is skipped. An unknown critical term inside an otherwise selected profile makes that profile unusable. It does not make other independent acquisition profiles unusable.

### 5.6 Human-readable terms

Machine policy does not replace prose needed to describe warranties, governing law, attribution form, privacy disclosures, refund policy, or other legal terms. An Offer MAY carry `humanTerms`, an array of Policy References generalized to any media type and optionally including a BCP 47 `language`.

A License MUST bind the digest of every human-readable term that the buyer was required to accept. Where `#presentTerms` applies downstream, the referenced bytes or a digest-verified locator MUST accompany the derivative LCH or transfer workflow.

## 6. Offers and acquisition entries

### 6.1 Offer Body

```cddl
offer-body = {
  "version": 1,
  "assetId": bstr .size 32,
  "usageProfile": tstr,
  "seller": bstr .size 33,
  "licenseIssuer": bstr .size 33,
  "requiredInterests": [1* tstr],
  ? "authorityIds": [* bstr .size 32],
  "policy": policy-reference,
  ? "humanTerms": [* human-term],
  "payment": payment-offer,
  "keyDelivery": key-delivery-offer,
  "enforcement": enforcement-offer,
  "notBefore": uint,
  ? "notAfter": uint,
  "nonce": bstr .size 16,
  ? "extensions": { * tstr => any },
  ? "critical": [* tstr]
}

human-term = {
  "mediaType": tstr,
  "digest": bstr .size 32,
  ? "inline": bstr,
  ? "locator": tstr,
  ? "language": tstr
}

key-delivery-offer = {
  "mechanism": tstr,
  * tstr => any
}

enforcement-offer = {
  "class": tstr,
  ? "mechanism": tstr,
  ? "connectivity": tstr,
  * tstr => any
}
```

The Offer Object MUST be signed by `seller`. The Seller and License issuer MUST have valid authority for every `requiredInterests` entry and every ODRL action granted by the Offer.

The top-level ODRL Offer `uid` MUST equal the LCH Offer IRI computed from the Offer Body. Because this creates a circular dependency if the final ID is inserted before hashing, the transmitted JSON-LD MUST instead use the literal placeholder IRI `lch:offer:self` only as that top-level `uid` value. After the exact Policy bytes have been digest-checked and parsed with the pinned contexts, an evaluator virtually substitutes the computed Offer IRI in its in-memory policy model. It MUST NOT rewrite the transmitted bytes or replace the same string in nested properties, targets, Parties, extension values, or other JSON strings.

The ODRL target MUST be the computed Asset IRI. There is no circularity because the Asset ID excludes acquisition entries.

### 6.2 Inline offers

An inline Offer is immutable within the Header that carries it. Its price, endpoints, policy digests, seller identity, and validity are committed by both the Offer signature and the Header signature. A publisher changes those details by issuing another signed Header, which may reference the same Asset ID and ciphertext.

### 6.3 Discovered offers

For `mode = discover`, the client sends an unsigned or profile-required signed Offer query to `endpoint`, naming the Asset ID, `usageProfile`, requested action, and optional Selection. The response MUST be a Signed Offer Object whose Seller equals the discovery entry and whose Asset ID and usage profile match the query.

Discovery permits current prices, temporary quotes, subscriptions, inventory decisions, or seller changes without altering the Asset Body. It does not permit the returned Offer to silently change the Asset or requested use.

### 6.4 Offer withdrawal

An Offer can expire through `notAfter`. A Seller MAY also publish a signed withdrawal naming its Offer ID through a status endpoint declared as an extension. Withdrawal prevents new Licenses and does not revoke a License already issued or erase CEKs already delivered.

## 7. BSV pricing and selections

### 7.1 Payment Offer

The version 1 Payment Offer below is the normative BSV form. A future non-BSV payment profile, including a stablecoin profile, MUST use a distinct profile and payment-mechanism identifier and define its own asset, unit, precision, settlement finality, proof, refund, and replay semantics; it cannot change the meaning of this map.

```cddl
payment-offer = {
  "protocol": tstr,
  "endpoint": tstr,
  "asset": "BSV",
  "unit": "satoshi",
  "recoveryPeriodSeconds": uint .ge 86400,
  "pricing": fixed-pricing / unit-pricing / quote-pricing
}

fixed-pricing = {
  "kind": "fixed",
  "requirements": [1* payment-requirement]
}

unit-pricing = {
  "kind": "unit",
  "quantityUnit": tstr,
  "unitSize": uint .gt 0,
  "minimumUnits": uint,
  ? "maximumUnits": uint,
  "requirements": [1* payment-requirement]
}

quote-pricing = {
  "kind": "quote"
}

payment-requirement = {
  "dutyUid": tstr,
  "payee": bstr .size 33,
  "buyer": bstr .size 33,
  "endpoint": tstr,
  "satoshis": uint,
  ? "interest": tstr
}
```

Every `dutyUid` MUST identify an ODRL `compensate` Duty in the Offer Policy. Its `compensatedParty` MUST correspond to the Payee described by the requirement, and its `payAmount` MUST equal the amount produced by the LCH pricing calculation. A mismatch invalidates the Offer.

`recoveryPeriodSeconds` declares how long every Payee and the License issuer will support idempotent completion after a valid payment. There is no implicit default. Offer expiry prevents a new purchase but MUST NOT truncate the recovery period of a transaction validly created before expiry.

For fixed pricing, the purchase total is the sum of `satoshis` across all requirements. For unit pricing, each requirement's `satoshis` is the price per `unitSize`; its amount is multiplied by `max(minimumUnits, ceil(quantity / unitSize))`. A `maximumUnits` violation requires a quote or another Offer and MUST NOT be silently capped.

Prices are unsigned integers in satoshis. Every amount and computed total MUST fit the BRC-100 `SatoshiValue` range, and all addition, multiplication, and ceiling operations MUST be checked for overflow before payment. Fiat display values or exchange-rate references MAY appear as non-critical metadata but are never substituted for the committed BSV amount.

If there is one requirement, `protocol` can be BRC-105 or another supported single-payee mechanism. More than one requirement requires the multilateral profile in section 11 unless a future payment protocol explicitly defines equivalent direct delivery to every Payee.

### 7.2 Selection

```cddl
selection =
  { "type": "all" } /
  { "type": "segments", "ranges": ranges } /
  { "type": "bytes", "ranges": ranges } /
  { "type": "pages", "ranges": ranges } /
  { "type": "media-fragment", "value": tstr }

ranges = [1* [start: uint, end: uint]]
```

Ranges are half-open: `start` is included and `end` is excluded. They MUST be sorted, non-empty, non-overlapping, and non-adjacent. A producer MUST merge adjacent or overlapping ranges before signing. Empty ranges and `start >= end` are invalid.

Where a Policy or another object needs an immutable Selection reference, it uses `id("selection", selection)` and the corresponding `lch:selection:sha256:<hex>` IRI under section 2.1.

`segments` refers to encryption segment indices. `bytes` refers to plaintext byte offsets. `pages` refers to a publisher-defined page index beginning at zero. `media-fragment` uses the W3C Media Fragments syntax and is semantic rather than cryptographic.

When segmented AES-256-GCM is used, a Quote and License for bytes, pages, or a media fragment MUST also carry the exact normalized segment ranges whose keys will be delivered. The issuer rounds outward to decodable boundaries and key periods and discloses the resulting coverage and price before payment. Another encryption mechanism MUST define an equally exact coverage representation in its profile. The client MUST NOT assume that a requested ten-second fragment reveals exactly ten seconds of compressed media.

## 8. Segmented AES-256-GCM encryption profile

The mandatory core encryption mechanism is:

```
https://bsv.brc.dev/apps/0170#a256gcm-segmented-v1
```

### 8.1 Encryption descriptor

```cddl
a256gcm-segmented = {
  "algorithm": "https://bsv.brc.dev/apps/0170#a256gcm-segmented-v1",
  "encryptionId": bstr .size 32,
  "plaintextLength": uint,
  "segmentSize": uint .gt 0,
  "segmentCount": uint .gt 0,
  "noncePrefix": bstr .size 4,
  "keyPeriods": [1* key-period]
}

key-period = {
  "keyId": bstr .size 32,
  "firstSegment": uint,
  "segmentCount": uint .gt 0
}
```

`encryptionId`, every CEK, and `noncePrefix` MUST be generated with a cryptographically secure random number generator. Each CEK is exactly 32 uniformly random bytes and MUST NOT be reused for another representation or key period.

The Key ID is:

```
SHA-256(UTF8("LCH key id v1") || 0x00 || CEK)
```

It is both an identifier and a pre-release digest commitment to the uniformly random CEK. A candidate CEK MUST match its Key ID before decryption is attempted.

The key periods MUST partition segment indices `0` through `segmentCount - 1` exactly once, without gaps or overlaps, in increasing order.

For plaintext length `P` and segment size `S`:

```
segmentCount = max(1, ceil(P / S))
ciphertextLength = P + 16 * segmentCount
```

An empty plaintext therefore has one zero-length encrypted segment carrying only its 16-byte authentication tag.

### 8.2 Segment encryption

For zero-based segment index `i`, define:

```
IV  = noncePrefix || uint64be(i)
AAD = UTF8("LCH A256GCM segmented v1") || 0x00
      || encryptionId
      || uint64be(i)
      || uint64be(segmentCount)
      || uint64be(plaintextLength)
      || keyId
```

Encrypt the segment with AES-256-GCM under the CEK for its key period, the 12-byte `IV`, the exact `AAD`, and a 16-byte authentication tag. The record is `ciphertext || tag`. The encrypted representation is the concatenation of records in segment order. IV bytes are not stored per record.

For every non-final segment, ciphertext record offset is:

```
i * (segmentSize + 16)
```

The final record length is `(plaintextLength - segmentSize * (segmentCount - 1)) + 16`, including the tag. These equations let a resolver request only records needed for a Selection.

A decryptor MUST authenticate a complete record before releasing any plaintext from it. A tag failure, wrong Key ID, invalid partition, record reordering, truncation, or extra bytes invalidates the representation. An application MAY render authenticated records before unrelated later records arrive, but it MUST identify the content as partial until the requested Selection is complete.

### 8.3 CHIRP alignment

For a detached ciphertext stored through CHIRP's 4 MiB fixed chunking profile, a publisher that wants one encrypted record per CHIRP leaf SHOULD use a plaintext segment size of `4,194,288` bytes, producing a `4,194,304`-byte ciphertext record after the 16-byte tag. Smaller encryption segments can be packed into a CHIRP leaf when lower key-release or seek granularity is more important.

The `.lch` header framing is not included in the detached ciphertext digest. This prevents the 12-byte container prefix and variable header from shifting CHIRP leaf alignment.

### 8.4 Other encryption mechanisms

An Acquisition Profile MAY identify another encryption mechanism, including media-native CENC, a hardened DRM package, or a future keyed-content BRC. Its identifier MUST specify exact algorithms, nonce and tag rules, associated data, record layout, random-access behavior, key identifiers, and failure behavior. Merely saying “AES-GCM” is insufficient.

Unknown encryption mechanisms make only the affected acquisition profile unusable.

## 9. License request and license

### 9.1 License Request Body

```cddl
license-request-body = {
  "version": 1,
  "offerId": bstr .size 32,
  "assetId": bstr .size 32,
  "buyer": bstr .size 33,
  "action": tstr,
  "selection": selection,
  "acceptedPolicyDigest": bstr .size 32,
  ? "acceptedHumanTermDigests": [* bstr .size 32],
  "requestNonce": bstr .size 16,
  "createdAt": uint,
  ? "mechanismChoices": { * tstr => tstr },
  ? "critical": [* tstr]
}
```

The License Request ID is the request's object ID and is the idempotency key for purchase. The selected profile determines whether the request is a public BRC-77 signature, a signature privately verifiable by the issuer, or an authenticated-session object. A BRC-105 plus BRC-78 core purchase MUST identify the buyer and bind the accepted policy digest before payment.

The issuer MUST reject a request whose Asset, Offer, action, Selection, terms digests, validity, or mechanism choices do not match the Offer.

### 9.2 Availability preflight

Before paying, a client MUST:

1. validate the Header, Asset ID, Offer, authority, ODRL Policy, and requested Permission;
2. resolve at least one ciphertext source and verify its declared length;
3. fetch and digest-verify the whole ciphertext when practical, or verify every storage object and ciphertext record required for the requested Selection;
4. contact the license endpoint and every Payee endpoint required by the selected payment profile; and
5. display or make available to wallet policy the exact total, Payees, requested action, Selection, Offer ID, and terms digests.

The initial unpaid request naturally exercises the BRC-105/121 reachability check contemplated by the historical LCH document. Availability before payment does not prove that the decrypted content is valuable or accurately advertised.

### 9.3 License Body

```cddl
license-body = {
  "version": 1,
  "assetId": bstr .size 32,
  "offerId": bstr .size 32,
  "requestId": bstr .size 32,
  "issuer": bstr .size 33,
  "subject": bstr .size 33,
  "issuedAt": uint,
  ? "notBefore": uint,
  ? "notAfter": uint,
  "agreement": policy-reference,
  "selection": selection,
  ? "segmentSelection": { "type": "segments", "ranges": ranges },
  "fulfillments": [* fulfillment],
  "keyGrants": [* key-grant],
  ? "statusEndpoint": tstr,
  ? "extensions": { * tstr => any },
  ? "critical": [* tstr]
}

fulfillment = {
  "dutyUid": tstr,
  "settlementProfile": tstr,
  ? "receiptIds": [1* bstr .size 32],
  ? "authorizationId": bstr .size 32,
  ? "transactionEvidenceId": bstr .size 32,
  ? "deliveryAcknowledgementId": bstr .size 32
}

key-grant = {
  "keyId": bstr .size 32,
  "delivery": tstr,
  "payload": bstr
}
```

The License MUST be signed by `issuer`, and the issuer MUST be the Offer's License issuer or have `issueLicense` authority. `subject` MUST equal the buyer from the License Request for the initial profiles.

The Agreement Policy MUST target the Asset, identify the subject as assignee, include the selected action and Selection, preserve relevant Prohibitions and downstream Duties, and bind all terms accepted by the buyer. Each fulfilled compensation Duty MUST identify its exact settlement profile and the evidence required by that profile. `#receipt-complete-v1` uses one or more Payment Receipt IDs. `#authorized-output-v1` uses the Payment Authorization, Transaction Evidence, and Delivery Acknowledgement IDs. Fields from another profile MUST NOT be substituted or combined to weaken the selected profile.

The top-level License Agreement `uid` uses the literal `lch:license:self` in transmitted Policy bytes and is interpreted during ODRL processing as the computed License IRI, using the exact digest-check, pinned-context parse, virtual substitution, and no-global-rewrite rule defined for Offers.

If an identical valid payment is resubmitted with the same License Request ID, the issuer MUST return the same License or an equivalent reissued License carrying the same Agreement and key grants. It MUST NOT request another payment. A different request attempting to reuse a payment MUST be rejected.

### 9.4 Validity, offline use, and revocation

Rental, perpetual use, online checks, offline use, device limits, and revocation are determined by the Agreement and selected enforcement mechanism.

If the Agreement permits offline use for a period, the application MUST NOT require network contact during that period merely as an implementation convenience. A status endpoint can stop future use by a conforming online or protected-module profile, but it cannot recall raw CEKs or plaintext already released. Applications and offers MUST describe that limitation accurately.

## 10. Payment and key-delivery mechanisms

### 10.1 BRC-105 single-payee profile

The mechanism identifier is:

```
https://bsv.brc.dev/apps/0170#brc105-single-v1
```

The client POSTs the signed License Request to the Offer payment endpoint over BRC-103/104. The server returns the standard BRC-105 `402 Payment Required` challenge. The client pays the exact one-requirement amount, repeats the same request with `x-bsv-payment`, and receives the signed License.

The BRC-105 server identity MUST equal the requirement Payee or be covered by a valid `receivePayment` Authority Grant. The License issuer MUST verify payment before issuing the License. BRC-118 MAY be used where message size requires multipart transport.

If a valid BRC-105 payment was created before the challenge expired, the Payee and License issuer MUST permit idempotent redelivery of the same request and payment until the challenge expiry plus the Offer's `recoveryPeriodSeconds`. They MUST NOT request a second payment for that License Request during recovery.

### 10.2 BRC-121 profile

The mechanism identifier is:

```
https://bsv.brc.dev/apps/0170#brc121-single-v1
```

The HTTP payment follows BRC-121. Because BRC-121 consumes a payment per request and does not itself define reusable entitlement, the LCH response still MUST be a signed License. A client MUST NOT infer that rereading the protected URL consumes another payment when its License already grants the requested action.

### 10.3 BRC-78 CEK delivery

The mechanism identifier is:

```
https://bsv.brc.dev/apps/0170#brc78-key-v1
```

Each `keyGrant.payload` is a complete BRC-78 serialized encrypted message from an authorized key releaser to the License subject. Its plaintext is exactly:

```
keyId (32 bytes) || CEK (32 bytes)
```

For avoidance of ambiguity, the four serialized BRC-78 version bytes are `42 42 10 33`, in that order, as specified by the normative BRC-78 serialization table. An implementation MUST NOT reverse those bytes based on a display convention or non-normative example.

The BRC-78 Key ID and identity keys are carried by BRC-78 itself. After decryption, the recipient MUST compare the embedded Key ID, the License `keyId`, and the digest-derived Key ID from section 8 before using the CEK.

The key releaser MUST be the License issuer or possess `releaseKey` authority. A License can contain only the key periods needed for its `segmentSelection`.

### 10.4 Raw CEK delivery

The mechanism identifier is:

```
https://bsv.brc.dev/apps/0170#raw-key-v1
```

`keyGrant.payload` is `keyId || CEK` without another encryption layer. This mechanism is permitted only when the acquisition profile requires an authenticated confidential transport such as BRC-103/104 over correctly validated HTTPS, and the Agreement does not require a stronger buyer or device binding.

Raw delivery is not described as legacy. It is a deliberate lower-enforcement profile. Applications MUST NOT store or log the payload outside the storage behavior permitted by the Agreement.

### 10.5 DRM-specific delivery

A DRM mechanism can place a CDM license, key-system message, device-bound capability, or other opaque bytes in `payload`. For such a mechanism, the 32-byte `keyId` is a mechanism-defined capability identifier rather than necessarily the CEK commitment from section 8. Its mechanism specification MUST define that identifier and state persistence, renewal, release, offline, device, output-protection, and error semantics. LCH does not reinterpret opaque DRM policy.

## 11. Multilateral BSV settlement

The mechanism identifier is:

```
https://bsv.brc.dev/apps/0170#brc105-multipay-v1
```

This profile extends the BRC-105/BRC-29 flow for one atomic transaction with several directly notified Payees.

### 11.1 Quote and Payment Demands

The License issuer or Offer endpoint returns a signed Quote containing the License Request ID, Offer ID, Asset ID, exact Selection, expiry, exact total, and one signed Payment Demand for every required ODRL compensation Duty.

```cddl
payment-demand-body = {
  "version": 1,
  "requestId": bstr .size 32,
  "offerId": bstr .size 32,
  "dutyUid": tstr,
  "payee": bstr .size 33,
  "buyer": bstr .size 33,
  "endpoint": tstr,
  "satoshis": uint,
  "derivationPrefix": bstr .size 32,
  "challengeNonce": bstr .size 16,
  "expiresAt": uint,
  "recoveryUntil": uint,
  "settlementProfile": tstr,
  ? "critical": [* tstr]
}

quote-body = {
  "version": 1,
  "requestId": bstr .size 32,
  "offerId": bstr .size 32,
  "assetId": bstr .size 32,
  "selection": selection,
  ? "segmentSelection": { "type": "segments", "ranges": ranges },
  "demands": [1* signed-object],
  "totalSatoshis": uint,
  "expiresAt": uint,
  "recoveryUntil": uint,
  ? "critical": [* tstr]
}
```

Each Demand MUST be signed by its Payee, bind `buyer` to the buyer identity in the License Request, name exactly one settlement profile, and use a fresh, single-use `derivationPrefix` generated or approved by that Payee system. The Quote validator MUST compare every embedded Demand buyer to the signed License Request buyer. The Quote MUST be signed by the License issuer. The Quote total MUST equal the sum of Demand amounts and the ODRL compensation duties. The Quote and every Demand MUST carry the same `expiresAt` and `recoveryUntil`. `recoveryUntil` MUST equal `expiresAt + recoveryPeriodSeconds` from the Offer, with checked unsigned arithmetic.

The mandatory baseline settlement profile is `https://bsv.brc.dev/apps/0170#receipt-complete-v1`. A full core implementation MUST support it. A Demand using `https://bsv.brc.dev/apps/0170#authorized-output-v1` opts into section 11.5. A client, issuer, or Payee that does not implement the exact selected profile MUST fail closed before transaction creation; it MUST NOT interpret an absent or unknown value as the baseline profile. Settlement profile selection belongs to each Payee because it changes which third-party evidence that Payee authorizes the issuer to rely upon.

Every Demand endpoint is independently chosen or approved by that Demand's Payee. It is not inherited from the Offer payment endpoint, and neither shared origin nor shared operation is implied. The License issuer, a recording controller, a composition controller, and a nested source Payee can therefore expose different endpoint authorities and independently controlled receiving wallets. Co-location at one URL is only a deployment shortcut.

Before creating a transaction, the client MUST contact every Demand endpoint directly and obtain a signed Payment Readiness from that Payee:

```cddl
payment-readiness-body = {
  "version": 1,
  "demandId": bstr .size 32,
  "requestId": bstr .size 32,
  "payee": bstr .size 33,
  "buyer": bstr .size 33,
  "issuedAt": uint,
  "readyUntil": uint,
  "recoveryUntil": uint,
  ? "critical": [* tstr]
}
```

The Payment Readiness MUST be signed by the Demand Payee and bind the exact Demand ID, Request ID, Payee, buyer, and recovery deadline. A client MUST require `issuedAt <= current-time < readyUntil <= expiresAt` and an exact `recoveryUntil` match. The Payee thereby states that its durable Demand ledger and receiving service are prepared for idempotent delivery through the Demand recovery window. A coordinator's assertion of reachability is insufficient. The client MUST persist every Payment Readiness with the Quote and MUST repeat readiness acquisition if any `readyUntil` expires before transaction creation.

A Payment Readiness is neither transaction evidence nor a Payment Receipt. It reduces the outage window but MUST NOT satisfy receipt-complete License issuance by itself.

### 11.2 Transaction construction

This specification distinguishes four transaction lifecycle states:

1. **finalized** — signed transaction bytes and their transaction ID exist;
2. **broadcast** — an identified network submission service has accepted a request to propagate those exact bytes;
3. **accepted** — an identified transaction processor has evaluated and accepted the transaction under a stated policy; and
4. **mined** — block inclusion is supported by SPV evidence anchored to an identified chain tip.

An Atomic BEEF proves transaction structure and ancestry sufficient for its verifier; by itself it proves neither broadcast, current processor acceptance, nor block inclusion. A wallet response containing finalized bytes MUST NOT be presented as broadcast, accepted, mined, or simply “paid” without the corresponding evidence. Unless a settlement profile specifies stronger evidence, the multipay construction establishes only the **finalized** state.

The buyer creates one BSV transaction with one BRC-29 P2PKH output per Demand:

1. use BRC-29 protocol ID `[2, "3241645161d8"]`;
2. use that Demand's `derivationPrefix`;
3. for `#receipt-complete-v1`, generate a fresh unique 32-byte `derivationSuffix`; for `#authorized-output-v1`, use only the exact suffix in the valid Payment Authorization;
4. use the Demand Payee identity as counterparty;
5. pay exactly the Demand `satoshis` amount; and
6. permit the wallet to apply its own output-ordering and privacy policy.

For BRC-29 key derivation and wallet remittance, encode each 32-byte prefix and suffix as unpadded base64url. The BRC-43 Key ID is exactly `<base64url-prefix> <base64url-suffix>` with one U+0020 space. No other base64 alphabet, padding, hexadecimal representation, or Unicode normalization is permitted in this profile.

All required outputs MUST be in the same subject transaction. Extra unrelated outputs are allowed, but an LCH License issuer and Payees inspect only the Demand-bound outputs. Before asking the wallet to create an authorized-output transaction, the client MUST independently derive the BRC-29 locking script from the authorized prefix, suffix, buyer wallet, and Payee counterparty and compare it byte-for-byte with the Payee-signed script. A mismatch MUST fail before transaction creation. After the wallet finalizes the transaction, the client MUST derive the expected locking script for every Demand and match it, together with the exact amount, against exactly one previously unmatched output. It records that final output index in the Payment Delivery. A missing, duplicate, or ambiguous match is `ERR_LCH_PAYMENT`. A wallet integration that cannot return the finalized transaction bytes MUST refuse this profile before payment. The transaction is transported as one Atomic BEEF.

### 11.3 Direct delivery

For each Payee, the client sends a Payment Delivery directly to that Payee's Demand endpoint over the declared authenticated transport:

```cddl
payment-delivery-body = {
  "version": 1,
  "demandId": bstr .size 32,
  "requestId": bstr .size 32,
  "buyer": bstr .size 33,
  "atomicBeef": bstr,
  "outputIndex": uint,
  "derivationPrefix": bstr .size 32,
  "derivationSuffix": bstr .size 32,
  ? "critical": [* tstr]
}
```

The Payee MUST verify the Demand, require the Payment Delivery signer and `buyer` to equal the Demand buyer, and verify the amount, derived locking script, output index, Atomic BEEF, transaction validity under its wallet policy, and single-use prefix before calling its receiving BRC-100 wallet's `internalizeAction` with protocol `wallet payment`. The payment remittance contains the unpadded-base64url `derivationPrefix`, unpadded-base64url `derivationSuffix`, and hexadecimal buyer identity. When independently deriving the receiving public key through BRC-100, the Payee wallet uses the buyer as counterparty and `forSelf: true`.

The client MUST persist the Quote, Payment Readiness objects, any Payment Authorizations, finalized Atomic BEEF, every remittance, and every signed Payment Delivery before the first network delivery. It then fans the Deliveries out independently and retains each valid Receipt as it arrives. Under `#receipt-complete-v1`, completion requires exactly one valid Receipt for every Demand. Under `#authorized-output-v1`, section 11.5 defines an additional exact evidence bundle that can satisfy only a Demand that selected that profile. A failed or slow Payee delivery MUST NOT cause already successful delivery evidence to be discarded or the transaction to be recreated. The License issuer coordinates completion but is not an implicit payment relay and MUST NOT treat its own unsigned acknowledgement, a timeout, or Payee silence as settlement evidence.

On success it returns a signed Payment Receipt:

```cddl
payment-receipt-body = {
  "version": 1,
  "demandId": bstr .size 32,
  "requestId": bstr .size 32,
  "payee": bstr .size 33,
  "txid": bstr .size 32,
  "outputIndex": uint,
  "satoshis": uint,
  "receivedAt": uint,
  ? "critical": [* tstr]
}
```

`txid` is the 32 bytes obtained by hex-decoding the conventional 64-character transaction ID without reversing its displayed byte order.

Exact redelivery of the same transaction and output for the same Demand MUST return the same Receipt or an equivalent newly signed Receipt. It is an idempotent recovery, not a second use. A different transaction, output, buyer, or request attempting to reuse the Demand MUST be rejected.

The buyer submits the Atomic BEEF and exactly one profile-valid settlement proof for every Demand to the License issuer. Before issuing the License, the issuer MUST independently verify the transaction ID, every required and distinct output index, exact amounts, Demand and Request bindings, required signatures, and evidence IDs. For `#receipt-complete-v1`, the receiving Payee's signed Receipt attests the BRC-29 locking-script and wallet-policy checks that require its private derivation context; an issuer that is not that Payee cannot reproduce that private derivation. For `#authorized-output-v1`, the Payee discloses and signs the exact destination before payment, allowing the issuer to verify it under section 11.5. A coordinator cannot omit all profile-valid evidence merely because an output probably exists.

### 11.4 Failure and recovery

One transaction makes the outputs atomic within the finalized transaction, but direct deliveries can fail independently and the transaction may or may not yet have stronger network evidence. Signed readiness reduces but cannot eliminate that risk. Payee systems and the License issuer MUST accept idempotent recovery while the current time is less than the signed `recoveryUntil` for a transaction created before `expiresAt`. No new transaction may be created at or after `expiresAt` for that Quote. The client MUST retain the Quote, Readiness objects, Atomic BEEF, remittance, signed Deliveries, and successful Receipts until it obtains the License or the recovery deadline passes.

After transaction creation and before License issuance, an application MUST expose an explicit pending-settlement state. It MUST show the strongest evidenced transaction lifecycle state, the settlement profile and received proof type for each Demand, the number of satisfied and required Demands, and the recovery deadline. It MUST NOT collapse a missing proof into a fresh purchase path or describe finalized-only bytes as a completed payment. When the recovery deadline passes without the evidence required by each selected profile, the application MUST surface an unresolved settlement and MUST NOT release key material.

A wallet MUST NOT create a replacement transaction automatically after an ambiguous failure. It first retries delivery and license recovery with the existing transaction.

The v1 transaction and Atomic BEEF do not prove when an unconfirmed transaction was created. A Payee therefore MUST NOT claim that recovery-time acceptance by itself proves pre-expiry creation. The client and wallet remain responsible for the `expiresAt` creation rule. A deployment that requires independently enforceable creation-time evidence MUST select a future critical commitment or timestamp mechanism that defines that evidence; it MUST NOT infer it from transaction lock time, first HTTP receipt time, or first-seen time.

### 11.5 Payee-authorized output settlement

The offline-capable settlement profile identifier is:

```
https://bsv.brc.dev/apps/0170#authorized-output-v1
```

This profile addresses a narrow availability failure: a Payee was online before payment and explicitly authorized an exact receiving output and independent fallback services, but its wallet or direct Delivery endpoint is unavailable after the one multilateral transaction has been finalized. It does not turn silence, an ordinary output, or issuer observation into Payee consent.

Before transaction creation, the Payee endpoint MUST return this signed object idempotently for the exact Demand:

```cddl
payment-authorization-body = {
  "version": 1,
  "settlementProfile": "https://bsv.brc.dev/apps/0170#authorized-output-v1",
  "demandId": bstr .size 32,
  "requestId": bstr .size 32,
  "payee": bstr .size 33,
  "buyer": bstr .size 33,
  "satoshis": uint,
  "derivationPrefix": bstr .size 32,
  "derivationSuffix": bstr .size 32,
  "lockingScript": bstr,
  "authorizedAt": uint,
  "authorizedUntil": uint,
  "recoveryUntil": uint,
  "evidenceProvider": bstr .size 33,
  "evidenceEndpoint": tstr,
  "evidencePolicy": "https://bsv.brc.dev/apps/0170#signed-processor-acceptance-v1",
  "minimumTransactionState": "accepted",
  "deliveryProvider": bstr .size 33,
  "deliveryEndpoint": tstr,
  "retrievalEndpoint": tstr,
  ? "critical": [* tstr]
}
```

The Authorization MUST be signed by `payee`; bind every repeated field exactly to the Demand; satisfy `authorizedAt <= now < authorizedUntil <= expiresAt`; and carry the exact Demand recovery deadline. The Payee generates or approves the single-use suffix through its receiving wallet and derives `lockingScript` under BRC-29 using the Demand buyer as counterparty and `forSelf: true`. The script MUST be nonempty and no larger than 10,000 bytes. A Payee MUST return the same Authorization for an idempotent repeat of the same Demand and MUST NOT authorize a second destination for it.

The initial evidence policy is `#signed-processor-acceptance-v1`. It supports exactly the `accepted` minimum and this object:

```cddl
transaction-evidence-body = {
  "version": 1,
  "authorizationId": bstr .size 32,
  "txid": bstr .size 32,
  "provider": bstr .size 33,
  "state": "accepted",
  "policy": "https://bsv.brc.dev/apps/0170#signed-processor-acceptance-v1",
  "observedAt": uint,
  ? "critical": [* tstr]
}
```

The named evidence provider MUST independently parse the exact Atomic BEEF, require exactly one output with the authorized amount and script, evaluate the transaction under its stated processor-acceptance policy, and sign the exact transaction ID and Authorization ID. `authorizedAt <= observedAt < recoveryUntil` is required. The provider MUST atomically bind an Authorization to the first accepted transaction ID and reject a conflicting transaction for that Authorization. Buyer assertion, wallet finalization, submission for broadcast, and an issuer-created label are insufficient. A future mined-state policy must define and carry verifiable SPV evidence; this v1 policy MUST NOT relabel a provider signature as mined evidence.

Before fallback completion, the buyer MUST submit the exact signed Payment Delivery to the authorized Delivery provider and obtain:

```cddl
payment-delivery-ack-body = {
  "version": 1,
  "authorizationId": bstr .size 32,
  "deliveryId": bstr .size 32,
  "demandId": bstr .size 32,
  "requestId": bstr .size 32,
  "payee": bstr .size 33,
  "provider": bstr .size 33,
  "storedAt": uint,
  "availableUntil": uint,
  "retrievalEndpoint": tstr,
  ? "critical": [* tstr]
}
```

The Delivery provider MUST durably store the byte-exact Authorization and buyer-signed Delivery before signing the Acknowledgement, and MUST durably record that signed Acknowledgement before returning it. Its identity and retrieval endpoint MUST equal the Authorization. `authorizedAt <= storedAt < recoveryUntil`, `storedAt < availableUntil`, and `availableUntil >= recoveryUntil` are required. Exact repeated storage returns the same Acknowledgement; conflicting Delivery bytes for the Authorization are rejected.

The Payee retrieves the stored Delivery with a fresh signed request:

````cddl
payment-delivery-retrieval-body = {
  "version": 1,
  "authorizationId": bstr .size 32,
  "payee": bstr .size 33,
  "requestedAt": uint,
  "nonce": bstr .size 16,
  ? "critical": [* tstr]
}

The request MUST be signed by the Authorization Payee and bind the exact Authorization ID. The provider authenticates that signature and, while `requestedAt < availableUntil`, returns the exact signed Authorization, Delivery, and Acknowledgement. Implementations SHOULD apply authenticated transport and access logging as defense in depth. A native message-box binding MAY satisfy the same durable retrieval role only when its registered profile defines equivalent authentication, retention, correlation, and replay behavior.

An authorized-output evidence bundle is:

```cddl
authorized-output-evidence = {
  "authorization": signed-object,
  "delivery": signed-object,
  "transactionEvidence": signed-object,
  "deliveryAcknowledgement": signed-object
}
````

The issuer MAY issue the License for an authorized-output Demand without a Payee Receipt only after independently verifying all four objects, their signatures and IDs, the exact completion Atomic BEEF, one distinct authorized output index and amount, all Demand/Request/buyer/Payee bindings, the accepted evidence policy, and retention through recovery. Each Quote Demand MUST be satisfied exactly once: by its valid Payee Receipt, or—only when that Demand selected this profile—by its valid authorized-output bundle. Mixing both for one Demand, reusing an output index, using a bundle for `#receipt-complete-v1`, or using an unknown evidence policy MUST fail closed.

Late Payee internalization remains required operational behavior. When the Payee returns, it retrieves the stored Delivery, performs the ordinary section 11.3 wallet validation and idempotent `internalizeAction`, and returns a Receipt. A late Receipt does not revoke, duplicate, or alter the already issued License and MUST NOT create another buyer transaction.

This availability improvement deliberately changes trust and privacy boundaries. The Payee delegates temporary Delivery availability and transaction-acceptance judgment to named providers; the issuer and buyer depend on those providers remaining available; processor acceptance can later be invalidated by conflict or reorganization; the exact destination script and provider relationships become linkable protocol evidence; and key release may occur before wallet internalization or mining. A Payee that does not accept those tradeoffs selects `#receipt-complete-v1`. A provider outage leaves the one transaction pending; it never permits a weaker proof. Deployments SHOULD separate Payee wallet, Delivery provider, evidence provider, and issuer failure domains, even though a small reference fixture may co-locate roles while preserving distinct identities, endpoints, stores, and signed responsibilities.

### 11.6 Deterministic-CBOR HTTP binding

The multilateral acquisition endpoint uses `POST` and deterministic LCH CBOR. The exact request media type is:

```
application/vnd.bsv.lch+cbor; type=<message-type>
```

The response uses the same media type with the response message type. Parameters are ASCII case-insensitive as HTTP media types, but a sender SHOULD emit the lowercase form above. The following operations are initially registered:

| Request message type            | Request value                                                   |        Success | Response message type and value                                 |
| ------------------------------- | --------------------------------------------------------------- | -------------: | --------------------------------------------------------------- |
| `license-request-preflight`     | signed License Request                                          |          `204` | no body                                                         |
| `license-request`               | signed License Request                                          |          `200` | `quote`, signed Quote                                           |
| `payment-demand`                | signed Payment Demand                                           |          `200` | `payment-readiness`, signed Payment Readiness                   |
| `payment-authorization-request` | signed Payment Demand                                           |          `200` | `payment-authorization`, signed Payment Authorization           |
| `payment-delivery`              | signed Payment Delivery                                         |          `200` | `payment-receipt`, signed Payment Receipt                       |
| `payment-delivery-store`        | `{ "authorization": signed-object, "delivery": signed-object }` |          `200` | `payment-delivery-ack`, signed Delivery Acknowledgement         |
| `payment-delivery-retrieval`    | signed Payment Delivery Retrieval                               | `200` or `404` | `payment-delivery-stored`, stored-delivery map below when found |
| `transaction-evidence-request`  | `{ "authorization": signed-object, "atomicBeef": bstr }`        |          `200` | `transaction-evidence`, signed Transaction Evidence             |
| `payment-completion`            | Payment Completion map below                                    |          `200` | `license`, signed License                                       |
| `license-recovery`              | `{ "requestId": bstr .size 32 }`                                | `200` or `404` | `license`, signed License when found                            |

```cddl
payment-completion = {
  "request": signed-object,
  "quote": signed-object,
  "atomicBeef": bstr,
  "receipts": [* signed-object],
  ? "authorizedOutputs": [* authorized-output-evidence]
}

stored-delivery = {
  "authorization": signed-object,
  "delivery": signed-object,
  "deliveryAcknowledgement": signed-object
}
```

Payment Completion MUST contain at least one settlement proof and exactly one profile-valid proof for each Demand in its Quote. An empty `receipts` array is therefore valid only when every Demand is satisfied by an authorized-output bundle.

The License Request preflight and Quote operations use the Offer payment endpoint. Payment Demand preflight, Payment Authorization, and ordinary Payment Delivery use that Demand's endpoint. Storage, retrieval, and transaction-evidence operations use only the endpoints signed in the Payment Authorization. Payment Completion and License recovery use the Offer payment endpoint. A client MUST dispatch by the signed endpoint on each object; it MUST NOT rewrite all destinations to the issuer origin. An implementation MAY mount all roles at the same URL, but MUST still verify every signed role, provider, and Payee identity independently.

This section initially registers the synchronous HTTP binding. A Payee can place that endpoint in front of an asynchronous inbox or message-box service, provided the externally observable request, response, identity, bounds, idempotency, and recovery behavior remains exactly this binding. A future native asynchronous or message-box binding can be registered by an acquisition profile with its own endpoint syntax, authentication, correlation, acknowledgement, polling or reply, expiry, and replay rules. Until then, an implementation-specific transport adapter is not portable protocol evidence and MUST NOT change or omit the signed Demand, Delivery, or Receipt objects.

Every body MUST be deterministic CBOR and MUST be bounded while streaming. Responses MUST use `Cache-Control: no-store`. A conforming browser endpoint SHOULD support CORS for the origins it serves. Unsupported media or message types, malformed CBOR, invalid signed objects, expired Quotes, payment failure, and unavailable recovery MUST fail without issuing key material. An error response SHOULD carry deterministic CBOR `{ "code": tstr }` with media type `application/vnd.bsv.lch+cbor; type=error`; HTTP status does not replace the stable LCH error code.

Exact redelivery is idempotent at both Payment Delivery and Payment Completion. A server MUST durably claim a Demand before calling the receiving wallet, return the stored Receipt for the same transaction fingerprint, and reject another transaction fingerprint. Multi-replica deployments require an atomic shared claim; an in-process map is sufficient only for a single-process reference deployment.

## 12. Quantity-controlled key release

The segmented profile can encrypt the whole Asset under one key period or many. A quantity-controlled Offer SHOULD use key periods no larger than the smallest separately purchasable range.

The Quote converts the semantic Selection to `segmentSelection` and lists the exact key periods that will be granted. If a requested range touches any part of a key period, the entire period is disclosed and billable under the Offer's rounding rule. The Quote MUST make this enlargement visible before payment.

For the License's exact `segmentSelection`—or the complete segment range when the semantic Selection is `all` and `segmentSelection` is omitted— `keyGrants` MUST contain every intersecting key period exactly once and MUST NOT contain any other key period. This rule also applies to a whole-Asset License: omitting a later key period is an incomplete delivery, not a valid partial implementation. Possession of ciphertext or a CHIRP leaf does not imply entitlement to every encrypted segment inside it.

Quantity units can include segments, plaintext bytes, pages, events, milliseconds, tokens, samples, or another profile-defined integer unit. The signed Quote is authoritative where compressed-media indexes, transformations, or nested compositions make local price calculation ambiguous.

## 13. Composition and C2PA

### 13.1 Required C2PA provenance

An LCH Asset created through `odrl:aggregate`, `odrl:extract`, or `odrl:derive` MUST carry a C2PA 2.4 Manifest reference in `asset.provenance` and a Composition Record in `asset.composition`. Performing LCH `train` alone does not make the trained model or another output a derivative Asset under this rule. When a resulting LCH Asset affirmatively claims training provenance, each explicitly identified training source MUST be recorded as an `inputTo` ingredient. Version 1 does not define dataset-root, batch, or aggregate-source commitments; an application needing them MUST use a future critical profile rather than expanding one ingredient into an implied source graph.

For each source Asset:

1. the C2PA Manifest MUST contain a current `c2pa.ingredient.v3` assertion;
2. a visible or audible component uses relationship `componentOf` and a corresponding `c2pa.placed` action;
3. an AI or process input that is not a component uses relationship `inputTo` where C2PA prescribes it;
4. audio composition SHOULD also record `c2pa.mixed` or `c2pa.remixed` as appropriate;
5. partial use SHOULD be described with C2PA ingredient assertion metadata and `regionOfInterest`; and
6. for an LCH source, the ingredient assertion metadata MUST carry the custom field `org.bsv.lch.sourceAsset` whose value is the source LCH Asset IRI; `informationalURI` SHOULD carry the same IRI.

The `org.bsv.lch.sourceAsset` field is an entity-specific assertion-metadata extension permitted by C2PA; it replaces the older `documentID` convention, which is not a member of the current v3 ingredient map. If a source lacks a C2PA Manifest, C2PA permits an ingredient assertion without `activeManifest`. The LCH Asset ID and source media type are still recorded. A derived Asset MUST NOT fabricate an active source manifest.

C2PA validates provenance claims; it does not prove that a license was granted or paid. The Composition Record supplies that binding.

### 13.2 Composition Record

```cddl
composition-record-body = {
  "version": 1,
  "c2paManifestDigest": bstr .size 32,
  "ingredients": [1* composition-ingredient],
  ? "critical": [* tstr]
}

composition-ingredient = {
  "sourceAssetId": bstr .size 32,
  "sourceLicenseId": bstr .size 32,
  "c2paIngredient": c2pa-hashed-uri,
  "relationship": "componentOf" / "inputTo",
  "sourceSelection": selection,
  "derivedSelection": selection,
  "mappingProfile": tstr,
  ? "nextPolicy": policy-reference,
  ? "settlementReceiptIds": [* bstr .size 32],
  ? "metadata": { * tstr => any }
}

c2pa-hashed-uri = {
  "url": tstr,
  ? "alg": tstr,
  "hash": bstr
}
```

Every source License MUST grant the action used to create the derivative and cover `sourceSelection`. `c2paIngredient` MUST reproduce the C2PA hashed URI that resolves to the corresponding ingredient assertion in the referenced C2PA Manifest; matching only `sourceAssetId` is insufficient, including when the same source is placed more than once. `alg` is omitted only where C2PA unambiguously inherits it from the Claim. `c2paManifestDigest` MUST equal `asset.provenance.digest`.

The initial mapping profile is:

```
https://bsv.brc.dev/apps/0170#whole-placement-v1
```

Under `#whole-placement-v1`, `derivedSelection` MUST be `{ "type": "all" }`. Any nonempty selection of the derivative therefore activates the ingredient's complete normalized `sourceSelection`. Each repeated placement is represented by a distinct ingredient with a distinct `c2paIngredient` binding, even when the source Asset and License are the same.

Whole placement is deliberately conservative, not edit-restrictive. A License that grants the applicable `modify`, `extract`, or `derive` action can be used for trimming, repetition, time stretching, reversal, distortion, frame-accurate edits, coordinate transforms, mixing, and other editorial operations without registering another LCH mapping profile. Those operations do not change which permissions were granted. Applications MAY describe them in C2PA actions, ingredient metadata, an edit decision list, or the Composition Record's non-critical `metadata`; a resolver ignores that descriptive metadata when determining obligations. The complete declared `sourceSelection` remains active for every nonempty selection of the derivative.

Composition Record metadata still obeys the deterministic CBOR restrictions in section 1.2. An application recording a fractional playback rate or scale therefore uses an exact integer ratio or another integer-unit representation, not a CBOR floating-point value. Version 1 does not standardize the vocabulary of that descriptive edit metadata.

This profile does not claim selective or proportional mapping for those edits. A future mapping profile is needed only when an implementation wants a deterministic rule that maps part of a requested derivative back to part of a source, or computes proportional obligations from such a mapping. It MUST define the source and derived units, normalization, transform, intersection, boundary, and rounding rules. An unknown mapping profile is critical to the ingredient and MUST fail closed. C2PA region, action, timeline, or transform metadata provides provenance context but MUST NOT be substituted for a selective settlement mapping profile.

If a source Agreement contains an `odrl:nextPolicy` Duty, the exact downstream Policy is carried in `nextPolicy` and MUST be applied to the derivative. If the obligation was discharged when the derivative was created, the corresponding signed Receipt IDs are carried. If it is triggered by future rendering, distribution, training, or another action, it remains live in the derivative's Offers and Licenses.

### 13.3 Nested payment resolution

When an action on a derived Selection triggers downstream compensation:

1. inspect only Composition Record entries whose `derivedSelection` intersects the requested Selection;
2. map the intersection to the applicable `sourceSelection` under the ingredient's `mappingProfile`;
3. evaluate each source `nextPolicy` for the requested downstream action;
4. recurse through any source Composition Records;
5. flatten the resulting compensation Duties into signed Payment Demands;
6. create one transaction with all required outputs; and
7. deliver that transaction directly to every Payee under section 11.

Resolvers MUST track `(Asset ID, Duty uid, Selection)` tuples, reject cycles, and support at least 32 nesting levels. They MUST reject rather than truncate a graph exceeding their supported depth. Identical Duties are fulfilled once only where ODRL's shared Duty `uid` semantics and the policy's aggregation rule say they are identical; matching Payee keys alone is not enough to deduplicate obligations.

This supports, for example, a movie whose selected scene contains a song, whose source License in turn carries obligations for independently licensed stems. The movie Seller can receive one output while the song and stem Payees receive their own outputs in the same transaction and receive the Atomic BEEF directly.

### 13.4 Export behavior

A raw derivative can be exported only if its Agreement grants the required `derive`, `reproduce`, `unwrap`, and, where applicable, `distribute` actions and no applicable `#retainLch` Duty remains.

If `#retainLch` applies, the exported work MUST remain LCH-wrapped and carry the required Composition Record and downstream Policy. If `#presentTerms` applies, the receiving workflow MUST present and record acceptance of the referenced terms. These rules are evaluated from the License; LCH does not impose one export policy on all works.

## 14. Initial acquisition profiles

The following usage profile IRIs define initial interoperable behavior. Each Offer still declares the exact payment, key-delivery, encryption, and enforcement mechanisms it selects.

These profiles standardize portable licensing behavior, not an application data model. Catalogue discovery, channel and social metadata, recommendations, thumbnails, previews, waveforms, DAW timelines, edit decision lists, royalty weighting, and similar application concerns remain in application schemas or separately identified critical profiles. Once interoperable semantics for one of those concerns are stable, a future BRC can register it without changing the v1 Header, License, or whole-placement rules.

### 14.1 Fixed render

`https://bsv.brc.dev/apps/0170#fixed-render-v1`

* Grants one or more of `play`, `display`, `read`, `execute`, or `render`.
* Uses fixed BSV pricing.
* A full core implementation supports BRC-105, segmented AES-256-GCM, and BRC-78 key delivery.
* Offline, archive, unwrap, copy, print, and redistribution rights are determined solely by the Agreement.

### 14.2 Metered range

`https://bsv.brc.dev/apps/0170#metered-range-v1`

* Requires a Selection and an exact signed Quote.
* Uses independently releasable key periods.
* Prices an integer quantity or returns exact Payment Demands.
* The License includes only intersecting key periods and exact segment coverage.

### 14.3 Metered event

`https://bsv.brc.dev/apps/0170#metered-event-v1`

* Covers a page, play, open, render, inference, or other counted event.
* Uses ODRL `count`, `event`, `dateTime`, or related constraints.
* MAY use BRC-105 or BRC-121.
* The resulting License records whether the event produced a reusable entitlement, a time-bounded entitlement, or exactly one completed action. A server MUST NOT charge a reread that the existing Agreement already permits.

### 14.4 Rental

`https://bsv.brc.dev/apps/0170#rental-v1`

* Uses an ODRL Agreement with `dateTime`, `elapsedTime`, or `meteredTime` constraints.
* Explicitly declares connectivity and enforcement class.
* Offline playback is permitted only when the Agreement says so; online enforcement is not inferred from the word “rental.”

### 14.5 Composition

`https://bsv.brc.dev/apps/0170#compose-v1`

* Grants `aggregate`, `extract`, or `derive` for a Selection.
* Requires C2PA and a Composition Record.
* Preserves or fulfills `nextPolicy`, `retainLch`, `presentTerms`, compensation, attribution, reporting, and watermark Duties.
* Uses multilateral settlement when more than one Payee must be paid for the action.

### 14.6 AI training

`https://bsv.brc.dev/apps/0170#training-v1`

* Grants LCH `train` for a declared Selection, purpose, model class, or other ODRL constraints.
* Does not imply display, redistribution, extraction for unrelated use, or ownership of the source.
* Does not require a Composition Record merely because training occurred. When a resulting LCH Asset explicitly claims training provenance, every individually identified source uses a C2PA `inputTo` ingredient and an LCH Composition Record; dataset-root and batch commitments await a future profile.
* Carries any attribution, reporting, deletion, output-policy, compensation, or wrapper Duties in ODRL.

Technical DRM cannot generally prove that plaintext was never used in an undeclared training process. This profile standardizes permission, payment, evidence, and conforming-tool behavior without overstating enforcement.

## 15. Application and wallet behavior

An application claiming LCH support MUST publish or expose the exact usage profiles, payment mechanisms, encryption mechanisms, key-delivery mechanisms, enforcement classes, ODRL terms, and selector types it implements. A bare claim of “BRC-170 compatible” is insufficient.

Applications MUST pass payment construction through the BRC-100 wallet boundary. They SHOULD label the action with the Asset ID, Offer ID, usage profile, requested action, and a human-readable description suitable for [BRC-65](/wallet/0065) and [BRC-111](/wallet/0111) wallet policy. They MUST NOT infer permission to spend from an LCH Policy; wallet spending authorization is separate from content permission.

A wallet MAY approve repetitive low-value purchases according to its own persisted permissions, counterparty trust, budgets, labels, and transaction details. This BRC neither requires a prompt for every micropayment nor authorizes an application to bypass wallet policy.

## 16. Reference conformance cases

The following cases are normative as to expected pass/fail behavior; brands, titles, amounts, endpoints, and keys in their data are illustrative.

1. **Purchased MP3.** `song.mp3.lch` offers `play` for a fixed BRC-105 payment. The License permits `play` and `archive`, prohibits `unwrap`, and permits offline playback. A player decrypts authenticated records and plays them; an unwrapper rejects export.
2. **Web font.** `font.woff2.lch` offers `display` for a fixed price and raw-key delivery over mutually authenticated HTTPS. A browser integration loads it through `FontFace`, preserves WOFF/OpenType license metadata, and does not expose a downloadable WOFF2 because `unwrap` and `distribute` are absent.
3. **Pay-per-page publication.** A document has free page zero and a `metered-event-v1` Offer for other pages. Buying page four returns an Agreement permitting `read` for that page for 30 days. Reopening page four within that interval does not pay again.
4. **Range audio acquisition.** A long recording uses four key periods. A request for segments `[2,4)` returns keys only for intersecting periods. Those records authenticate; an attempt to decrypt another period has no granted key and fails closed.
5. **DAW composition.** A loop License grants `derive` for a selected interval and requires a downstream Policy. A song repeats the loop, reverses one placement, time-stretches another, and distorts a third. Every placement has a distinct C2PA v3 `componentOf` ingredient; the edit descriptions are non-critical provenance metadata; every ingredient still uses `whole-placement-v1`; and the Composition Record carries the required `nextPolicy`. The edits neither expand the License nor require a selective mapping profile.
6. **Nested movie settlement.** A requested movie scene intersects a song ingredient, which intersects licensed stem ingredients. The resolver detects no cycle, produces all required Payment Demands, creates one transaction with all outputs, directly delivers the Atomic BEEF to each Payee, obtains every Receipt, and receives the movie License.
7. **Federated band settlement.** The downstream-work issuer, drummer Payee, and composer Payee operate three distinct HTTPS origins and independently controlled wallets. The client persists one funded transaction, delivers only to the endpoint signed in each Demand, retains the drummer Receipt while the composer endpoint is unavailable, and later retries the same composer Delivery. Sending either Demand to the other Payee endpoint is rejected, no replacement transaction is created, and issuer completion succeeds only with both Receipts.
8. **Offline authorized Payee.** A drummer selects `authorized-output-v1`, signs exact BRC-29 output and provider details, and then goes offline after renewed signed readiness. The buyer creates one transaction, the exact output is accepted by the authorized processor, the signed Delivery is retained through recovery, and the issuer releases the License from the complete evidence bundle. When the drummer returns, authenticated retrieval internalizes that same output once and returns a late Receipt; no second transaction or License is created.
9. **Unavailable or invalid fallback.** The same flow remains pending when the Delivery provider is unavailable, its retention ends before `recoveryUntil`, the transaction evidence is only `broadcast`, the provider identity or policy differs, the output script or amount differs, or another transaction was already accepted for the Authorization. None of those cases releases keys. A receipt-complete Demand whose Payee goes offline also remains pending because silence does not select the fallback profile.
10. **AI training.** A dataset Offer grants `train` for a stated purpose but not `display`, `unwrap`, or `distribute`. A conforming trainer records `inputTo` provenance where required and rejects an attempted raw export.
11. **Rental.** A License permits `play` until a fixed time and allows 24 hours offline. Playback works offline inside that interval and fails after it. A later status change does not claim to erase a raw CEK previously delivered.
12. **Unknown semantics.** A client that understands the Header but not a critical policy term, settlement profile, evidence policy, or mechanism rejects that acquisition profile and may select another fully supported profile.
13. **Tampering.** Modified Header CBOR, wrong ciphertext digest, wrong CEK, reordered records, modified AAD fields, reused payment demands or Authorizations, missing settlement proofs, C2PA ingredient mismatch, authority cycles, and composition cycles are all rejected.

## 17. Conformance vectors

The companion vector set is [`apps/media/0170-conformance-vectors.json`](https://github.com/bsv-blockchain/BRCs/tree/master/apps/media/0170-conformance-vectors.json). Its C2PA composition pair is [`0170-c2pa-composition.mp3`](https://github.com/bsv-blockchain/BRCs/tree/master/apps/media/0170-c2pa-composition.mp3) plus [`0170-c2pa-composition.c2pa`](https://github.com/bsv-blockchain/BRCs/tree/master/apps/media/0170-c2pa-composition.c2pa). The vector set contains deterministic diagnostic objects and expected:

* deterministic CBOR bytes and object IDs;
* LCH container bytes and detached-header equivalence;
* segmented AES-256-GCM records, Key IDs, ciphertext digest, range offsets, successful partial decryption, and tamper failures;
* Offer and Agreement Policy digests;
* BRC-77 object signatures and BRC-78 CEK recovery;
* normalized selections and unit prices;
* BRC-29-derived multi-payee locking scripts from both sender and receiving-wallet `forSelf` derivation, output indices, Demand IDs, signed Delivery IDs, and Receipt IDs;
* receipt-complete and authorized-output Demand selection, Payee-side exact destination authorization, buyer-side script comparison, signed processor acceptance, durable Delivery acknowledgement, authenticated late retrieval, and mixed-profile completion;
* offline Payee success plus wrong script, wrong amount, wrong provider, broadcast-only evidence, insufficient retention, unavailable provider, repeated proof, conflicting accepted transaction, and strict-profile fallback rejection;
* deterministic-CBOR HTTP request payloads, Payment Completion, recovery, exact media types, success statuses, and stable error envelopes;
* finalized randomized-output matching plus missing, duplicate, and ambiguous output rejection;
* Quote, Demand, and BRC-105 recovery deadlines and idempotent redelivery;
* fresh unspent, stale, mempool-spent, confirmed-spent, unknown, and reorganization-affected Authority revocation states;
* C2PA ingredient-to-Composition-Record bindings;
* whole-placement mapping, distinct repeated ingredients, reversal, rational-rate time warps, distortion metadata, unsupported selective-mapping rejection, nested-obligation flattening, and cycle rejection;
* inclusive `notBefore` plus exclusive `notAfter`, `expiresAt`, and `recoveryUntil` boundaries;
* complete whole-Asset key-period delivery plus missing, duplicate, and out-of-selection Key ID rejection; and
* the pass/fail outcomes for every reference case in section 16.

A conforming implementation MUST reproduce the success vectors it claims to support and MUST reject the corresponding negative vectors. Cryptographic values in the vector set use disclosed private keys and MUST never be reused outside testing.

The C2PA pair is signed with `c2patool`'s disclosed development certificate. C2PA claim-signature, hashed-URI, assertion, and asset-binding validation MUST succeed; `signingCredential.untrusted` is the expected trust result for that test certificate and MUST NOT be treated as a production trust assertion.

## 18. Error handling

Implementations SHOULD expose stable errors at least as specific as:

| Code                          | Meaning                                                                                |
| ----------------------------- | -------------------------------------------------------------------------------------- |
| `ERR_LCH_FRAMING`             | Magic, length, trailing payload, or version invalid                                    |
| `ERR_LCH_CBOR`                | CBOR is not well formed or not deterministic LCH CBOR                                  |
| `ERR_LCH_SIGNATURE`           | Required BRC-77 signature invalid                                                      |
| `ERR_LCH_AUTHORITY`           | Seller, issuer, releaser, publisher, or Payee authority invalid                        |
| `ERR_LCH_REVOCATION`          | Authority revocation status is spent, stale, unknown, or invalidated by reorganization |
| `ERR_LCH_ENDPOINT`            | Endpoint or locator failed URL, address, redirect, origin, or identity validation      |
| `ERR_LCH_PROFILE_UNSUPPORTED` | Acquisition profile or critical mechanism unsupported                                  |
| `ERR_LCH_POLICY`              | ODRL invalid, conflicting, unsupported, or does not grant the action                   |
| `ERR_LCH_TERMS`               | Policy or human terms digest mismatch                                                  |
| `ERR_LCH_CONTENT_UNAVAILABLE` | No required ciphertext source or range is currently available                          |
| `ERR_LCH_CONTENT_DIGEST`      | Ciphertext does not match the Asset Body                                               |
| `ERR_LCH_KEY`                 | Key delivery invalid or CEK does not match Key ID                                      |
| `ERR_LCH_AUTHENTICATION`      | An encrypted record tag failed                                                         |
| `ERR_LCH_SELECTION`           | Selection invalid or outside the Asset                                                 |
| `ERR_LCH_QUOTE`               | Quote expired, mismatched, or arithmetically invalid                                   |
| `ERR_LCH_PAYMENT`             | Payment missing, invalid, replayed, or underfunded                                     |
| `ERR_LCH_DELIVERY`            | Direct Payee delivery, durable storage, or authenticated retrieval failed              |
| `ERR_LCH_LICENSE`             | License invalid, expired, revoked under its mechanism, or mismatched                   |
| `ERR_LCH_PROVENANCE`          | Required C2PA or Composition Record validation failed                                  |
| `ERR_LCH_CYCLE`               | Authority or composition graph contains a cycle                                        |

Errors MUST NOT include CEKs, decrypted content, private keys, BRC-78 plaintexts, or unrelated wallet data.

## 19. Security and privacy considerations

### Untrusted parsers

Headers, ODRL Policies, C2PA manifests, media metadata, decrypted files, and DRM messages are untrusted input. Implementations MUST impose defensible depth, allocation, string, array, header-size, policy-size, and media-decoder limits. The framing length MUST be checked before allocation. URLs MUST be subject to scheme, redirect, local-network, and origin policy to prevent SSRF and credential leakage.

### Nonce and key reuse

AES-GCM confidentiality and authenticity fail catastrophically when a key/IV pair repeats. Publishers MUST generate fresh CEKs and nonce prefixes exactly as section 8 requires. A conforming publisher SHOULD maintain a local duplicate detector for encryption IDs and Key IDs in addition to using a cryptographically secure generator.

### Record substitution

The AAD binds each record to its encryption ID, position, total segment count, plaintext length, and Key ID. Applications MUST supply the exact AAD and MUST NOT decrypt records as standalone AES-GCM messages with empty associated data.

### Plaintext and metadata disclosure

Ciphertext length, media type, filename, Seller, rights holders, prices, Payees, and acquisition requests can reveal sensitive information. A plaintext digest additionally reveals equality with a known file. Publishers can omit that digest, use generic public metadata, pad within a separately specified encryption mechanism, or choose privacy-preserving transports. Those choices can reduce discoverability and preview quality.

Multilateral transactions expose that the outputs were paid together. Direct delivery exposes the buyer and request to every Payee under the authenticated profile. Authorized-output settlement additionally discloses the exact destination script and links the Payee, buyer, evidence provider, Delivery provider, and issuer completion record. Providers MUST restrict unrelated access to stored Deliveries and minimize operational logs while retaining the audit evidence their policy promises. Privacy-enhancing future profiles must state what linkages they remove and must not weaken payment verification silently.

### DRM limits

Any application capable of rendering plaintext can be modified, instrumented, recorded, or subjected to the analog hole unless a stronger protected path changes the threat model. `#conformingApplication` describes interoperable behavior, not impossibility of circumvention. Watermarks can aid attribution but are not access control.

### Seller and issuer failure

Cryptography proves that bytes, policies, signatures, keys, and payments match their commitments. It cannot prove that content is worthwhile, that descriptive claims are true, or that an issuer will behave honestly after receiving payment. Identity, authority, payment receipts, idempotent recovery, and reproducible failure evidence make disputes more attributable; they do not remove counterparty risk.

### Offline use and revocation

Once a buyer possesses a raw CEK or plaintext, revocation cannot make it unknown. A policy promising both permanent offline access and retroactive technical revocation is contradictory and invalid. Protected-module and online profiles can provide different behavior only within their declared boundaries.

### Composition disclosure and graph attacks

C2PA and LCH Composition Records can reveal creative inputs, license relationships, and commercial splits. Implementations must obtain the permissions required to publish that information. Recursive resolvers MUST detect cycles, cap resource use, verify every source independently, and reject missing rather than silently ignored obligations.

### Payment recovery

An ambiguous network failure after transaction creation is not evidence that payment failed. Clients retain the existing Atomic BEEF and retry direct delivery and license recovery. They do not automatically create a second transaction.

Authorized-output fallback moves rather than removes availability and finality risk. The Payee explicitly trusts named providers before payment; an unavailable provider leaves settlement pending, and processor acceptance can later be displaced by a conflict or reorganization. Implementations MUST preserve the evidenced lifecycle label, MUST NOT describe accepted evidence as mined, and SHOULD expose these provider dependencies before the Payee enables the profile.

## 20. Legal and evidentiary considerations

LCH provides canonical bytes, named parties, signed Offers, accepted term digests, buyer-specific Agreements, authority chains, payment transactions, direct Payee receipts, timestamps, and provenance. Those records can improve reproducibility and evidence in a dispute.

LCH standardizes protocol records and conformance behavior; it does not define copyright ownership, contract formation, consumer rights, exhaustion, fair use or fair dealing, privacy obligations, choice of law, tax, securities treatment, or the applicability of anti-circumvention rules. A cryptographic claim of authority can be false in the real world. Human-readable terms and applicable rules remain controlling where machine policy is incomplete or ineffective.

Applications MUST NOT describe an LCH signature as a government-qualified signature, a C2PA claim as proof of copyright ownership, or a DRM mechanism as universal legal enforcement unless an independent legal basis supports that statement.

## 21. Implementation guidance

A practical implementation can be layered:

1. deterministic CBOR and `.lch` framing;
2. Asset ID and BRC-77 verification;
3. ODRL validation with the pinned LCH Profile;
4. segmented AES-256-GCM range reads;
5. BRC-105 purchase and BRC-78 key delivery;
6. portable License storage and recovery;
7. metered selections and key periods;
8. Authority Grants;
9. multilateral direct settlement;
10. Payee-authorized output settlement with durable retrieval; and
11. C2PA composition and nested downstream policies.

An end-to-end reference deployment has these visible role classes: a creator wizard that produces the protected Asset and Offer; a content host implementing the `ContentSink`/`ContentSource` boundary; an issuer endpoint implementing section 11.6; one independently routable delivery service and receiving BRC-100 wallet for every Payee; optional Payee-authorized transaction-evidence and durable-Delivery providers; and a player that separates non-spending preflight from the buyer wallet's `createAction`. The roles need not share an origin, process, operator, or wallet substrate. Under receipt-complete settlement, Payee wallets directly receive and acknowledge Demand outputs through `internalizeAction`. Under authorized-output settlement, the Payee signs its exact destination and provider choices before payment, and later retrieves the signed Delivery for the same wallet operation. The issuer persists immutable Quotes, Demands, settlement proofs, issued Licenses, and protected CEKs needed for recovery; the buyer persists its funded fan-out state until completion or `recoveryUntil`.

A single-process reference server MAY combine those roles with in-memory stores while still exposing distinct endpoints and signed identities. A horizontally scaled or value-bearing service needs a transactional shared Payment Demand and Authorization ledger, durable Delivery retention through its signed deadline, atomic transaction-evidence claims, durable License recovery, encrypted CEK storage, independently controlled wallet identities, authenticated creator publication, bounded public endpoints, and CHIRP, UHRP, or HTTPS ciphertext retention. Those operational substitutions do not change the signed LCH objects.

The minimum full core Reader implements fixed-render-v1 with deterministic CBOR, BRC-77, ODRL, BRC-105, BRC-78, and segmented AES-256-GCM. Smaller applications may truthfully claim a narrower profile and mechanism set.

## References

* W3C, [ODRL Information Model 2.2](https://www.w3.org/TR/odrl-model/)
* W3C, [ODRL Vocabulary & Expression 2.2](https://www.w3.org/TR/odrl-vocab/)
* W3C, [Encrypted Media Extensions](https://www.w3.org/TR/encrypted-media-2/)
* C2PA, [C2PA Technical Specification 2.4](https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html)
* Readium Foundation, [Licensed Content Protection 1.0](https://readium.org/lcp-specs/releases/lcp/latest)
* NIST, [SP 800-38D: GCM and GMAC](https://csrc.nist.gov/pubs/sp/800/38/d/final)
* IETF, [RFC 8188: Encrypted Content-Encoding for HTTP](https://www.rfc-editor.org/rfc/rfc8188.html)
* IETF, [RFC 8949: Concise Binary Object Representation](https://www.rfc-editor.org/rfc/rfc8949.html)
* W3C, [Media Fragments URI 1.0](https://www.w3.org/TR/media-frags/)
* W3C, [WOFF File Format 1.0](https://www.w3.org/TR/WOFF/)
* Microsoft, [OpenType `name` table](https://learn.microsoft.com/en-us/typography/opentype/spec/name) and [`OS/2` embedding permissions](https://learn.microsoft.com/en-us/typography/opentype/spec/os2)


# 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` `timelock` `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 candidate 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 version 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 candidate 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 candidate 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.6.

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


# Application Output Knowledge and Source Adapters

Ty Everett (<ty@projectbabbage.com>)

## Abstract and status

This specification defines a replaceable application runtime that learns about Bitcoin outputs from overlays, wallet baskets and direct delivery. It separates received information, verified transaction history, assessments of current state, application interpretation and authorized actions. A music catalogue can display results from its first responding host and incorporate later changes without treating that host as the authority over every artist. A task application can use the same machinery with a wallet basket instead.

This is a behavioral contract, not a claim that existing packages implement it. It is additive to [BRC-100](/wallet/0100), [BRC-22](/overlays/0022) and [BRC-24](/overlays/0024). It neither moves wallet custody into the runtime nor establishes a new consensus protocol. The capitalized requirement words in this packet have the meanings in RFC 2119 and RFC 8174.

## 1. Shared representation rules

These rules also apply to BRCs 193–199 wherever those documents use JSON. Existing Bitcoin, BEEF, BRC-77 and LCH encodings retain their own rules.

`Hex32` is exactly 64 lowercase hexadecimal characters. `Identity` is a valid compressed secp256k1 public key encoded as 66 lowercase hexadecimal characters. `Bytes` is canonical RFC 4648 standard base64, with required padding and no whitespace. `U64` is a decimal string matching `0|[1-9][0-9]*`, at most 18446744073709551615. Arithmetic MUST be exact and checked. `U32` is a JSON integer from 0 through 4294967295. Times are `U64` whole Unix seconds. An expiry is exclusive: a record is expired when `now >= expiresAt`.

JSON MUST be UTF-8 without duplicate property names, non-finite numbers or unpaired surrogates. Numbers in new protocol objects MUST be safe integers; applications needing decimals encode them as strings in their own schema. Protocol objects are recursively closed: every object, including a nested union member, admits only the fields defined for that object. This does not close application-defined `query`, policy payloads or extension values; those obey their selected schemas. Optional extensions use an `extensions` object keyed by absolute IRIs; `critical` is a duplicate-free array of keys present in that object. Unknown non-critical extensions are retained when relaying a signed object but not interpreted. Unknown critical extensions fail with `unsupported`. Missing and `null` are distinct; `null` is permitted only where specified.

For packet type `T` and body `B`, define:

```
preimage(T,B) = UTF8("BRC-OUTPUT/1/" + T) || 0x00 || UTF8(JCS(B))
digest(T,B)   = lowercaseHex(SHA256(preimage(T,B)))
```

`JCS` is [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785). The digest excludes an enclosing signature, never fields inside the body. A signed packet object is `{body, signature}`, with `signature: Bytes` containing a complete [BRC-77](/peer-to-peer/0077) signature over `preimage(T,B)` using the `anyone` verifier. Verify both cryptography and the specified signer role. Identity authentication alone does not confer application authority. No document in this packet permits resolving arbitrary extension IRIs as executable code or trusted schemas.

```typescript
type Chain = { network: string; genesisHash: Hex32 }
type Outpoint = { chain: Chain; txid: Hex32; outputIndex: U32 }
type Partition = { application: string; account: string; access: string }
type Scope = {
  chain: Chain; provider: string; service: string;
  queryDigest: Hex32; rulesDigest: Hex32; access: string; epoch: string
}
type Evidence = { txid: Hex32; outputIndex: U32; beef: Bytes }
type Extensions = { extensions?: Record<string, unknown>; critical?: string[] }
```

Strings without a narrower constraint MUST be nonempty UTF-8 strings of at most 1024 encoded bytes. `network` is an application-configured namespace, not a sender-selected switch; genesis hash further separates otherwise similarly named chains. Outpoint equality compares all three components, including both chain fields. Epochs are opaque provider identifiers, not timestamps. Access identifiers are opaque local/provider partition identifiers, never credentials. The local partition is supplied by trusted application configuration and MUST NOT be accepted from a remote payload.

## 2. Observations and identities

A source delivers `Observation = {id, scope, kind, payload, extensions?, critical?}`. `id` is unique within its scope epoch and stable on replay. The runtime adds local receipt time, partition, adapter identity and authenticated peer evidence. A payload cannot set those trusted fields.

The supported `kind` and exact `payload` pairs are:

| Kind                     | Payload                                                                                                                         |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `output`                 | `{evidence: Evidence, context?: {schema: string, bytes: Bytes}}`                                                                |
| `spend`                  | `{previous: Outpoint, spendingTxid: Hex32, beef: Bytes}`                                                                        |
| `withdraw`               | `{outpoint: Outpoint, reason: string}`                                                                                          |
| `assessment-invalidated` | `{contextId: string, reason: string}`                                                                                           |
| `proposal`               | `{proposal: SignedProposal}` as defined in BRC-194                                                                              |
| `proposal-state`         | `{service:string, policy:{id:string,digest:Hex32}, channel:Hex32, proposalId:Hex32, state:ProposalState}` as defined in BRC-194 |
| `proposal-remove`        | `{service:string, policy:{id:string,digest:Hex32}, channel:Hex32, proposalId:Hex32, reason:string}`                             |

`context` is provider data, subject to byte limits and private partition rules; it is not trusted executable behavior. Its schema is an absolute IRI implemented by the application. The standard adapter schema `https://bsv.brc.dev/apps/0192#legacy-lookup-context-json-v1` carries UTF-8 JSON bytes representing an existing BRC-24 context value, retaining its original JSON semantics including valid fractional numbers. The enclosing protocol does not reinterpret or canonicalize those opaque bytes. An application that does not support the schema can retain/ignore non-critical context, but cannot claim to have validated its domain meaning. A source's `output` means that source reports the output in its collection. `withdraw` removes only that source's membership assertion. Neither implies a Bitcoin spend. The same output from two hosts has two receipts and one output identity. An application object, such as a song or editable post, has a third, domain-defined identity that can span many outputs.

Deduplication keys are `(partition, scope, id)`. An identical replay is a no-op. Reusing that key with different normalized observation bytes is `equivocation`; preserve the conflicting claim in bounded quarantine, invalidate continuity for that source and require recovery. Do not let a forged remote receipt overwrite trusted provenance. Evidence variants are separately keyed by digest of their exact BEEF bytes. One rejected variant MUST NOT poison the outpoint or prevent a later valid variant from being verified.

`Evidence.txid` selects the asserted target, not an arbitrary transaction from the bundle. The assembler MUST locate that transaction, derive its txid from its raw bytes, verify the output index, and provide a target-specific BEEF candidate to the verifier. An Atomic BEEF target MUST equal the assertion. It MUST NOT silently use the last transaction in an aggregate wallet bundle. Txid-only references are unresolved dependencies until the required bytes are present. Retrieval follows configured, bounded adapters, not arbitrary URLs supplied by a receipt.

## 3. Evidence, currentness and domain meaning

The verifier accepts an evidence candidate and an immutable `VerificationContext` containing the caller's chain view identifier, verification policy digest and cancellation signal. Its result is one of `verified`, `invalid`, `unresolved`, `limited`, `cancelled` or `context-changed`. Only `verified` introduces transaction-derived facts. A verified creation transaction does not establish that its output is unspent or relevant to an application.

Currentness is a separate assessment with the complete `Currentness` structure in section 8. `state` is `unknown`, `reported-unspent`, `spent`, `conflicted` or `stale`. `spent` requires a verified spending transaction and the application's selected acceptance policy; `reported-unspent` MUST retain its provider and scope. Two conflicting unconfirmed spends remain representable. Confirmation placement records name block hash and height, and can be invalidated by a changed chain view. No fixed confirmation count is imposed on all applications.

An assessment is usable only in its partition, policy and chain view. Late results from an older view MUST NOT overwrite assessments for the current view. Changing view invalidates affected assessments before publishing projections based on the new view. Immutable raw transaction facts can be reused; chain placement and currentness cannot be reused without reassessment. Wall-clock freshness is not blockchain finality.

A domain projector interprets scripts, controllers, lineage and application rules. It consumes a coherent accepted revision and produces records plus explicit conflicts or unresolved dependencies. It MUST be deterministic for the same accepted inputs, domain policy and context. Domain rejection is not evidence that the Bitcoin transaction is invalid. Successor selection, membership epochs, editable post permissions and vote uniqueness belong here or in an explicitly named domain protocol.

## 4. Interchangeable ports

The following are behavioral ports. Language bindings MAY name methods differently but MUST preserve their behavior and expose the same outcomes.

`Source.open(request, signal)` returns a bounded async iterator of `SourceBatch` objects. Each batch has the `SourceBatch` structure in section 8, retaining indivisible groups from ingress through accepted projection. Coverage records the source scope, phase (`snapshot`, `live`, `finite`), status (`partial`, `complete`, `reset-required`, `unavailable`, `limited`) and any named checkpoint. A complete finite response describes that source/query invocation only. It does not establish network-wide completeness or chain finality. A durable source checkpoint additionally follows BRC-193.

`KnowledgeStore.commit(expectedRevision, mutation)` atomically persists receipts, evidence references, membership changes, invalidations, durable pending verification work and source checkpoint advancement. Revisions are strictly increasing local `U64` values. A mismatched revision is `conflict` with no partial write. A successful commit returns its revision. An ambiguous storage result is resolved by reading the mutation's stable idempotency key, never by assuming failure. `read(revision)` provides a coherent snapshot or `revision-unavailable`; `watch(afterRevision)` provides ordered coherent changes or an explicit reset. A memory adapter declares `durability: volatile`; it MUST NOT advertise restart recovery.

`Projector.apply(revision, changes, context)` commits its projection and checkpoint together, or uses replayable idempotent work. A failed projection cannot be marked current. Consumers see both knowledge and projection revision, so a lagging projection cannot masquerade as the latest result. Rebuild uses retained evidence/domain history or explicitly requests a fresh source snapshot. Retention MUST preserve unresolved work, current source fences and obligations such as paid acquisition recovery; if history needed for a guarantee is unavailable, return a reset or unsupported result.

`ActionPort.request(intent)` is invoked only by an authorized application workflow. Observing, verifying, projecting or replaying MUST NOT automatically sign, internalize, pay, broadcast or change wallet permissions. Automatic domain actions need separately granted authority and their own durable idempotency records. The runtime does not create a second wallet.

## 5. Adapter requirements

The overlay adapter retains per-host receipts before union/deduplication. Finite BRC-24 and progressive multi-host results may be adapted, but MUST declare their weaker continuity. BRC-193 is required for a replayable live claim. Callback APIs without awaited backpressure need a bounded ingress queue: overflow reports `limited` and triggers reconciliation; it never silently drops data while advancing a checkpoint.

The wallet adapter uses BRC-100 `listOutputs`, existing baskets/tags, permissions and aggregate BEEF. Offset pagination is not an atomic snapshot. Concurrent mutations require repeated reconciliation or a separately supported wallet change capability. An absent row alone is not a spend. Switching accounts closes sources, cancels work and partitions cached/private data before exposing the new account.

The direct-delivery adapter supports transports such as MessageBox, NFC and peer exchange. Receipt identity, sender authentication and delivery acknowledgement are distinct from evidence validity. Delivery acknowledgement is issued only after durable receipt or an explicitly declared volatile policy. It supplies no claim to an exhaustive collection.

A private-state adapter decrypts only within an authorized partition after validating the output/controller binding. Client-encrypted participant state and a private overlay holding a seller's key are distinct trust models. Membership epochs, late-write reconciliation and ephemeral typing/presence do not become Bitcoin consensus by entering this runtime.

## 6. Limits, errors and compatibility

Every adapter declares maximum batch bytes, observations, pending bytes, verification concurrency, dependency work, retained history and deadline. Configuration MUST be finite and validated before opening a source. Ingress bytes are charged before parsing nested content. Verification failures are isolated per candidate unless they invalidate a batch's integrity. Cancellation releases resources and prevents further publication from the cancelled context; already committed durable facts remain available.

Common errors are `invalid`, `unsupported`, `unauthorized`, `conflict`, `equivocation`, `limited`, `unavailable`, `expired`, `reset-required`, `cancelled` and `context-changed`. Retryability is explicit; budget exhaustion is never reported as invalid cryptographic evidence. Scheduling across sources MUST prevent one slow or busy host from indefinitely starving others.

Existing SDK exports, BEEF formats, lookup defaults, wallet calls and domain token formats remain unchanged. New runtime interfaces SHOULD live above the SDK with optional storage/UI/transport adapters. Replacing a database or UI framework MUST NOT change receipt identity, replay, authorization or projection semantics. GASP can synchronize either strict or federated providers, as described by [BRC-183](/overlays/0183); it is not a substitute for the application contract.

## 7. Conformance

A conforming runtime MUST exercise two independent source adapters and a restart-capable store. Required cases are duplicate receipts; two hosts reporting one output; bad evidence followed by valid evidence; alternate BEEF proofs; aggregate target selection; unrelated withdrawal; verified spend and successor in one coherent revision; conflicting spends; context changes during verification; projection failure/rebuild; queue overflow; cancellation; restart; account switching; missing dependencies; and cursor advancement only after durable work exists. A volatile implementation may claim the corresponding narrower port conformance, never durable conformance.

The packet's [qualification guide and fixtures](https://github.com/bsv-blockchain/BRCs/tree/master/overlays/media/0192-0199/README.md) distinguish representation tests, behavioral traces and implementation qualification. They are specification checks, not certification of a database, cryptographic verifier or deployed application.

## 8. Structural port contract

The following structures complete sections 2–4. `LocalId` is a nonempty string under section 1's bound. `ChainView` is caller-selected, never created by an observation. Signals are local cancellation handles, not serializable credentials. Every field below is required except those marked optional. All lists are bounded by section 9 and the smaller negotiated budgets.

```typescript
type ChainView = {
  id:LocalId; chain:Chain; tipHash:Hex32; tipHeight:U64;
  medianTimePast:U64; chainPolicyDigest:Hex32
}
type VerificationContext = {
  id: LocalId; partition: Partition; generation: U64; view: ChainView;
  policyDigest: Hex32; now: U64;
  limits: { bytes: U32; transactions: U32; dependencies: U32; deadline: U64 }
}
type Provenance = {
  partition: Partition; generation: U64; adapter: LocalId;
  scope: Scope; authentication: 'brc103' | 'configured-transport';
  peer: string; receivedAt: U64
}
type EvidenceCandidate = { chain: Chain; evidence: Evidence; variantId: Hex32 }
type TransactionFact = {
  chain: Chain; txid: Hex32; rawTransaction: Bytes; factId: Hex32
}
type VerificationResult =
  | { status:'verified'; contextId:LocalId; variantId:Hex32;
      fact:TransactionFact; placement?:{blockHash:Hex32,height:U64} }
  | { status:'invalid'|'unresolved'|'limited'|'cancelled'|'context-changed';
      contextId:LocalId; variantId:Hex32; reason:string; dependencies:Outpoint[] }
type Currentness = {
  id: Hex32; outpoint: Outpoint;
  state:'unknown'|'reported-unspent'|'spent'|'conflicted'|'stale';
  contextId: LocalId; generation: U64; policyDigest: Hex32;
  origin: { kind:'local' } | { kind:'source'; scope:Scope };
  evidenceIds: Hex32[]; expiresAt?: U64
}
type SourceGroup = { id:string; sequence:U64; observations:Observation[] }
type Coverage = {
  scope:Scope; phase:'snapshot'|'live'|'finite';
  status:'partial'|'complete'|'reset-required'|'unavailable'|'limited';
  through?:U64; highWater?:U64
}
type SourceBatch = {
  provenance:Provenance; groups:SourceGroup[]; coverage:Coverage;
  checkpoint?: { session:string; cursor:string; expiresAt:U64; replayUntil:U64 }
}
type Mutation = {
  key: Hex32; body:
    | { kind:'receive'; batch:SourceBatch }
    | { kind:'accept'; scope:Scope; groupId:string; generation:U64;
        contextId:LocalId; results:VerificationResult[]; assessments:Currentness[];
        reconciled:ReconciledState }
    | { kind:'reconcile'; generation:U64; contextId:LocalId;
        reconciled:ReconciledState; assessments:Currentness[] }
    | { kind:'invalidate'; generation:U64; assessmentIds:Hex32[]; reason:string }
    | { kind:'context'; context:VerificationContext }
}
type StoreRevision = { received:U64; accepted:U64 }
type CommitResult =
  | { status:'committed'|'replayed'; revision:StoreRevision }
  | { status:'conflict'|'equivocation'|'limited'|'context-changed'; reason:string }
type AcceptedInput = {
  partition:Partition; generation:U64; revision:StoreRevision;
  context:VerificationContext; observations:Observation[];
  facts:TransactionFact[]; assessments:Currentness[]; reconciled:ReconciledState;
  pendingGroups:{scope:Scope,groupId:string,reason:string}[]
}
type Projection = {
  acceptedRevision:U64; generation:U64; contextId:LocalId;
  records:{id:string,schema:string,value:Bytes}[];
  conflicts:{id:string,reason:string,observationIds:string[]}[];
  unresolved:{id:string,reason:string,dependencies:Outpoint[]}[]
}
```

A variant ID is `SHA256(decoded BEEF bytes)`; a fact ID is `digest("transaction-fact", {chain,txid})`; an assessment ID is `digest("assessment", bodyWithoutId)`. These are different namespaces. Evidence IDs in an assessment are variant IDs with retained verification results in that assessment's context. Raw transaction bytes having the same txid but different content are an integrity failure. A source-reported unspent assessment requires `origin.kind=source`, its exact scope and an expiry. `spent` requires evidence actually consuming the named outpoint. Absence and an unspent assertion do not establish a spend. Context-changing assessments use new IDs; they do not mutate the historical result.

The trusted adapter checks the **entire** scope of every observation against its established source session, including configured chain, provider, service, query/rules digests, access and epoch. It compares coverage and batch scope as well. A mismatch rejects the whole batch before receipt or cursor commit. Remote `assessment-invalidated` only invalidates source-origin assessments whose complete scope and contextId match that same authenticated source. It cannot name a local ChainView or invalidate another source's assessment. A locally authorized context change computes affected assessment IDs from that view/policy and emits an atomic invalidation; it may affect many providers. `proposal-state` and `proposal-remove` are attributable **provider assertions**, not author signatures, and never fabricate a `SignedProposal`.

A finite source maps its entire response to one group unless its declared rules provide independent boundaries. Its `sequence` is zero; snapshot sequence rules come from BRC-193. `receive` commits all group boundaries, bytes, immutable provenance, verification jobs and the checkpoint atomically. It increments `received` but not `accepted`. An accept mutation carries exactly one complete group and a result for every evidence candidate; no member may publish independently. Unresolved/limited/cancelled/context-changed members keep the group pending and make affected old projection records visibly stale. An invalid member quarantines the whole group, invalidates that source's continuity, and requires reconciliation; it cannot publish its valid siblings. This does not discard evidence variants, independent sources or historical facts. A later valid proof uses a new observation ID and a replacement group, or a new reconciled generation; it never rewrites an equivocated observation.

Verification jobs and accept mutations are guarded by partition, generation and context. On dependency completion after restart they recheck those guards and all expiring assessments, then accept the group atomically. `accepted` increments only for a whole accepted group or a local reconciliation/invalidation/context/expiry event. A reconcile mutation recomputes selection over retained facts and accepted groups; it cannot introduce unverified facts or bypass a source-group barrier. Receipt, acceptance and projection revisions are separate progress indicators. A projection failure leaves its checkpoint unchanged and reports lag/staleness. All providers' accepted memberships remain distinct until domain interpretation. An expiry scheduler MUST publish local invalidation at the expiry boundary even with no incoming data; reads also evaluate the clock so a delayed timer cannot expose expired assessments as current.

A mutation key is `digest("knowledge-mutation", body)`, with trusted local provenance frozen at first receipt and reused on retry. `commit` compares the expected **received** revision. Every successful new mutation increments received; accepted increments only as above, with checked U64 arithmetic. Replay lookup precedes revision comparison: the same key/body returns the saved revisions; different bytes for an existing key are equivocation. A lost commit result is resolved with `getMutation(key)` returning `committed` with saved result, `absent`, or `unavailable`; only absent permits repeating the operation. No caller guesses rollback. Overflow is `limited` before any mutation and requires a new explicit store generation/reset, not wraparound.

`read(acceptedRevision)` returns AcceptedInput or revision-unavailable. Projector inputs are sorted by UTF-8 bytes of JCS(scope), then UTF-8 observation ID, with facts by chain/txid and assessments by ID. Identical facts collapse by fact ID; source claims never collapse across scopes. Conflicting claims are passed together, never resolved by arrival order. Verified Bitcoin spend selection follows section 10; domain policy interprets application conflicts on that reconciled history and cannot override its actual input-consumption edges. Presentation sorting is not an event order. `apply` atomically records Projection plus its revision/generation/context or idempotent replay work keyed by those fields and the domain policy digest. A publication gate checks active partition/generation/context and cancellation immediately before committing the projection visible to readers. Cancellation linearizes at that gate: previously published facts remain, queued/late publications fail cancelled/context-changed. Account switching closes that gate before exposing the new partition.

`watch` retains contiguous accepted changes or returns reset-required with the earliest available revision. A reset never silently substitutes an unrelated latest revision. Receipt keys, group mappings, mutation results and source generations MUST be retained for the entire resumable source lifetime; unresolved groups remain until resolution or an explicit reset terminating their guarantee. Retired generations retain a permanent small closed-generation fence so late jobs cannot revive them. Paid obligations are retained independently through their specified completion/recovery terms. Explicit generation reset permits compacting historical receipt bodies once no source, unresolved work, projection rebuild or paid obligation depends on them; it cannot delete the fence.

## 9. Exact representation boundary

Protocol input parsing rejects duplicate **decoded** keys (including escaped aliases), malformed UTF-8, a BOM, non-finite numbers and lone surrogates before JCS. Numeric spelling is not an additional canonical-input rule: `-0`, `1.0` and `1e0` parse as safe integers and canonicalize to `0`, `1`, `1`. An unsafe integer or fractional numeric value in a new protocol object is invalid. Signed preimages use RFC 8785 UTF-16 key ordering and ECMAScript number/string serialization; a language's generic sorted JSON encoder is not a substitute. U64 fields remain strings and reject leading zeros, signs, exponents and overflow. Opaque decoded bytes may be empty unless their installed schema or a named cryptographic format requires nonempty bytes. Base64 decoders MUST reject nonzero pad bits and noncanonical padding by round-trip re-encoding. Identity validation includes point-on-curve checking, not only length/prefix.

Default hard limits across this packet are nesting depth 32 (root counts as one), 4096 array elements, 256 keys per map, 32 extension/critical entries, and 4 MiB decoded message bytes, unless a specific field has a smaller limit. String bounds in section 1 apply except opaque base64, URLs with an expressly stated bound, and application data within bounded query/extension containers. Those containers permit strings up to the complete message bound; they do not relax nesting, map, array or numeric limits. Cryptographic signatures, raw transactions and BEEF must parse completely; input limits include decoded allocations and verification work. Unknown nested protocol keys fail invalid. Duplicate critical keys fail invalid; unsupported critical mechanisms fail unsupported. Extension values are retained under the same canonicalization/bounds and never dereferenced automatically.

Normalized equality is equality of RFC 8785 bytes of the complete object, including extensions. Different BEEF bytes under one observation ID therefore constitute equivocation even if both proofs are valid. Different observations can introduce those variants for one fact. Only the specifically documented publication/finalization evidence-variant rules exclude proof bytes from semantic request equality. These exclusions never apply to opaque LCH signed-request bytes.

## 10. Deterministic spend reconciliation

This section registers `https://bsv.brc.dev/apps/0192#bitcoin-spend-reconciliation-v1`. It is the required spend-selection contract for this BRC's stateful runtime; a representation-only implementation need not implement it. Parameters are `{nonFinal:boolean}` in the locally selected verification policy. False retains non-final evidence without selecting provisional replacements. True additionally tracks fully signed, structurally and Script-valid non-final candidates as described below. Neither setting authorizes signing, broadcasting, topical admission or private release. A domain with stricter transaction rules remains stricter, but BRC-197 imposes no additional zero-locktime/all-final-sequence acceptance rule. Its ordinary construction recommendation does not exclude Script-valid alternative locktime/sequence combinations from lineage. Permissionless expiry retirement retains its Script-enforced height locktime and nonfinal listing-input sequence. For every BRC-197 route, a currently non-final candidate is retained as pending evidence; `{nonFinal:true}` may select it only as nonfinal intent, without consuming the listing in the current projection. Once Bitcoin-final, it is eligible under either setting, subject to dependencies, conflict selection and the remaining Script/domain checks. A verified included transition is assessed in its containing block's applicable finality context, not rejected because it was non-final when first received.

### 10.1 Transaction history and application state

Within one authorized partition and configured chain, any source can contribute transaction evidence. Providers need not agree about catalogue membership. Validate exact target bytes, every consumed input, dependencies, value conservation and the applicable Script/covenant rules before adding a usable spend edge. Derive edges from actual transaction inputs, never from a claimed successor, a matching object ID or observation order. An input references a parent transaction even when the child's observation arrives first. Missing dependencies remain unresolved; a later verified parent completes the graph without making a consumed parent output current again.

Transaction validity, chain inclusion, selected spend history, domain acceptance and source membership are separate fields. The application validates controller/lineage/other domain rules before treating a successor as a valid application object. A domain-invalid successor does not undo a Bitcoin spend: a valid transaction can consume an application's output and create no valid replacement in that domain. For example, spending a copied BRC-197 active Script whose ancestry lacks the signed genesis and verified activation can pass that local Script yet fail the listing domain. If such a spend is selected and final, its input remains consumed; Script validity alone neither establishes lineage nor chooses it over a conflicting transaction. Do not resurrect an old output merely because its successor is ineligible for catalogue admission.

The graph contains all verified alternatives. Selection produces a compatible subset; losing alternatives, their sources and evidence remain visible as conflicts. A selected unconfirmed **final** spend provisionally consumes each of its inputs together. Its finality does not establish mining or global acceptance. A selected non-final candidate only records pending replacement intent; it does not establish a spent input or a spendable successor. A selected final transaction cannot depend on an unselected or non-final unconfirmed parent. A creation proof, including an ancestor's verified inclusion, never defeats a verified eligible forward spend or establishes global unspentness.

### 10.2 Whose first-seen and which order

First-seen in this profile means **this store's first complete selectable observation**, not a provider's timestamp, a network-wide arrival time, or the worker that finished first. Before starting verification, the store atomically assigns each authorized receive a strictly increasing ingress position within a partition's durable journal. It records the active chain/finality context there. Context changes also occupy durable journal positions; elapsed wall-clock time and worker completions do not advance chain finality. Independently accepted source batches have separate positions; within one atomic batch, newly encountered raw transactions are indexed by ascending txid. No network payload assigns these values. A txid alone does not establish a transaction observation. A duplicate or alternative proof does not reset its first raw-transaction position.

The eligibility support for a candidate is its exact raw transaction, required raw predecessors or verified chain anchors, cryptographic evidence and any protocol prerequisites. A domain's object acceptance is evaluated separately and cannot conceal a spend. `readyAt` is the earliest ingress position at which one sufficient, valid support set had been received. This is cryptographic/protocol readiness; actual selection additionally requires usable parents and the enabled finality policy. It is the minimum, over qualifying support sets, of the greatest receipt position in that set. Include the candidate, all unconfirmed ancestors and the integrity prerequisites of their source groups. Evaluate support against the historical context and evidence available at that position, not today's anchors. Later inclusion does not retroactively shorten an earlier dependency path or change its depth. A chain anchor must have been verified for the historical context it supports; a proof on another branch is not that anchor. Verification completion time is never a member of that set. An invalid proof is not support; another valid variant may establish readiness. Evidence obtained later cannot be backdated to an earlier txid-only reference or incomplete promise.

At a shared readyAt, process ancestors before descendants. `depth` is zero if all unconfirmed parents have smaller readyAt (or are verified chain anchors); otherwise it is one plus the maximum depth of parents with this same readyAt. The total candidate order is `(readyAt, depth, firstRaw.position, firstRaw.index, txid)`, with numeric comparison of integers and lexicographic display-hex comparison of txid. These choices settle ties inside a receive transaction without pretending to recover a network arrival order. `readyContextId` names the immutable finality context active at readyAt, retained for replay.

An earlier complete support set whose verification is unfinished creates a reconciliation barrier for its connected input-conflict/dependency component. A later worker cannot publish its competing choice while that earlier set might be eligible. Retain the previous accepted component as stale, or no selection if there was none, until the needed results are known. Other components can advance. An earlier **incomplete** candidate has no eligible order yet; it is reported unresolved and does not reserve priority ahead of an already complete candidate. For example, A's raw bytes arriving first does not beat B when A requires an additional predecessor first supplied after B's complete evidence. If both complete packages were present before either worker finished, reversing completion or restarting cannot change their order.

The store must determine the earliest qualifying support, rather than accepting the first successful proof job. Until earlier potentially sufficient variants are resolved, that determination remains pending. Bounded parse/dependency planning and resource policy may report limited or unsupported; those are explicit retained outcomes, not Script invalidity or a fabricated network rejection. Removing a pending candidate from a bounded local selection requires a recorded policy decision and a stale/incomplete coverage indicator. New evidence can reopen it with the original raw receipt and the newly established readiness. Garbage collection must not silently change a retained first-seen decision.

### 10.3 Exact finality and non-final replacement

The replacement relation follows the [Genesis specification's nLockTime/nSequence section at revision 67d7de07c75d03598fb059d59c61c29ae3dbf87b](https://github.com/bitcoin-sv-specs/protocol/blob/67d7de07c75d03598fb059d59c61c29ae3dbf87b/updates/genesis-spec.md#nlocktime--nsequence). For the exact finality boundary this profile uses the pinned BSV node [IsFinalTx and contextual checks at 879fc8b42168dd0e608dafd51b39c6dabad37d4d](https://github.com/bitcoin-sv/bitcoin-sv/blob/879fc8b42168dd0e608dafd51b39c6dabad37d4d/src/validation.cpp#L228-L251). The Genesis prose says greater-or-equal at the height/time boundary; the implementation uses strict inequality. This profile explicitly preserves the implementation's boundary rather than introducing a different consensus cutoff. The dependency registry records both sources and this distinction.

For an unconfirmed candidate, let H be selected tipHeight+1 and M be the median timestamp of the selected tip and its ten predecessors (or all available predecessors when fewer than eleven exist). Both come from the configured chain verifier, not a provider clock or VerificationContext.now. A transaction is final exactly when locktime=0, all input sequences equal 0xffffffff, or locktime is strictly below H when locktime<500000000 and strictly below M otherwise. Equality is non-final unless the zero/all-final exception applies. Included transactions are evaluated against their containing block's height and preceding-chain MTP under that chain's applicable rules. This profile covers the post-Genesis rules; other rule eras require an explicitly selected compatible verifier, not an assumed sequence interpretation.

A later candidate replaces a selected non-final candidate only if both are otherwise eligible, their complete ordered input-outpoint vectors are identical, every corresponding new sequence is at least the old sequence, and at least one is strictly greater. Compare unsigned 32-bit values component by component. Changed input count, ordering or any outpoint fails. Equal vectors, a decreasing component or incomparable vectors do not replace; a greater sum or maximum sequence is irrelevant. A locktime-only change without a sequence increase does not replace. Outputs may change only as permitted by the actual signatures, Script and selected protocol constraints.

The prior candidate must still be non-final at the **new candidate's first selectable journal frontier**, before considering that replacement. This is readyContext when its parents are already selected final; otherwise it is the recorded later context that makes those parents usable. The replacement can itself be final, either through all-final sequences or a satisfied locktime. Recheck all finality and transaction rules; increasing one sequence to 0xffffffff does not make all other inputs final. Once the selected transaction is final in the context of a later candidate, a higher sequence cannot replace it through this rule. Conflicting different input sets retain the earlier eligible selection, including when it is non-final. A non-final candidate cannot replace an earlier final spend. These are observed provisional choices, not proof that a miner has made the same choice.

Retain the context and reason of each replacement. Time advancing does not resurrect an older version that was permissibly replaced while non-final. Replaying uses the recorded readiness and context-change frontiers for historical replacement decisions and the current view for today's finality/currentness. Reorganization can remove chain overrides and require new eligibility assessments; it does not assign fresh arrival positions. If the historic support/context needed for an existing guarantee was discarded, return reset-required rather than deriving a new order from today's worker schedule.

### 10.4 Selection and accepted-chain overrides

For a complete reconciliation component, first validate the selected chain's included transactions and their necessary ancestor closure. They take precedence over incompatible unconfirmed choices regardless of first-seen. A Merkle path must resolve to the locally selected verified header chain; an arbitrary provider assertion or proof on another branch is not an override. Two contradictory spend histories claimed within one selected chain view are a verifier/context-integrity failure; stop the component with context-changed rather than picking one by txid.

Selection MUST preserve historical replacements before deriving today's usable graph. Implement the following deterministic two-pass rule, or an equivalent computation with the same result. It does not reproduce every miner's mempool or fee/resource policy.

First reserve the current selected chain's verified choices and ancestor closure. Replay the remaining candidates over journal frontiers: every readyAt, every retained chain/finality-context change and the current journal frontier, in numeric order. At each frontier, reconsider supported candidates in their total candidate order. A candidate needs all unconfirmed parents selected and final in that frontier's context; current-view forced chain ancestors are already reserved. Until then it is dependent/unresolved, not a selectable child. Record eligibleAt and its context at the first frontier where those dependencies and the enabled finality policy permit consideration, before testing competing input reservations. This is the candidate's first-seen eligibility; worker completion cannot assign it. An eligible candidate takes free inputs or replaces the single non-final selection occupying exactly its ordered inputs under section 10.3. It cannot replace multiple independently selected transactions. An alternative that loses a reservation remains a conflict in this historical pass; do not repeatedly retry it as though it had just arrived. A valid replacement records predecessor, replacement, frontier and context. A replaced version cannot be reselected later in this replay. Deferred dependencies and non-final candidates excluded by the nonFinal=false setting are reconsidered at later frontiers. This historical pass prevents later maturation from attaching a child to an old version before replay reaches the replacement that already displaced it.

For current-view rebuilding, a historical conflict is still an available alternative if the prior winner is no longer usable; historical replacement records remain distinct from that conflict status. Then collapse each valid replacement chain into its surviving candidate. That candidate inherits the original candidate's input reservation priority, because each link consumes the same ordered inputs. Reservation order is (original candidate eligibleAt, original candidate total order); candidates without a replacement chain use their own values. A candidate never selectable in historical replay has no earlier eligibleAt; if it first becomes usable during the current-view rebuild, use the current journal frontier, then its total order. Ancestors precede descendants within that frontier. In particular, nonFinal=false cannot give an earlier unsupported non-final candidate priority over a final candidate that was already selectable. Descendants retain their own eligibility/order. This inheritance prevents an intervening different-input-set conflict from taking priority merely because the replacement arrived later. With the current chain choices still reserved, rebuild the usable graph in reservation order, with ancestors before descendants. Omit replaced versions. Select a candidate only if its current dependencies are selected final, its inputs are free and its current finality is supported by the configured policy. Retain losing alternatives and reasons. Finality/currentness uses the current view, not the historical replacement context. A replacement chain preserves which version survived; it does not exempt that version from today's dependency or finality checks. Missing current evidence makes the affected component unresolved/stale.

Rebuild both passes when support, authorization, policy or accepted-chain constraints change, retaining original ingress and historical contexts. A reorg that puts a parent back behind its locktime boundary removes its descendants from the usable selection until their dependencies qualify again. This can release unrelated inputs previously reserved by those descendants; reconsider all affected candidates in the same ordered component computation. Publishing requires a compatible dependency-closed graph, never a child whose parent lost or is currently non-final. The replacement journal and today's selection are separate durable results; neither performs wallet actions.

A selected ancestor can be included while its selected child is unconfirmed: the child still consumes the ancestor's output provisionally. Conversely, accepted-chain evidence for B replacing unconfirmed A invalidates the A-derived selection and its descendants together. On reorg, remove only invalidated placement/assessment claims, reverify affected dependencies, replay the retained provisional choices and publish a coherent component revision. Historical facts, consent, acquisitions and original release evidence remain retained. Missing proof of the new view yields stale/unresolved state, never assumed confirmation or unspentness.

For BRC-197 purchases, different valid txids may have the same full authenticated purchase commitment because unlocking Scripts are outside the `0x41` signature preimage. Reconciliation continues to keep their raw transaction facts, spend edges, descendants and selected-chain status separate by exact txid. A BRC-196/198 domain projection may associate independently verified equivalent txids with one acquisition and License entitlement by that commitment; it must not collapse Bitcoin facts, treat an alias as the signed historical release txid, or count the aliases as two purchases. A txid seen without complete independently verified bytes and lineage is unresolved, even if a peer claims the same commitment.

### 10.5 Membership and durable ports

Off-chain output/withdraw/re-entry order is per source generation and sequence, not Bitcoin transaction order. For BRC-193, a completed snapshot seeds that source at W; only its live groups strictly after W change that seed. Process live membership groups in ascending sequence, applying each whole group atomically. A stale replay cannot undo a later withdrawal or re-entry, and verification finishing later cannot change that order. Within a group, observations retain the provider's committed array order. Source reset stages a new snapshot generation; publication swaps that source's membership only after completion, fences late work from the old generation and leaves other sources and verified transaction history intact. Finite adapters serialize their own refresh generations and never infer a chain spend from absence.

Membership and transaction-derived currentness remain separate. A later source re-entry for an already-spent output makes that source's assertion present again; it does not make the output unspent. A withdrawal removes only that source's assertion, not the transaction fact, spend edge or another source's membership. Domain projections may require membership for discovery, but they MUST consult the reconciled graph before labelling any output current.

The following additional local structures are part of the port contract. References to transactions are scoped to the AcceptedInput partition and configured chain. Empty arrays are permitted. The optional order is absent until earliest valid support is established; eligible is absent until a selectable frontier exists; replaces is present only for a selected replacement and names its immediately superseded candidate.

```typescript
type IngressPosition = { journalId:LocalId; position:U64; index:U32 }
type CandidateOrder = {
  firstRaw:IngressPosition; readyAt:U64; depth:U32; readyContextId:LocalId
}
type ReconciledTransaction = {
  txid:Hex32; evidenceIds:Hex32[]; firstRaw:IngressPosition;
  order?:CandidateOrder; eligible?:{at:U64,contextId:LocalId};
  status:'unresolved'|'invalid'|'limited'|'unsupported'|'conflicting'
    |'dependent-conflict'|'selected-non-final'|'selected-final'|'included';
  reason:string; dependencies:Outpoint[];
  replaces?:Hex32; conflictsWith:Hex32[]
}
type SourceMembership = {
  scope:Scope; generation:U64; outpoint:Outpoint; present:boolean;
  phase:'snapshot'|'live'|'finite'; sequence:U64; observationId:string
}
type ReconciledState = {
  profile:'https://bsv.brc.dev/apps/0192#bitcoin-spend-reconciliation-v1';
  nonFinal:boolean; journalId:LocalId; through:U64; contextId:LocalId;
  transactions:ReconciledTransaction[]; memberships:SourceMembership[];
  replacements:{previous:Hex32,replacement:Hex32,at:U64,contextId:LocalId}[];
  pendingComponents:{txids:Hex32[],reason:string}[]
}
```

AcceptedInput carries this ReconciledState. An alternative language binding may expose an equivalent lossless journal plus deterministic reconciler, but must reproduce every field and outcome. ChainView retains medianTimePast and chainPolicyDigest as declared in section 8. Reconcile mutations verify that supplied state equals the deterministic result of retained facts/order/context; callers cannot install arbitrary choices. The journal retains authorization provenance, raw/proof availability positions, context changes, source sequence/generation fences, assessment support and selection/replacement reasons. Replacement records retain replay frontier/order; they are not sorted by txid. Transaction rows sort by txid; membership rows by scope/generation/outpoint; conflicts/evidence lists are sorted and duplicate-free under the common encodings. dependencies retains the actual ordered input-outpoint vector; sorting it would destroy the replacement predicate. Valid Bitcoin transactions cannot repeat an input outpoint. Reconciliation and its accepted revision commit together; projector replay uses that state, not lexicographic observation presentation order as an implicit chronology. One invalidated candidate can change an entire connected component; commit all affected removals/additions/currentness together while preserving the existing source-group barrier. Reads expose pending/stale state while that work is incomplete.

The reusable reconciliation corpus exercises both verification-completion orders with restart, successor-before-predecessor evidence from different sources, incomplete versus merely unfinished earlier evidence, competing final spends overridden by actual selected-chain inclusion, reorg and descendant reassessment, pairwise sequence comparisons, finality boundaries and finalization, and output/withdraw/re-entry with stale replay. Test fixtures use actual signed transactions and independently verified inclusion bytes. A stateful adapter must reproduce those results with its real journal/store and the existing cancellation, partition, bounded-work and recovery requirements.


# LCH Acquisition through Private Overlays and Listing Covenants

Ty Everett (<ty@projectbabbage.com>)

## Abstract and status

This specification binds [BRC-170 Locked Content Header](/apps/0170) licensing to two private-overlay purchase paths: BRC-105-funded lookup and a BRC-197 listing purchase followed by BRC-196 POTATOES. It reuses LCH assets, offers, buyer requests, authority, policies, licenses, encryption and key grants. Any conforming content application can use these bindings without adopting another application's token format or user interface.

LCH's deterministic CBOR and signature preimages remain authoritative. BRC-192's JSON transport carries complete LCH bytes as base64 and MUST NOT rehash diagnostic JSON as if it were an LCH object. An unsupported critical mechanism makes the selected offer unusable before payment.

## 1. Common bindings

This BRC registers extension IRI `https://bsv.brc.dev/apps/0198#overlay-acquisition-v1`, abbreviated `E` below. In an LCH Offer's `extensions[E]`, the value is the following deterministic CBOR map. The Offer's `critical` array MUST include E:

```cddl
overlay-binding = {
  "version": 1,
  "mode": "paid-lookup" / "listing-covenant",
  "seller": bstr .size 33,
  "service": tstr,
  "endpoint": tstr,
  "chain": { "network": tstr, "genesisHash": bstr .size 32 },
  ? "lineageAnchor": { "txid": bstr .size 32, "outputIndex": uint },
  ? "releasePolicy": bstr
}
```

Seller MUST equal the Offer seller and be authorized for its roles. Endpoint is the selected concrete base from BRC-194; payment.endpoint names the appropriate acquisition route below. `lineageAnchor` and `releasePolicy` are present exactly for listing-covenant mode. Output index is at most 4294967295. `releasePolicy` is the UTF-8 JCS encoding of the BRC-196 ReleasePolicy selected by this Offer, without a JSON digest wrapper. Its interpretation is critical. The chain and seller are compared with authenticated capability and transaction evidence, not taken on trust from a locator.

The application submits a complete LCH signed License Request in the outer `request` bytes. Use a BRC-77 publicly verifiable buyer signature. `requestId` for BRC-195 or BRC-196 is the lowercase hexadecimal LCH License Request ID. That deterministic binding replaces the normally random retry ID; the LCH requestNonce provides freshness. Asset ID equals the LCH Asset ID. `termsDigest` is the LCH Offer ID, thereby binding the full signed offer body including policy, payment and key-delivery choices. The authenticated recipient equals the LCH buyer. An adapter MUST compare each binding before initiating payment.

Both modes return context/secret with schema E and deterministic CBOR bytes:

```cddl
acquisition-context = {
  "version": 1,
  "license": signed-object,
  "evidence": [* {"type": tstr, "object": signed-object}],
  "settlement": bstr,
  ? "purchaseEvidence": bstr,
  ? "paymentEvidence": bstr
}
```

`signed-object` is BRC-170's encoding. Evidence contains explicitly typed objects under section 5. Settlement contains the complete UTF-8 JCS signed packet for the selected mode. PurchaseEvidence is present exactly for covenant mode and contains the complete UTF-8 JCS package defined in section 5. Key grants remain inside the License; this wrapper cannot expand permitted actions or release extra key periods.

## 2. Paid lookup binding

The binding selects mode paid-lookup. The Offer uses an existing supported LCH usage profile and payment protocol `https://bsv.brc.dev/apps/0170#brc105-single-v1`, with exactly one buyer-bound requirement and payment.endpoint equal to the **absolute HTTPS URL** formed by appending `/overlay/v1/private/acquire` to the selected canonical base. The requirement endpoint equals that same URL. E expressly selects the outer Acquire envelope instead of posting a bare LCH request; unaware LCH clients reject E rather than sending the wrong wire shape. The price and challenge match the single LCH compensation duty exactly. The selected seller is payee or has the required receivePayment authority.

After BRC-105 verification, the issuer returns the signed License and selected key grants in protected lookup context alongside the purchased output's BEEF. LCH authority, Offer validity, buyer consent, policy evaluation, ciphertext preflight and key-commitment checks still apply. The recovery deadline is at least the Offer's recoveryPeriodSeconds beyond payableUntil, as well as BRC-195's minimum. An expired catalogue output does not erase a funded License Request. Payment retry and License reissue preserve the same request and rights without another charge.

Existing ciphertext MUST NOT be labeled as LCH segmented AES-256-GCM unless actually encoded that way. A migration can retain an older decoder under an explicit application schema while newly published LCH assets use supported encryption. Reusing a container label does not change encryption bytes or historic buyer rights.

## 3. Covenant accumulation binding

This BRC registers payment mechanism and settlement profile `https://bsv.brc.dev/apps/0198#listing-accumulation-v1`, abbreviated C. An Offer using C MUST include C and E in its critical array, select listing-covenant mode, and use the critical standing-offer profile S defined in section 5 with exactly one fixed-price standing requirement. The Payee MUST be the authorized seller/collector and the amount MUST equal purchasePrice. payment.endpoint and the standing requirement endpoint equal the absolute HTTPS URL formed by appending `/overlay/v1/purchases/prepare` to the selected canonical base. A duty that requires immediate separate payments to several rights holders cannot be satisfied by this profile. Such an offer needs a distinct compatible settlement design or LCH's existing multilateral path.

To avoid a circular commitment, the LCH Offer commits to the already existing lineage anchor, not a listing ID or future genesis txid. The chain is already in the binding. The listing descriptor includes the resulting Offer ID; its signed genesis authorization is delivered during catalogue lookup/preparation. Clients compare the Offer anchor, descriptor anchor, seller, asset, price, chain and exact genesis proof. No self-reference is solved by substituting an arbitrary placeholder into signed bytes.

Preparation returns BRC-196 signed terms binding the outer request digest. The buyer constructs the BRC-197 permissionless purchase with the acquisition/request/recipient/Offer commitments, submits the complete transaction to the selected private topic and recovers through BRC-196. There is no additional BRC-105 payment. Accumulated value remains in the listing until a permitted payout; a purchase is not a BRC-29 payment directly delivered to every artist.

The seller issues a BRC-192 signed packet of type `lch-covenant-settlement` with this body:

```typescript
type CovenantSettlement = {
  version: 1; seller: Identity; buyer: Identity;
  requestId: Hex32; offerId: Hex32; assetId: Hex32;
  dutyUid: string; acquisitionId: Hex32;
  listingId: Hex32; previous: Outpoint; successor: Outpoint;
  txid: Hex32; purchaseCommitment: Hex32; satoshis: U64;
  releasePolicy: ReleasePolicy; releaseEvidenceDigest: Hex32;
  issuedAt: U64; recoveryUntil: U64
}
```

Its ID is `digest("lch-covenant-settlement", body)`. The signer equals the authorized seller/collector. The issuer MUST independently verify the historical transaction, exact predecessor/successor, increment, receipt, genesis ancestry, the full BRC-197 purchase commitment and selected release evidence before marking the duty fulfilled. A seller signature alone cannot substitute for those checks. `txid` and `successor` identify the exact transaction for the original signed release; `purchaseCommitment` is the same full 32-byte authenticated preimage digest carried by BRC-196 POTATOES. Neither the License nor the settlement is silently rewritten if a different, independently valid txid for that commitment later becomes current on chain. Historical fulfillment and selected-chain currentness are separate checks: the License verifier must not require the historical successor outpoint to remain on the selected chain when an independently verified equivalent alias is current.

The License includes a fulfillment `{dutyUid, settlementProfile:C, receiptIds:[settlementIdBytes]}`. E's critical extension in the License maps this particular ID to the complete covenant settlement packet in acquisition-context.settlement; it is expressly not an LCH Payment Receipt ID. No other BRC-170 fulfillment fields are used for C. The License's `extensions[E]` is a CBOR map containing `version:1`, `mode:"listing-covenant"` and `settlementId` as a 32-byte byte string; its critical array includes E and C, with extensions\[C]={version:1} in the License (the Offer has the richer section 6 value). The buyer-bearing Request, License subject, key-grant recipient, POTATOES recipient and settlement buyer must all match. The standing Offer contains no preselected buyer; S defines the exact late binding. The LCH request ID and the outer BRC-196 request digest are different hashes with different domains; the adapter validates both, never equates them.

This registers one new interpretation of settlement evidence while retaining LCH's existing fulfillment shape and critical-extension discipline. It MUST NOT be relabeled receipt-complete or authorized-output settlement. Exactly one compensation duty is supported in either mode; an Offer with two such duties is unsupported even when both name the same collector. Its dutyUid matches the single requirement and fulfillment exactly; later payout outputs are constrained by the listing’s current revenue schedule under BRC-197; the purchase itself does not immediately pay the individual recipients.

## 4. Playback, recovery and conformance

POTATOES delivery occurs only when the License, settlement proof and key grants can be returned consistently with the selected release policy. Pending license issuance remains delivery-pending even if STEAK exists. The same accepted payment/purchase commitment and License Request can recover an equivalent License through the promised period. A verified txid alias does not create a second compensation duty, License, key grant or charge. For mined release, the settlement must name the actually mined alias before first issuance. If a weaker release policy delivered against an earlier alias, keep the original License, settlement and release evidence as historical authorization; assess a later mined alias separately by verifying its complete transaction and matching commitment. A later alias proves current economic settlement only through that independent chain assessment, never by changing the signed historical receipt. Already granted offline use cannot acquire a new online dependency merely because the transport migrated.

Ciphertext/UHRP/CHIRP range retrieval and authenticated decryption are separate from catalogue event streaming. A live output feed neither supplies media bytes nor changes an Agreement. LCH's safeguards on unknown terms, plaintext release, composition, rights authority and raw-key revocation limits remain in force.

Conformance covers existing fixed-render acquisition through paid lookup; correct CBOR/JSON separation; request-ID binding; wrong Offer/asset/buyer; unknown critical extension; covenant purchase and License; delayed release; wrong increment or lineage; duplicate recovery; multi-payee duty rejection; legacy ciphertext distinction; and authenticated playback of the selected key periods. Claims must list the exact usage, payment, encryption and key-delivery mechanisms implemented; “LCH compatible” alone is insufficient.

## 5. Standing Offers, retry identity and verification closure

The critical extension `https://bsv.brc.dev/apps/0198#standing-offer-v1` (S) has value `{version:1}` in Offer.extensions, and MUST be listed in Offer.critical for C. Only C uses S in this version. S explicitly replaces **only** C's fixed pricing requirement shape with:

```cddl
standing-requirement = {
  "dutyUid": tstr,
  "payee": bstr .size 33,
  "endpoint": tstr,
  "satoshis": uint,
  ? "interest": tstr
}
```

Buyer is **forbidden**, not ignored or filled with an anyone key. BRC-170's ordinary buyer-bearing payment requirement remains unchanged for all Offers without S. A client lacking S rejects this Offer before payment. All other Offer/authority/policy/fixed-price rules remain. The standing policy must leave the individual assignee open at Offer time; the signed License Request supplies the authenticated buyer, consents to the complete Offer ID/policy/action/selection and late binding, and fixes that buyer in the Agreement/settlement/License. A successor listing continues committing to the same Offer ID. Thus buyers A and B can issue distinct consented Requests against one unchanged descriptor/Offer, each receiving a separately recipient-bound License; neither Request edits the standing Offer. The Offer uses C as payment protocol, E as transport binding, and S as requirement semantics, with all three critical and the exact extensions\[C] value defined below. C requires BRC-197's mandatory permissionless payout and expiry retirement; a payout-less listing is not a valid v1 family member.

Outer retries are **byte-stable**. Before first Acquire/Prepare, persist the complete original signed deterministic-CBOR License Request, including its ordered signatures array. Re-signing the same body, reordering signatures or adding a signature changes outer request bytes and therefore conflicts under the same LCH request ID/acquisition ID, even when every signature is cryptographically valid. It is not a second purchase and must not generate a new payment. Recovery by acquisition ID remains available when the original request envelope is unavailable. Two valid BRC-77 signatures over one body have the same LCH body ID and different outer digests by design. This explicit rule applies to all nested signed request retries; no implementation-specific semantic normalization is allowed.

The selected capability has a non-critical-to-other-profiles extension keyed E, value `{version:1,bindings:[{kind:"lookup"|"topic",service:string,mode:"paid-lookup"|"listing-covenant",mechanisms:string[]}]}`. It is **required** by clients selecting this BRC. A paid binding names kind lookup, the exact BRC-195 service and E plus the selected BRC-170 payment/usage/encryption/key-delivery profiles. A covenant binding names kind topic, the exact BRC-196 topic and E/C/S plus those LCH mechanisms and the BRC-197 revenue-listing-v1 executable family. Entries are unique by kind/service/mode; mechanisms are unique sorted absolute IRIs. All required mechanisms must be installed locally and advertised before wallet construction. Generic steak-potatoes capability alone is insufficient. Offer.binding.service equals this exact entry and Acquire.service or Prepare.topic; Offer seller equals the selected manifest identity and authenticated seller. Offer.binding.endpoint is the canonical base, not a route suffix. With base <https://seller.example/api>, covenant payment.endpoint is <https://seller.example/api/overlay/v1/purchases/prepare>.

Before creating a preparation/quote require Offer.notBefore <= now < Offer.notAfter if present, and preserve its authority/terms/bytes. Freeze the cutoff in payableUntil/purchaseUntil no later than Offer.notAfter. Require recoveryUntil >= cutoff + max(86400, advertised recoverySeconds, Offer.payment.recoveryPeriodSeconds) in Challenge/Terms, settlement and POTATOES. Once prepared/quoted, later expiry or withdrawal stops new preparations but **does not** revoke timely first-delivery/recovery for that existing obligation. BRC-195/196 pinned-candidate and unfinished-delivery rules prevail. A License recovered after Offer expiry is evaluated against those frozen accepted terms, not misrepresented as a newly accepted expired Offer. Original rights periods cannot be expanded by reissue.

Typed acquisition evidence is a list of `{type,object}`. Permitted type strings are exactly the pinned BRC-170 signature domains `offer`, `authority`, `quote`, `payment-demand`, `payment-receipt`, `payment-authorization`, `transaction-evidence`, and `payment-delivery-ack`, when needed by the selected mechanism. A type is never guessed by trying signatures; its shape must be the corresponding BRC-170 body and signer roles must verify. Sort by ASCII type then raw 32-byte LCH ID. Duplicate (type,ID) entries, including signature variants, are forbidden; differing bodies under one ID are an integrity failure. Maximum 128 objects, authority depth 16, decoded CBOR context 2 MiB and 256 signature checks; apply smaller local bounds first. Unknown evidence types fail unsupported. Any necessary external authority resolution is bounded and explicitly configured; missing authority is unresolved before payment/issuance, not implicitly granted. The complete Offer and Asset must be available from the Header plus evidence, with their exact original bytes/IDs; neither a diagnostic JSON map nor a displayed name suffices.

Covenant purchaseEvidence is `{version:1,lineage:LineagePackage,purchase:Evidence,terms:SignedPurchaseTerms,release:ReleaseEvidence}`. The lineage target is the consumed listing, purchase selects the successor at index zero, and signed terms/domainEvidence carry the same lineage package semantics. The buyer and License issuer verify this package independently against the same installed family; the issuer does not merely accept a seller's claimed increment. Availability of all package bytes and bounded full ancestry, including reserve-stage activation, is a precondition to purchase. C requires the exact BRC-197 activation and active artifacts and all four active routes. Reproduce their Script and domain checks before advertising C; a passing arithmetic model is insufficient.

Both modes require exactly one compensation duty and no additional unmet prerequisite duties. The Offer's policy may carry separately stated downstream nonpayment duties, but this profile cannot pretend the one transfer discharged them. Buyer matches Request.buyer, authenticated caller, outer recipient, Agreement assignee, License.subject, settlement.buyer and recipient of each BRC-78 grant. Seller matches Offer seller, capability identity, Challenge/Terms seller, settlement signer and POTATOES signer. The requirement payee equals that seller or an explicit receivePayment grant authorizes it; C additionally requires descriptor.seller to equal that collector. License issuer equals Offer.licenseIssuer or has issueLicense authority; key releaser equals that issuer or has releaseKey authority. Asset/Offer/Request IDs match every repeated commitment; chain and lineageAnchor match E/descriptor/package; price matches the unique duty, requirement, Challenge or actual successor increment. ReleasePolicy matches E, signed Terms and ReleaseEvidence byte-for-byte under JCS. POTATOES.evidenceDigest and settlement releaseEvidenceDigest equal the actual release digest. Verify settlement ID, exact historical txid/outpoint, full purchase commitment and allowed fulfillment profile; substituting another profile, beneficiary or amount is invalid. If the selected chain contains an equivalent txid alias, verify that candidate's complete transaction, commitment and lineage before reporting the original License as currently paid. Do not substitute the alias into the signed settlement, infer equivalence from equal outputs alone or mint a second fulfillment. A collector receipt does not claim direct distribution to several beneficiaries.

Paid lookup uses a new explicit settlement profile `https://bsv.brc.dev/apps/0198#paid-lookup-settlement-v1` (P), declared in the Offer mechanisms and critical extensions with value `{version:1}`. It is compatible with the BRC-170 brc105-single payment transport as specialized by E, but does not fabricate a multilateral Payment Demand. Its signed BRC-192 packet type is `lch-lookup-settlement`, with body `{version:1,seller:Identity,buyer:Identity,requestId:Hex32,offerId:Hex32,assetId:Hex32,dutyUid:string,acquisitionId:Hex32,funding:{chain:Chain,txid:Hex32,outputIndex:U32},satoshis:U64,acceptancePolicy:ReleasePolicy,releaseEvidenceDigest:Hex32,issuedAt:U64,recoveryUntil:U64}`. The signer is the authorized payee/collector, and the amount/output/acceptance are independently verified against the frozen BRC-195 challenge. Its ID is digest(lch-lookup-settlement,body). Context additionally contains `paymentEvidence` (present exactly in paid mode), a byte string of UTF-8 JCS `{version:1,challenge:Challenge,payment:Evidence,release:ReleaseEvidence,derivationSuffix:string}`; payment selects the exact challenged funding output. License.fulfillments is exactly `[{dutyUid,settlementProfile:P,receiptIds:[settlementIdBytes]}]`; License.extensions\[E] is `{version:1,mode:"paid-lookup",settlementId:bstr32}` and License.critical includes E/P, with License.extensions\[P]={version:1}. This expressly maps the ID to context.settlement, not to an LCH Payment Receipt. For C the analogous C fulfillment remains as in section 3. Other fulfillment fields are forbidden in these single-duty bindings.

Equivalent License reissue retains exact Agreement bytes/digest, request, asset, accepted rights/selection/key periods/commitments and settlement ID. Signature randomness, BRC-78 nonce and non-rights issuedAt may change, never the historical settlement/release basis or original entitlement. Decrypt and authenticate each selected content segment before exposing plaintext, check keyId against the CEK commitment and encryption period, and reject overgrant/unknown critical mechanisms before playback. Complete cryptographic/byte fixtures and qualification status are enumerated in the packet guide; no claim of deployable covenant acquisition is permitted without the registered family’s Script/domain qualification and compatible wallet integration.

## 6. Script-enforced collector revenue

For C, Offer.extensions\[C] is exactly the following CBOR map (and C remains critical):

```cddl
collector-revenue = {
  "version": 1,
  "family": "https://bsv.brc.dev/tokens/0197#revenue-listing-v1",
  "expiryHeight": uint,
  "initialRevenue": {
    "recipients": [1*8 {"identity": bstr .size 33, "weight": uint}]
  },
  "schedule": "immutable",
  "derivation": "brc29-anyone-fixed",
  "withdrawal": "permissionless-quanta",
  "remainders": "retain-until-payout",
  "retirement": "seller-child-or-expiry-height-exact-top-up"
}
```

Recipients are sorted/unique by raw key, valid secp256k1 identities, and weights/total obey BRC-197. Convert identity byte strings to lowercase hex when comparing with descriptor.initialRevenue. Compare every entry, family and expiryHeight exactly; a digest of a displayed schedule is insufficient. The Offer and descriptor disclose the expiry height and public fixed-child derivation risk before payment. These initial terms are known before the Offer ID, so including them does not reintroduce the Offer/descriptor hash cycle. Paid mode does not carry C or claim script-enforced distribution of its payment.

An issuer selecting C accepts one immutable schedule per lineage. Preparation discloses the actual consumed outpoint and schedule through domainEvidence, and rejects a listing already at or beyond expiryHeight under its current chain view. Purchase preserves the script and increments its value; split preserves the schedule on both branches. Anyone can initiate payout, which pays every committed BRC-29-derived child destination in whole integer quanta and retains the remainder. The protected seller child can retire early; after expiry anyone can retire with an exact externally funded top-up. A later expiry does not revoke a timely prepared purchase's BRC-196 delivery/recovery obligation. No seller-only payout bypass, merge or amendment exists.

The Offer remains reusable across valid purchases and splits until its cutoff or lineage retirement. An outstanding preparation remains bound to its exact old outpoint and cannot silently follow a competing purchase, split, payout or retirement. Siblings and previously issued Licenses are not rewritten. A changed recipient or share schedule requires a new Offer, descriptor and seller-authorized lineage. An Offer promising immediate separate payments is incompatible with this accumulation profile.

The issuer and buyer verify the same portable genesis/activation/transition package, schedule, purchase scripts, increment and receipt. A settlement signature is attributable evidence of that validated purchase, not a replacement for it. The covenant enables withdrawal by anyone and complete distribution after expiry, but it does not prove a recipient has spent the resulting output. All signers and recipients require fixed-child wallet support without exporting a private scalar.

Lineage and purchase validation follow BRC-197's ordinary Bitcoin finality rules. A valid settled transition is not excluded because it was previously non-final or uses locktime/sequences other than the recommended construction. A currently non-final purchase remains pending and cannot fulfill the compensation duty or release a License until the selected BRC-196 admission/release conditions are met. Recovery from a local rejection through an independently verified eligible alias follows BRC-196's state transitions under the same acquisition and purchase commitment; it does not create a new duty or alter an already signed settlement.

Only operation 1 and the exact prepared acquisition/request/recipient receipt can satisfy this profile's purchase duty or create a License. A copied Script, direct active output or unrelated funded contribution is not a lineage descendant and cannot be counted as a sale.

Clients use BRC-192 section 10 to reconcile actual input consumption and current listing state across sources. Domain rejection does not undo a Bitcoin spend or resurrect its input listing. A chain reorganization can invalidate a current settlement assessment while leaving the historical License, Agreement and release evidence retained. Neither source re-entry nor a new provisional selection grants additional rights or initiates payment.


# 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 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) specifies 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-190](/apps/0190) 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-190 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-190](/apps/0190) 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-190](/apps/0190) 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 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-190](/apps/0190) 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-190](/apps/0190) 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-190](/apps/0190) 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-190](/apps/0190) 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-190](/apps/0190)'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-190](/apps/0190) 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-190](/apps/0190) 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-190](/apps/0190) 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-190](/apps/0190)'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 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-147: 1Sat Ordinals Basket Profile for BRC-46 / BRC-100](/tokens/0147)
* [BRC-150: 1Sat Provenance Remittance for Basket `1sat`](/tokens/0150)
* [BRC-168: Verifiable Time Allocation](/apps/0168)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [BRC-190: Access Gates for Metanet Rooms](/apps/0190)
* [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)


# Dead-Man Timed Reveal of a Sealed Secret

RexStarBSV (<RexStarBSV@protonmail.com>)

## Abstract

This BRC defines a dead-man switch: a way for an owner to seal a document so that it stays private while the owner is active and becomes public if the owner stops checking in. The owner encrypts the document under a data key, splits that key into `n` shares with a threshold `m` (\[SSS]), and commits the shares in a Merkle tree whose root is `root`. The owner arms the switch on-chain by publishing the encrypted document, a set of `k` check-in outputs at addresses derived from one base key `P`, and one watched outpoint `F`. Each check-in output has a cutoff height; the owner stays alive by spending it before that height. If the owner misses a cutoff, the holders of the shares reconstruct the data key and publish a release: an output carrying one share, its Merkle path, and a sealed pointer to the document, under a 137-byte header that binds `root` to `P` with a signature only the owner could make. A reader that collects `m` releases reconstructs the data key, decrypts the document, and reads it. A single reserved marker makes releases discoverable, and a reader ranks them by the capital locked toward them, treated as advisory because the marker is permissionless. The disclosure is gated by the holders, not by any script: the firing rule, the quorum that decides a cutoff has passed, the authentication a reader performs, and the bounds on everything a reader parses from the chain are all specified here, because each is a place where a careless implementation discloses too early, fails to disclose at all, or trusts a value an attacker chose.

## Motivation

A person who holds information that should come out if they are silenced, an estate that should open if its owner dies, a document that should survive the person who wrote it: each needs a secret that stays sealed while a hand is on it and opens when the hand is lifted. A plain encrypted file does not do this, because someone has to hold the key, and whoever holds the key can open it at any time or lose it. Splitting the key among several holders with a threshold spreads that trust, and tying the opening to an on-chain signal that the owner has stopped acting turns "someone decided to open it" into "the owner stopped saying not to".

Building this correctly is harder than it looks, and every error is silent until the day it matters. There are five classes of error this BRC exists to rule out.

1. **Disclosing too early.** The opening is a decision the holders make by watching the chain. If a holder reads a provider that is only caught up to last week and treats "no spend seen" as "the owner missed the cutoff", it releases a live person's secret. The quorum that decides a cutoff has passed has to measure how far each source's address history is actually complete, and compare that against the holder's own proof-of-work tip, before it may ever conclude a check-in was missed (section 8).
2. **Never disclosing.** The opposite failure is a release that the holder believes it published but that no node ever accepted, or a firing rule that waits for the final deadline before it will act on an earlier missed check-in. A holder has to tell apart a release it has merely recorded from one a node has acknowledged from one a block has confirmed, and it has to judge each cutoff on its own, not behind the last one (sections 7 and 9).
3. **Opening the wrong thing.** The data key becomes public the moment a reveal is meant to happen, so the Merkle root over the shares, the shares themselves, their paths, and the key can all be re-packaged by a stranger into a different header pointing at different content. "The first release of a root wins" is not an authenticity rule. A reader has to bind the released document to a signature the owner made over `root`, under the key the owner used to arm the switch, and has to treat a root that appears under two different commitments as disputed rather than silently electing a winner by block height (section 10).
4. **Trusting attacker-supplied bytes.** Everything a reader pulls off the chain here, the clear metadata, the release outputs, the discovery feed, was written by whoever paid the fee, which is anyone. A title of unbounded length, a category outside the fixed set, a transaction listed in the feed that no server will serve: each is an input a reader has to bound and validate on read, not at creation (sections 11 and 12).
5. **Linking the owner to the switch.** The switch's whole privacy rests on its on-chain footprint being indistinguishable from ordinary activity before a reveal, and on the owner's own coins never being spent into the switch's machinery in a way that links them. That has to hold on every coin-selection path, and the check-in keys have to be derived so that their addresses are unlinkable to the base key and so that one nonce reuse cannot compromise every key at once (sections 5 and 7).

A single specified format, with the firing rule and the reader's authentication fixed to the byte, lets two implementations agree on when a switch has fired, on whether a release is genuine, and on what a reader may show, so that a holder in one program and a reader in another reach the same verdict about the same switch.

## Scope and conformance

Section 1 defines terms. Section 2 states the trust model and what each party learns. Sections 3 to 6 specify the sealed secret and the arming transaction. Sections 7 to 9 specify the owner's upkeep, the firing rule, and the release. Sections 10 and 11 specify how a reader reconstructs, authenticates, discovers, and ranks. Sections 12 to 14 specify the clear metadata bounds, the owner-to-holder message encoding, and the reserved markers. Section 15 lists every requirement by role.

Out of scope are how the owner recruits and reaches the holders, how a holder is told to watch a switch, the transport that carries a share to a holder or a release instruction back, the fee rate, and the user interface. The wire bytes, the firing rule, and the reader's checks are in scope, because those are what two implementations must share.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in capitals. Four roles are used. An **owner** seals a document and arms, maintains, cancels or withdraws a switch. A **holder** keeps one share and, after a cutoff is missed, publishes a release. A **reader** collects releases, reconstructs, authenticates and displays a document, and reads the discovery feed; a reader is a wallet's registry view or a standalone page, and the two MUST reach the same verdict. A **wallet** is any implementation that builds transactions on behalf of third-party applications or agents.

## Relationship to existing standards

### The companion deadline-gate covenant

The companion BRC-215 (block-height deadline covenant templates, OP\_PUSH\_TX) gates a *spend* so that it cannot be mined before a height. This BRC gates a *disclosure*, and it does so off-chain: its secret is released by the holders when they decide a cutoff has passed, rather than unlocked by a script. The two are independent.

### Key derivation and signatures

Check-in keys are derived from a base key `P` by additive tweaks on secp256k1, in the manner of child-key derivation (\[BRC-42] uses the same group operation with a different tweak input). Signatures are ECDSA over secp256k1, DER-encoded, in canonical low-S form, and a signer MUST produce the nonce deterministically (\[RFC6979]); section 5 states why a randomized nonce is catastrophic here and not merely discouraged.

### Hashing, encryption and encoding

Hashes are SHA-256 (\[FIPS180-4]), single unless stated. Documents and the sealed pointer are encrypted with AES-256-GCM (\[SP800-38D]). Owner-to-holder messages are JSON (\[RFC8259]). The secret is split with Shamir's scheme over GF(2^8) (\[SSS]).

### Discovery

A release is found by the script hash of a fixed marker output, the same mechanism an \[ElectrumX]-style index exposes for any output script. The marker is permissionless; section 11 states what a reader may and may not conclude from it.

## Specification

### 1. Terminology

* **Data key**: a 32-byte key. The document is encrypted under it; the shares reconstruct it.
* **Share**: one Shamir share, a 33-byte string `x || y`, where `x` is one byte in `1..255` and `y` is 32 bytes. With threshold `m = 1` the single share's `y` is the data key itself (section 3).
* **`root`**: the 32-byte Merkle root over the shares (section 3).
* **`key_commitment`**: a 32-byte hash binding `root`, the data key and the sealed pointer (section 3).
* **`P`**: the 33-byte compressed base public key for the check-in outputs; `p` its private half, the fuse master key.
* **Fuse**: one check-in output, at a P2PKH address derived from `P` and an index `k` (section 5). "Fuse" and "check-in output" are the same thing.
* **`F`**: the watched outpoint. Spending `F` re-encodes or cancels the switch; the owner watches it for re-encoding and so does a reader (sections 7 and 10).
* **Cutoff `cutoff(j)`**: the block height by which the owner must have spent fuse `j`. `cutoff(j) = first_cutoff + j * interval`.
* **Release**: the on-chain output that carries one share, its Merkle path, the header, and the sealed pointer, so a reader can reconstruct (section 9).
* **`CONFIRM_DEPTH`**: 3. The number of blocks a height-dependent fact must be buried under before any party acts on it.
* **`TIP_AHEAD`**: 6. How far past a cutoff a source's history must reach before it counts as a witness that a fuse is unspent (section 8).
* **`MIN_SOURCES`**: 2. The least number of independent archival sources an unspent verdict needs (section 8).

Hex strings below are byte strings in the order they appear on the wire. ASCII strings in `b"..."` are their UTF-8 bytes.

### 2. The trust model

The disclosure is not gated by any script or key held back from the holders. Any coalition of `m` holders can reconstruct the data key and read the document at any moment, before any cutoff, because the shares are the only secret and the holders already have them. The on-chain schedule tells an honest holder *when it is meant to* release; it does not stop a dishonest coalition of `m` from releasing early, and it cannot. An owner MUST choose `m` and the holders accordingly, and an implementation MUST present this plainly rather than implying the chain enforces the timing.

The case `m = 1` is the sharp edge: a single holder can open the secret alone, and that holder's share is the data key verbatim, so even passive leakage of that one share discloses the document. An implementation MUST restrict `m = 1` to a holder the owner controls, such as the owner's own second device, and SHOULD require an explicit, logged acknowledgement from the owner before arming with `m = 1`, and SHOULD warn for low `m` generally.

What a holder learns is bounded on purpose. A holder is given its share and a watch spec (section 4): the outpoint `F`, the base key `P`, the fuse index `k`, and the schedule. A holder is not told `m`, `n`, the number of other holders, the document length, or the owner's identity. The sealed pointer a holder carries in a release stays sealed until the data key is reconstructed, so a holder cannot read the document, or even locate it, from what it holds.

### 3. The sealed secret

#### 3.1 Shares

The data key is split with Shamir's scheme over GF(2^8) with the AES field polynomial `0x11b`, threshold `m`, count `n`, with `1 <= m <= n <= 32`. Each share is `x || y`: `x` a byte in `1..255`, `y` 32 bytes, one GF(2^8) interpolation per key byte. Reconstruction interpolates `m` shares at `x = 0`.

Shamir reconstruction over this field has no integrity of its own: a wrong, foreign, or insufficient set of shares interpolates to some 32-byte value with no error raised, and a single crafted share lets an attacker steer the result to a key of their choosing. Authentication of a reconstructed key is therefore never the reconstruction itself; it is `key_commitment` and the Merkle path together (section 10). With `m = 1` the interpolation is the share's `y` unchanged, so the one share is the data key.

#### 3.2 The Merkle root

The shares are the leaves of a binary Merkle tree with a fixed 32 leaves. The `n` real shares are placed among `32 - n` random 33-byte leaves and the 32 leaves are shuffled, so the tree's shape does not reveal `n`. A leaf hash is

```
leaf = SHA256(b"TKDM2/leaf" || share)            # share is 33 bytes
```

and an internal node is

```
node = SHA256(b"TKDM2/node" || left || right)
```

single SHA-256, domain-separated, not double SHA-256. `root` is the 32-byte root. A share's Merkle path is its 1-byte leaf index (`0..31`) and the 5 sibling hashes from leaf to root, 160 bytes, enough for a 32-leaf tree.

#### 3.3 The key commitment

```
key_commitment = SHA256( b"TKDM2/key" || root || data_key || SHA256(sealed_pointer) )
```

where `sealed_pointer` is the 540-byte sealed pointer of section 4. The commitment binds the root, the key the shares reconstruct, and the pointer to the document, so a reader that holds a reconstructed key and a decrypted pointer can check that all three belong together (section 10).

### 4. The sealed pointer and the watch spec

#### 4.1 The pointer

The pointer locates the document and records the schedule. Its plaintext is laid out as follows and zero-padded to `POINTER_PLAIN = 512` bytes:

| Field            | Width        | Notes                                                               |
| ---------------- | ------------ | ------------------------------------------------------------------- |
| version          | 1            | `2`                                                                 |
| `doc_txid`       | 32           | the document transaction id, display byte order                     |
| `len(doc_vouts)` | 1            | `1..24`                                                             |
| `doc_vouts`      | 2 each       | output indices carrying the document chunks, little-endian          |
| `doc_length`     | 4            | sealed document length, little-endian; `28 < doc_length <= 1000028` |
| `f_txid`         | 32           | the outpoint `F` transaction id                                     |
| `f_vout`         | 4            | little-endian                                                       |
| `P`              | 33           | base key, compressed                                                |
| `k`              | 2            | fuse count, little-endian                                           |
| `first_cutoff`   | 4            | little-endian                                                       |
| `interval`       | 4            | little-endian                                                       |
| `len(token)`     | 2            | little-endian, `<= 300`                                             |
| `token`          | `len(token)` | an opaque holder-supplied tag, MAY be empty                         |
| zero padding     | to 512       |                                                                     |

The pointer is sealed with AES-256-GCM under the key `SHA256(b"TKDM2/pointer" || data_key)`, with associated data `AD_POINTER` (section 1 of Test Vectors), giving `POINTER_SEALED = 512 + 28 = 540` bytes (a 12-byte nonce and a 16-byte tag). The pointer carries neither `m`, nor `n`, nor the document hash, nor the encryption parameters: a holder who decrypts nothing, and an attacker who steals a sealed pointer, learn only its length.

#### 4.2 The watch spec

A holder is given an 80-byte watch spec, which carries only what the holder needs to judge the switch:

```
version(1)=2 || f_txid(32) || f_vout(4 LE) || P(33) || k(2 LE) || first_cutoff(4 LE) || interval(4 LE)
```

The renewal fuse is index `k - 1`.

### 5. Fuse-key derivation

Fuse `j` (for `j` in `0 .. k-1`, with `k <= 1000`) is a P2PKH output whose key is derived from the base key by an additive tweak:

```
t = SHA256( b"TKDM2/fuse" || P || uint16_le(j) )
fuse_pubkey(j)  = P + t * G
fuse_privkey(j) = p + t   (mod the curve order)
fuse_script(j)  = P2PKH( HASH160( compressed fuse_pubkey(j) ) )
```

The tweak preimage MUST be exactly `b"TKDM2/fuse"`, the 33-byte compressed `P`, and `j` as a 2-byte little-endian integer, in that order. Including `P` in the preimage is a privacy requirement and not a free choice: it keeps the fuse addresses unlinkable to the base key, so an observer who does not hold `P` cannot tie a switch's check-ins to a common key (the Mathematical and cryptographic basis gives the boundary, including what a release later discloses). A derivation MUST reject a tweak `t` that is `0` or greater than or equal to the curve order, so that every fuse key is well defined and the derivation fails closed.

A signer that spends a fuse (a check-in, section 7) MUST produce its ECDSA nonce deterministically (\[RFC6979]) and MUST NOT use a randomized nonce. This is stronger than the usual advice. The fuse keys share the base secret `p` through additive tweaks, so a single nonce reused across any two fuse signatures reveals a fuse key, and from it the base secret `p` to anyone holding `P`, and with `p` every fuse key and the release-authorisation key `P` fall at once. A conforming-looking implementation that signed with a random nonce would produce valid signatures and be catastrophically breakable.

### 6. The arming transaction

Arming publishes three things, none of which carries a reserved marker, so that an armed switch is indistinguishable on-chain from ordinary wallet activity before it fires.

1. **The document.** The plaintext document, at most `MAX_DOCUMENT = 1000000` bytes, is placed in an envelope

   ```
   b"TKDE2" || root(32) || uint32_le(len(metadata)) || metadata || uint32_le(len(document)) || document
   ```

   encrypted with AES-256-GCM under the data key with associated data `AD_DOCUMENT`, which adds 28 bytes. The envelope embeds `root`, which is what section 10 anchors against. The sealed envelope is split across `1..24` outputs, each an untagged `OP_FALSE OP_RETURN <push>` carrying roughly 60,000 to 90,000 bytes of envelope with up to a quarter of random padding, and the pointer records which outputs and in what order (`doc_vouts`, `doc_length`).
2. **The fuses.** The `k` fuse outputs, at the scripts of section 5, each of value at least `MIN_FUSE_VALUE = 1000` satoshis and all distinct in value, so that spends of them are individually identifiable to the owner.
3. **The outpoint `F`.** A pre-existing outpoint that the arming spends as an input; a transaction is this switch's arming by virtue of spending `F`. The owner re-encodes or cancels the switch by spending `F` in a competing transaction, and since only one spend of `F` can be mined, the owner and a reader follow the switch by watching which spend of `F` is block-proven (section 7).

The arming's inputs, `F` among them, are the owner's and are subject to the coin segregation of section 7.

#### 6.1 The arming-validity contract

Two readers must agree on which armings exist, so the checks divide into what an owner enforces when it builds a switch and what a reader can verify from the chain.

An owner building a switch MUST enforce the following, which are fixed at creation and are not carried on chain (the sealed pointer and the watch spec hold none of `m`, `n`, `window` or `grace`), so a reader cannot check them:

* `1 <= m <= n <= 32`;
* `MIN_WINDOW <= window < interval`, where `MIN_WINDOW = 7`;
* `0 <= grace < 65536`;
* `first_cutoff > window` (the first cutoff lies beyond the check-in window).

A reader MUST reject an arming that fails any of the following, each derivable from the sealed pointer and the arming transaction:

* exactly one document envelope, whose embedded `root` the release's header matches (section 10);
* the sealed document length is consistent with the pointer's `doc_length`, and `28 < doc_length <= 1000028`;
* the `k` fuse outputs are present, at the derived scripts of section 5, each of value at least `MIN_FUSE_VALUE` and all distinct;
* `1 <= k <= 1000`;
* `1 <= len(doc_vouts) <= 24`;
* `len(token) <= 300`;
* `first_cutoff + (k - 1) * interval + CONFIRM_DEPTH < 500000000` (every cutoff is a block height, not a timestamp).

### 7. Check-in, renewal, cancellation and the owner's block discipline

The owner stays alive by spending fuse `j` before `cutoff(j)`, in order. Spending the renewal fuse (index `k - 1`) renews or cancels the switch. The owner MAY withdraw the fuse coins to end the switch. A check-in and a cancellation are version-1 spends, a check-in carrying sequence number `0xFFFFFFFE` and a cancellation `0xFFFFFFFF`, so that only the owner's own spend re-encodes or ends a switch.

Two disciplines bind the owner, and both are about not acting on a chain fact that a reorganisation can undo.

**Block proof gates every state change.** Every transition the owner records that depends on a block, the acceptance of an arming, the arm-proven state, a withdrawal, and a release coin seen spent, MUST be gated on `CONFIRM_DEPTH` confirmations, not only the firing decision. A shallower reorganisation MUST NOT be able to flip the owner's bookkeeping from armed to not, or the reverse.

**Re-encoding is detected on the outpoint, not the outputs.** An owner that re-encodes a switch spends `F`. The owner (and a reader, section 10) MUST detect this by observing `F`'s outpoint being spent, by any transaction, independent of what that transaction pays to, and MUST fetch and block-verify the full spending transaction even though it credits none of the owner's addresses. On every pass the owner MUST drop any recorded re-encoding whose transaction is no longer block-proven and re-run detection against whatever spend of `F` is block-proven now, and MUST NOT re-sign a check-in over, or treat as armed, a transaction that is not presently block-proven.

**Coin segregation.** The switch's own coins, the change from a check-in, the coin a cancellation sweeps to, and a withdrawal's output, MUST be segregated from the owner's ordinary wallet on every coin-selection path, not only the default send. A wallet MUST refuse to spend a switch output in any transaction other than the switch's own machinery, enforced at the coin store or the signing gate rather than at one chokepoint, so that no application spend, agent spend, manual coin control, or split can link the switch to the owner's other coins.

### 8. The firing rule

A holder fires (releases) when a cutoff has been missed. Deciding that a cutoff was missed is the most dangerous judgement in the system, because a false positive discloses a live owner's secret and cannot be undone. The determination has three states, and a holder MUST act only on the first two and MUST treat everything else as the third.

* **SPENT**: a block proves a transaction that spends the fuse. A spender that is merely seen, in a mempool or over a mesh, but not block-proven, is not SPENT; it yields UNKNOWN.
* **UNSPENT**: the fuse is proven unspent by a quorum that is caught up past the cutoff. This holds only when the holder's own proof-of-work header tip is at least `cutoff + TIP_AHEAD`, and at least `MIN_SOURCES` independent archival sources each report an address history that is complete to a height at least `cutoff + TIP_AHEAD`.
* **UNKNOWN**: anything else. A holder MUST NOT treat UNKNOWN as UNSPENT and MUST NOT fire on it. An implementation MUST raise UNKNOWN explicitly rather than return a value that defaults toward firing.

Several rules make UNSPENT mean what it says.

* Absence MUST be measured by how far a source's address history is known complete (its address-index cursor), not by the source's reported block-header tip. A source whose history is only recent, and so cannot be complete back to the cutoff, MUST NOT count as an unspent witness. Each source's reported height MUST be cross-checked against the holder's own proof-of-work tip.
* The quorum floor for judging fuse `j` is `cutoff(j)`. No fuse's verdict may depend on a quorum caught up past a later fuse's cutoff. A holder judges fuse `j` once the chain reaches `cutoff(j) + CONFIRM_DEPTH`, against an unspent quorum floored at `cutoff(j)`, so that an earlier missed check-in fires at its own cutoff and not behind the final one.
* Presence (reading a transaction to confirm it spends the fuse) needs the transaction itself. A holder SHOULD have at least two independent providers that can serve a raw transaction, and the spec's "any one source that reveals a spend wins" holds only when an independent fetch can also serve that transaction; its integrity is checked by its transaction-id hash, which a holder MUST verify.
* A single source MUST NOT be able to wedge the determination to UNKNOWN forever by listing one transaction that no provider will serve. A candidate spender MUST be corroborated by at least two sources before it can force UNKNOWN, or the lone source that listed an unservable transaction is dropped and the quorum re-evaluated without it. Liveness, the eventual ability to fire, is only as strong as the most adversarial single source, and an implementation MUST provide a recovery or timeout path so that one source cannot suppress a fire indefinitely.

### 9. The release

A holder that has decided a switch fired publishes a release: a transaction with two zero-value outputs.

1. **The bundle output**, `OP_FALSE OP_RETURN b"TKSH2" OP_PUSHDATA2 <len LE> <bundle>`, where `bundle` is 872 bytes:

   ```
   version(1)=2 || header(137) || index(1) || path(160) || share(33) || sealed_pointer(540)
   ```

   `index` is the share's leaf index (`0..31`) and `path` its 5 sibling hashes. The header is

   ```
   root(32) || key_commitment(32) || len(sig)(1) || sig(<=72) || zero-pad to 137
   ```

   where the padding MUST be all zero. `sig` is the release-authorisation signature:

   ```
   e   = SHA256( b"TKDM2/relauth" || root || key_commitment )
   sig = ECDSA_sign(e)  under the fuse master key p,  DER, canonical low-S
   ```

   so the header carries a signature, under the key that armed the switch, over the root and the commitment together. This is what binds a release to its owner (section 10).
2. **The discovery marker**, `OP_FALSE OP_RETURN b"TKSHREG1"`, 11 bytes, which makes the release discoverable (section 11).

A holder's view of its own release has three states, and a holder MUST keep them apart and MUST NOT collapse them:

* **RECORDED**: signed and persisted, not yet acknowledged by any node.
* **SENT**: a node returned the exact transaction id.
* **RELEASED**: a block confirms it.

A holder MUST NOT report a release as published before it is at least SENT; "recorded" is not "published". A holder's resend loop SHOULD surface a persistent "not accepted by the network" condition to the owner-facing side rather than present an unbounded "releasing" state. A holder MUST serialise releases per switch, one in flight per fuse, and MUST pin a release to a single, deterministically chosen funding outpoint; a holder MAY legitimately be funded more than once by the owner, and pinning keeps "the holder publishes once" true under that.

### 10. Reconstruction and authentication

A reader collects releases, reconstructs the data key from `m` shares, decrypts the document, and shows it. Because every byte it collects was written by whoever paid the fee, the reader authenticates before it shows anything.

**Group by header.** A reader MUST group released bundles by their exact header and reconstruct per group, never over a mixed set of shares drawn from different headers.

**Reconstruction is not authentication.** The Shamir step returns a key with no integrity (section 3). The only authentication of a reconstructed key is both of:

* a Merkle path from each used share's leaf to the header `root` (section 3.2); and
* `key_commitment` equal to `SHA256(b"TKDM2/key" || root || data_key || SHA256(sealed_pointer))`, which ties the reconstructed key and the decrypted pointer to `root`.

A reader MUST treat a reconstruction that fails either check as unauthenticated and MUST NOT show its result.

**Anchor to an on-chain document.** A reader MUST anchor every opened switch to an on-chain document whose envelope embeds a `root` equal to the header `root` (section 6), and MUST require that an on-chain arming pays `fuse_script(P, 0)` for the `P` the header's signature verifies against. A bare reconstruction, not anchored to an arming and a document, is unauthenticated.

**Authenticity is the signature, not the timing.** The first on-chain release of a root is not a proof of authorship: once the data key is public, the root, the shares, the paths and the key can all be re-packaged into a different header pointing at different content. A reader MUST verify the release-authorisation signature in the header under the base key `P`, and MUST bind `P` to the on-chain arming (the fuse output the arming pays and the owner spends), rather than to which release was seen first. Block height, and any tie-break on header bytes, are influenceable by an attacker (an unmined release has no height, and a same-block tie is grindable) and MUST NOT be used as an ordering or authenticity oracle.

**Disputed roots.** A reader MUST treat a `root` that appears under two or more distinct `key_commitment` values as disputed: it MUST surface both, or neither, and MUST NOT silently elect one as canonical by height or by byte order. The reader states OPEN, SEALED, COPY, UNREADABLE or DISPUTED for a switch, and DISPUTED is a terminal display state, not a resolved one.

### 11. Discovery and ranking

Releases are discovered through the `TKSHREG1` marker, found by its output script's hash in an index. The marker is permissionless: anyone can publish one, over any transaction. A reader MUST NOT treat the discovery feed as an access-controlled or complete list of switches, and MUST NOT present it as authoritative.

**Version dispatch.** The discovery marker `TKSHREG1` does not encode a version; the release output's first push is the version tag (`TKSH2`). A conforming reader MUST dispatch on that tag, and MUST NOT silently drop a well-formed release whose version it does not implement: it reports such a release instead.

**Partial reads are named, not hidden.** A transaction listed in the feed that a reader cannot fetch MUST be surfaced as a named gap, and MUST NOT suppress the releases that were read. A reader MUST NOT present "I could read only part of the feed" as "there is nothing here". A reader SHOULD draw from more than one source so that one server's refusal is not authoritative, and SHOULD bound the size it will read per listed transaction.

**Saturation is reported.** A window limit truncates the feed by height, and the newest entries can be flooded by an attacker to push older ones out of the window. A reader MUST read newest-first under a stated window policy, and MUST distinguish "read the whole marker history and found nothing" from "read only the newest N", reporting when the window was saturated. On-chain releases remain recoverable by transaction id or a private index, which bounds the harm, but the default view MUST NOT read as empty when it was merely truncated.

**Ranking is advisory.** A reader MAY rank switches by importance, the sum of the satoshis currently locked toward a switch and still unspent. This weight is sound only when each lock's spent state is resolved against a block proof: only block-proven-unspent weight may rank or hide. A lock's weight MUST decay as it is spent, and a reader MUST NOT accept an attacker-chosen expiry height as a way to hold weight indefinitely. Until spent-state resolution is in place, a reader MUST mark importance ranking as unverified and advisory. A listing fee, if any, is advisory and MUST NOT be presented as a spam floor for discovery, because it does not raise the cost of flooding the window.

**Denial-of-service caps.** A reader MUST bound releases read per transaction (`MAX_RELEASES_PER_TX = 64`), switches held per read (`MAX_SWITCHES = 200`), and shares per header group, so that a flooded feed cannot exhaust it.

### 12. Clear metadata bounds

An arming MAY carry clear metadata so a reader can list a switch before it is opened. This metadata is attacker-controlled bytes and MUST be validated on read, against fixed bounds, not only at creation. A reader MUST parse it defensively: metadata that is not a small JSON object shows nothing.

For this format a reader reads three fields and bounds each:

* `title`: coerced to a string and truncated to `TITLE_MAX_CHARS = 200` characters.
* `categories`: a list, each entry coerced to a string, at most 8 kept; anything that is not a list yields none.
* `explicit`: a boolean.

A reader MUST bound the serialised metadata it will parse, so that one arming cannot inject unbounded bytes into every reader's feed, and MUST NOT emit a value whose type or range a consumer could not have anticipated. A reader parsing a release output MUST treat any script too short for its declared pushes as "not a release" and skip it, at per-output granularity, so that one malformed output never discards a transaction's valid release.

### 13. Owner-to-holder messages

The messages an owner sends a holder, and a holder's instructions back, are JSON and MUST be strict \[RFC8259]: `NaN`, `Infinity` and `-Infinity` MUST be rejected, because they are not valid JSON and two parsers disagree on whether a message containing them is valid. The judging side (the holder) MUST reject such a message rather than risk acting on it. A message is at most `MESSAGE_CHARS = 1500` characters.

### 14. Reserved markers and footprint

The markers this switch uses are reserved protocol identifiers. A wallet that builds transactions on behalf of third-party applications or agents MUST refuse any supplied output whose first `OP_RETURN` push is one of the switch's reserved markers, `TKSH2` or `TKSHREG1`, and SHOULD refuse any reserved marker of the wider protocol it implements, so that an application cannot plant a marker that makes an ordinary wallet transaction look like a switch's and defeat the indistinguishability of section 6. The refusal applies when the reserved tag is the first push of an `OP_FALSE OP_RETURN` output.

The footprint of a switch, before it fires, is untagged document chunks shaped like any other data store, an untagged arming that pays P2PKH fuse addresses, and the watched outpoint `F`. The only deliberate on-chain mark a switch ever makes is the `TKSH2` release beside the global `TKSHREG1` marker, after it fires. An implementation MUST NOT promise a per-switch opt-in stealth tag that it does not provide; there is no such tag.

### 15. Summary of requirements

| #   | Role          | Requirement                                                                                                                                                                                                                  |
| --- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| R1  | owner         | MUST present that `m` holders can disclose at any time; the chain does not gate the timing.                                                                                                                                  |
| R2  | owner         | MUST restrict `m = 1` to a holder the owner controls, and SHOULD log an explicit acknowledgement for `m = 1`.                                                                                                                |
| R3  | owner         | MUST build shares, the 32-leaf Merkle tree, `key_commitment`, the sealed pointer and the arming exactly as sections 3, 4 and 6 specify.                                                                                      |
| R4  | owner         | MUST derive fuse keys with the exact tweak preimage of section 5 and MUST reject a tweak of 0 or at least the curve order.                                                                                                   |
| R5  | owner         | A fuse signer MUST use a deterministic nonce (\[RFC6979]) and MUST NOT use a randomized nonce.                                                                                                                               |
| R6  | owner         | MUST gate every block-dependent state change on `CONFIRM_DEPTH` confirmations.                                                                                                                                               |
| R7  | owner         | MUST detect re-encoding by `F`'s outpoint being spent, MUST block-verify the full spending transaction, and MUST drop and re-detect any re-encoding no longer block-proven.                                                  |
| R8  | owner         | MUST segregate the switch's coins on every coin-selection path and MUST refuse to spend a switch output outside the switch's own machinery.                                                                                  |
| R9  | owner         | MUST build a valid arming per the contract of section 6.1 and MUST refuse one that fails any clause.                                                                                                                         |
| R10 | holder        | MUST act only on SPENT or UNSPENT and MUST raise UNKNOWN explicitly; MUST NOT fire on UNKNOWN.                                                                                                                               |
| R11 | holder        | MUST measure absence by address-history completeness to at least `cutoff + TIP_AHEAD`, across at least `MIN_SOURCES` archival sources, cross-checked against its own proof-of-work tip.                                      |
| R12 | holder        | MUST judge fuse `j` against a quorum floored at `cutoff(j)`, never past a later fuse's cutoff.                                                                                                                               |
| R13 | holder        | MUST verify a fetched spender by its transaction-id hash and MUST NOT let a single unservable listing wedge the determination; MUST provide a recovery path.                                                                 |
| R14 | holder        | MUST keep RECORDED, SENT and RELEASED apart and MUST NOT report published before SENT.                                                                                                                                       |
| R15 | holder        | MUST serialise releases per switch and pin each to one deterministically chosen funding outpoint.                                                                                                                            |
| R16 | holder        | MUST build the release outputs exactly as section 9 specifies, header padding all zero, `sig` the release-authorisation signature.                                                                                           |
| R17 | reader        | MUST group bundles by exact header and reconstruct per group.                                                                                                                                                                |
| R18 | reader        | MUST authenticate a reconstruction by both a Merkle path to the header root and `key_commitment`, and MUST treat a bare reconstruction as unauthenticated.                                                                   |
| R19 | reader        | MUST anchor an opened switch to an on-chain document whose envelope root equals the header root and to an arming paying `fuse_script(P, 0)`.                                                                                 |
| R20 | reader        | MUST verify the release-authorisation signature under `P` and bind `P` to the arming; MUST NOT order or elect by height or header bytes.                                                                                     |
| R21 | reader        | MUST treat a root under two or more distinct commitments as DISPUTED and MUST NOT elect a winner.                                                                                                                            |
| R22 | reader        | MUST dispatch on the release tag and MUST handle or report every defined version; MUST NOT silently drop an unimplemented version.                                                                                           |
| R23 | reader        | MUST surface an unfetchable listed transaction as a named gap without suppressing read entries, and MUST NOT treat the feed as complete or access-controlled.                                                                |
| R24 | reader        | MUST read newest-first and MUST report a saturated window rather than show it as empty.                                                                                                                                      |
| R25 | reader        | MUST rank only by block-proven-unspent weight, MUST decay weight by spend, MUST reject an attacker-chosen expiry, and MUST mark unresolved ranking advisory.                                                                 |
| R26 | reader        | MUST bound releases per transaction, switches per read, and shares per group.                                                                                                                                                |
| R27 | reader        | MUST validate clear metadata on read against the bounds of section 12 (title coerced and capped, categories a bounded list of strings, serialised length bounded) and MUST skip an output too short for its declared pushes. |
| R28 | owner, holder | Owner-to-holder messages MUST be strict \[RFC8259] JSON; `NaN`, `Infinity` and `-Infinity` MUST be rejected.                                                                                                                 |
| R29 | wallet        | MUST refuse an application- or agent-supplied output whose first `OP_RETURN` push is a reserved marker (section 14).                                                                                                         |
| R30 | owner, reader | MUST NOT advertise a per-switch stealth tag; the only deliberate mark is the post-fire release beside the discovery marker.                                                                                                  |

## Test Vectors

The vectors below are reproducible: every input is the SHA-256 of the fixed UTF-8 label shown, so a reader can rebuild each value from this document. They were produced by the reference implementation, and the primitives were checked against a second implementation that shares no code with it. Hex is in wire byte order.

The pointer and document are sealed with AES-256-GCM under two fixed associated-data strings:

```
AD_POINTER  = b"TKDM2/pointer-ad"
AD_DOCUMENT = b"TKDM2/document-ad"
```

### Fixed keys

The fuse master private key is `p = SHA256("dead-man timed reveal test vector: fuse master key")`, and `P` is its compressed public key, the key the header's release authorisation verifies against:

```
p = a9f5588dc17c2d9431fc0c23083a37276eaf7a442d3ad14fbaa2e9db692ff005
P = 0263ec1bce2a536b062f970f9824888e0fa5b32d186e85943dfc9e1cfc8dbccd67
```

### 1. Fuse derivation

`tweak(k) = SHA256(b"TKDM2/fuse" || P || uint16_le(k))`; the fuse public key is `P + tweak(k)` on the curve, compressed; the fuse script is `OP_DUP OP_HASH160 <hash160(fuse public key)> OP_EQUALVERIFY OP_CHECKSIG`.

| k   | tweak                                                              | fuse public key (compressed)                                         | fuse script                                          |
| --- | ------------------------------------------------------------------ | -------------------------------------------------------------------- | ---------------------------------------------------- |
| 0   | `2c95be339bc468e8d319168ec1d43595812c623c6f73cce3de4bb007f0ef8451` | `02dd7e2f0d4656d774fdd9ccf372507bf13fb709c0e999414826f51bb37fd24a73` | `76a91476ce359004a1c59eb9bd05007fdc1e52b2383dd688ac` |
| 1   | `2b0d137fc5ba2db59380ecc1c37eb4899db3b045cb6b324e5dd83afc909dbeab` | `02828e60100c0ebb46529421e807443a18ca34e4411d2cf04430be6620e59c78c4` | `76a9149e857cca0d5bbbb8f69fddadda5e7b9d24c7a65e88ac` |
| 999 | `2810ed21b252ac6d05e7200c733964cba988256e24460b0271ac14fe4e80a015` | `035e4c3cbd06b96cc7866191e64514d7958df19ee2e4d384fea6d13231cdc97518` | `76a914bb6c6ee423a465a79958347108f58d2abd6b94bf88ac` |

The private half of fuse 0 is `(p + tweak(0)) mod n = d68b16c15d40967d051522b1ca0e6cbcefdbdc809cae9e3398ee99e35a1f7456`.

### 2. The Merkle tree

The 32 leaves are the leaf hashes of 32 shares, where `share i = byte(i+1) || SHA256("dead-man timed reveal test vector: leaf " || decimal i)` for `i = 0..31`. A leaf is `SHA256(b"TKDM2/leaf" || share)`, and a parent is `SHA256(b"TKDM2/node" || left || right)`.

```
share 0 = 012b0f391bd2c9364ad6c4b5e880c3f8d16253bfd635a47967774f715bc32cdee8
leaf  0 = 2643546ba02c4d57d55ece2681e04d31131a1e9f03c8dd20bba8a9205b46cc22
root    = 4cd0102926c4d162e63336d1633967d99227ac4967f22e77b9b8ba302a32c0f9
```

The path from leaf 0 to the root is the five sibling hashes

```
8837f18e0c6cd25209d0880a2485157a85db87475d9c2d1502904531a0446381
c9587bf46efb00084c72f7ca47735a0c6c82c0a077466aa4c554e74c77f2bfe1
ad8246c99c4462dc386e076ec52d4b5876e5133c68a20229af74e03d7c5f1a9b
b7f0841503671d76465d75791d1236768ecb1671ef50faf3568aacee8569265b
1aec0b1146de7e33a6a73296bd8643c249656a4b467f5879d829bb40369fa22c
```

and folding them upward from leaf 0 reproduces `root`.

### 3. The key commitment

With `root` from section 2, `data_key = SHA256("dead-man timed reveal test vector: data key")`, and `SHA256(sealed_pointer)` the hash of the 540-byte sealed pointer:

```
data_key           = 71377245532a48c16c88348804a28d48f09689e8bba4a750977946097fcf22a6
SHA256(sealed_ptr) = b2b46e4ec38053dc84e3d448441a4694ed76b3f4656551e00a790f0b1d127efc
key_commitment     = 2c2d0e1ee68b123a1b25885541ca9fa7a8dd4aca9d606b6794f09b4cfbf30005
```

where `key_commitment = SHA256(b"TKDM2/key" || root || data_key || SHA256(sealed_pointer))`.

### 4. The release authorisation

With `root` from section 2 and `key_commitment` from section 3, `e = SHA256(b"TKDM2/relauth" || root || key_commitment)`:

```
e   = a44c8fe9c884241c2a93797cff9a1924aeb65beb48cd29477df7cf95f41f255b
sig = 3045022100ff0cdf9512f736e24cc161a7b88cd4d68480d0e903b2a702cb5a81ea2eacf7cc02203b80a7f69bc01b80b2a4ae36d34b8aadf2c00f1057c454b9a104d4ec26c0d93b
```

`sig` is 71 bytes, DER, canonical low-S, over `e` under `p` (\[RFC6979]). It verifies under `P`. The same `sig` does not verify against a `root` with one byte changed, nor under any key other than `P`.

### The shared fixture snapshot

The full sealed pointer (540 bytes), the 872-byte bundle, the release output and the document envelope each carry an AES-GCM nonce and a shuffled 32-leaf layout, so they are not reproducible from fixed inputs. The two reference implementations pin one byte-exact snapshot of them in a shared fixture file generated from the reference, and a reader checks invariants rather than exact bytes: the 137-byte header decodes to `root`, `key_commitment` and a `sig` that verifies under `P` (sections 2 to 4); the bundle's share lies on the tree named by `root`, through its path; `key_commitment` binds the reconstructed key and the decrypted pointer; and the document envelope embeds a root equal to the header `root`. The snapshot also carries two odd-message cases: one owner-to-holder message containing `NaN` and one containing `Infinity`, each rejected by both implementations (section 13).

## Implementations

A reference implementation exists in a wallet that arms, maintains, cancels and withdraws switches, holds shares and publishes releases, and reads and displays releases; and a second implementation, in a different language and sharing no code, judges switches and publishes releases from the holder's side. The two share a fixture file that pins every constant and every byte layout of sections 1, 4, 5, 9 and the firing rule, and they agree on it byte for byte. The firing rule's three-state determination, the quorum, and the per-cutoff judgement were exercised against multiple independent chain sources, including a source that serves only recent history, which the quorum correctly refuses to count as an unspent witness.

## Mathematical and cryptographic basis

This section states each property the format rests on and the assumption it reduces to: the information-theoretic secrecy of Shamir's scheme, the semantic security of AES-256-GCM, the collision and preimage resistance of SHA-256, the existential unforgeability of ECDSA over secp256k1, and the determinism of the signing nonce (\[RFC6979]).

### Confidentiality below the threshold

The document is sealed with AES-256-GCM under the data key, and the data key is split with Shamir's scheme over GF(2^8) at threshold `m`. Any set of fewer than `m` shares is, by the secrecy of Shamir's scheme, statistically independent of the data key: it excludes no value and gives no advantage in recovering it. A coalition of fewer than `m` holders learns nothing of the data key, and the document ciphertext is protected under that key by the semantic security of AES-256-GCM, so an observer of the on-chain ciphertext learns nothing of the document beyond its length. This is a bound on the shares, not on the holders: section 2 states that any `m` holders can open the document whenever they choose.

### Authentication of a reconstructed key

Shamir reconstruction carries no integrity of its own. A wrong, foreign or insufficient set of shares interpolates to some 32-byte value with no error, and a single crafted share steers the result to a value the attacker picks (section 3.1). A reconstructed `data_key'` is authenticated only by two checks together, each reducing to the collision resistance of SHA-256:

* a Merkle path from each used share's leaf to the header `root`, where a leaf is `SHA256("TKDM2/leaf" || share)` and a node is `SHA256("TKDM2/node" || left || right)`, so a share that lies on a given `root` but was not placed there is a SHA-256 collision; and
* `key_commitment = SHA256("TKDM2/key" || root || data_key' || SHA256(sealed_pointer'))`, so a reconstruction with a `data_key'` or a pointer other than the committed ones that still matches the header's `key_commitment` is a SHA-256 collision.

A reader that performs both accepts `data_key'` only as the key the switch's author committed to, with the pointer the author sealed. An interpolation that skips either check is unauthenticated.

### Authorship of a release

Once a switch fires its data key is public, so the root, the shares, their paths and the key can be re-packaged by anyone under a header of their own. The header's release authorisation distinguishes the author's release from such a re-packaging. `sig` is an ECDSA signature over `e = SHA256("TKDM2/relauth" || root || key_commitment)` under the fuse master key `p`, whose public half `P` the arming pays through `fuse_script(P, 0)`. Producing a valid `sig` for a `(root, key_commitment)` under a given `P` is an existential forgery against ECDSA under `P`; a party that does not hold `p` can sign only under a key `P'` of its own, and a reader binds `P` to the on-chain arming, so a release under any key but the arming's `P` is unauthorised. A root that is presented under two distinct `key_commitment` values is surfaced as disputed and is never resolved by ordering (section 10), because a block height and a header-byte tie-break are influenceable by an attacker and are not an authorship oracle.

### Unlinkability and the nonce

Fuse `k`'s key is `P + t_k * G` with `t_k = SHA256("TKDM2/fuse" || P || uint16_le(k))`, and the arming pays each fuse's P2PKH script. The tweaks are a function of `P`, so tying a switch's fuse addresses to their common key needs `P`. Putting `P` inside the tweak preimage is what denies `P` to an observer of a spent fuse: a check-in reveals the fuse public key `P + t_k * G`, and recovering `P` from it would need `t_k`, which needs `P`, a circular dependency behind a hash with no algebraic inversion. The `k` fuses are paid in one arming transaction, so they are grouped by that co-occurrence as any batch payment of distinct-value outputs is; what `P` hides is their derivation from one key and the switch's meaning, not their co-location. A release then discloses `P` to everyone: its header carries `root` and `key_commitment`, so `e` is computable, and ECDSA public-key recovery on the header's `sig` yields `P` (two candidates, resolved by matching `fuse_script(P, 0)` on chain). So the moment one release is mined, anyone can recover `P`, compute every `t_k`, and tie the switch's whole fuse cluster and check-in history to it. The derivation is unlinkable to a common key only until the first release is mined.

The derivation shares one secret `p` across every fuse through tweaks that anyone holding `P` can compute, and this is where the nonce rule earns its MUST (section 5). From two ECDSA signatures that reuse a nonce, which share the value `r`, an observer recovers the nonce and then the signing key by the standard same-key relations (`kk = (z1 - z2) * (s1 - s2)^-1` and `d = (s1 * kk - z1) * r^-1`, both reduced modulo the curve order). Every fuse key is `p + t_k`, so a party that holds `P` (every holder does, and `P` is recoverable from any release) turns a recovered fuse key into `p`, and with `p` every fuse key and the release-authorisation key fall together. A deterministic nonce (\[RFC6979]) makes the nonce a function of the key and the message, so two signatures reuse a nonce only when they sign the same message under the same key, and this recovery cannot arise. A randomized-nonce signer would still produce valid signatures and would be breakable this way, which is why the rule is normative.

### Domain separation

The six hashing contexts are separated by the prefixes `TKDM2/leaf`, `TKDM2/node`, `TKDM2/key`, `TKDM2/pointer`, `TKDM2/fuse` and `TKDM2/relauth`, and the two sealing contexts by the associated-data strings `TKDM2/pointer-ad` and `TKDM2/document-ad`. No value produced in one context is a valid value in another, so a leaf is not read as a node, a commitment is not read as a relauth digest, and a sealed pointer is not read as a sealed document, under the collision resistance of SHA-256.

### What is not cryptographic

The decision that a cutoff has passed (section 8) rests on an observation of the chain, not on a proof. Its safety is the quorum and freshness discipline of section 8, and the firing rule is written so that every outcome short of a proven, caught-up quorum is UNKNOWN, on which a holder never fires.

## Security Considerations

**The timing is social, not cryptographic.** The chain says when an honest holder should release; it cannot stop `m` holders releasing early. The owner's protection is the choice of `m` and of trustworthy holders, and, for `m = 1`, control of the one holder. An implementation that implies otherwise misleads the owner about the one thing that matters.

**A false fire is unrecoverable.** Section 8 is written so that the only way to conclude a cutoff was missed is a quorum that is provably caught up past that cutoff, measured by how complete each source's address history is and checked against the holder's own proof of work. Every weaker signal (a recent-only source, a mempool sighting, a single provider) yields UNKNOWN, and a holder never fires on UNKNOWN. The cost of this is liveness: a hostile source can delay a fire, which section 8 bounds with corroboration and a recovery path, and which is the safe direction to fail.

**Authenticity rests on one signature.** Once a switch fires, its data key is public, so anyone can re-wrap its root into a new header. The release-authorisation signature under the base key `P`, bound to the on-chain arming, is the only thing that tells a genuine release from a re-wrapping. A reader that ranked by block height instead would show an attacker's copy as the original. A root seen under two commitments is disputed, and disputed is where it stays.

**One nonce reuse breaks everything.** The fuse keys and the release-authorisation key all derive from the base secret `p` through additive tweaks, so a single reused ECDSA nonce reveals a fuse key, and to anyone holding `P` the base secret `p` and with it every key the switch has. This is why the deterministic nonce is a MUST and not advice (section 5).

**Everything read is attacker-controlled.** The discovery feed, the clear metadata and the release outputs are all permissionless bytes. The feed is not complete and can be flooded, so a reader reports gaps and saturation rather than showing a truncated view as empty. The metadata is bounded and validated on read. A short or malformed output is skipped, per output, so one bad output does not discard a transaction's valid release.

**Linkage.** An armed switch looks like ordinary activity: untagged chunks, P2PKH fuses, a watched outpoint. The deliberate mark is made only on firing. The owner's own coins stay unlinked to the switch only if segregation holds on every spend path, which is why it is enforced at the coin store and not at one send path (section 7).

**The holders learn little.** A holder is given its share and a watch spec, and no more: not `m`, not `n`, not the document's length or location, which stay inside the sealed pointer until a reconstruction. A stolen share is one of `m` needed; a stolen sealed pointer discloses only its length.

## Privacy Considerations

Before a switch fires, its on-chain footprint carries no mark that identifies it: the document rides in untagged `OP_FALSE OP_RETURN` outputs shaped like any other data store, the arming is a transaction that pays P2PKH fuse addresses and spends the watched outpoint `F`, and none of these carries a reserved marker (section 14). An observer who does not hold the base key `P` cannot tell an arming or a check-in from ordinary wallet activity, and cannot tie a switch's fuse addresses to a common key, though the fuses do co-occur in the one arming transaction like an ordinary batch payment (the Mathematical and cryptographic basis gives the argument). What the chain leaks before a release, to anyone, is the size of the sealed document, from the number and length of its chunk outputs; and, to a party that holds `P`, which is every holder, the cadence of the owner's check-ins, since that party can watch each fuse address for its spend.

A holder is given only its share and the watch spec, and never `m`, `n`, the document's length or location (Security Considerations). Because the watch spec carries `P`, a holder can watch the switch's fuse addresses, so the owner's check-in cadence is visible to every holder. A holder is not anonymous to that cadence, and an owner who needs it hidden should weigh which holders learn `P`.

After a switch fires the release is deliberately public: the `TKSH2` output beside the `TKSHREG1` marker announces the switch, the clear metadata (section 12) is legible to anyone, and the reconstructed document is readable by all. The release also discloses the base key `P` to anyone, through public-key recovery on its header signature (the Mathematical and cryptographic basis), so the switch's whole fuse cluster and the owner's full check-in cadence become publicly linkable at the first release, not only to the prior holders. The format has no setting that keeps a fired switch private, because disclosure is its purpose. The clear metadata an owner writes is published verbatim within the bounds of section 12, so an owner controls how much a listing reveals before a reader opens the document.

## References

* \[SSS] A. Shamir, How to Share a Secret, Communications of the ACM, 1979.
* \[FIPS180-4] NIST, Secure Hash Standard (SHS), FIPS 180-4.
* \[SP800-38D] NIST, Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC, SP 800-38D.
* \[RFC6979] T. Pornin, Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). <https://www.rfc-editor.org/rfc/rfc6979>
* \[RFC8259] T. Bray, The JavaScript Object Notation (JSON) Data Interchange Format. <https://www.rfc-editor.org/rfc/rfc8259>
* \[BRC-42] BSV BRCs, BRC-42: BSV Key Derivation Scheme.
* \[ElectrumX] An index that exposes an output script's history by the hash of the script.


# 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 unsent message 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](/apps/0190), 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](/apps/0190)
* [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 specification 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 specification 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. For `ECDSA-secp256k1`, `payloadHash` is the message digest *H* of SEC 1 §4.1.3: it is read as a big-endian integer and is **not hashed again**. Some ECDSA libraries hash their input by default, and some Bitcoin libraries can read the digest as little-endian; either produces a signature over a different value, which conformant verifiers reject.

### 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).

  The leaf datum `d` for each proof is its 32-byte **`proofHash`**, so a leaf is `SHA256(0x00 ‖ proofHash)` — that is, `SHA256(0x00 ‖ SHA256(canonicalBytes))`, not `SHA256(0x00 ‖ canonicalBytes)`. Both are sound, but they produce different roots, so the choice is stated rather than left to RFC 6962's generic `d`. Leaves are in the order the batch was assembled, and `leafIndex` in the certificate's `merkle` object is that position, counted from 0.

### Certificate

A self-contained JSON object, canonicalised via [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) for hashing and transport, carrying the twelve required fields below. Hex is written **lowercase, without a `0x` prefix**; a reader MAY accept upper case and the prefix. Numbers are JSON integers. Verifiers ignore members they do not recognise, which is how the SPV envelope is added to a certificate already issued.

| field           | value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`      | the string `"NotaryHash"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `version`       | the string `"1.0"`: the certificate format version. It corresponds to `u8(version=1)` in the canonical proof bytes and the on-chain record, and to the domain separator `"NotaryHash/1.0"`, but is written as a string. A verifier MUST reject a certificate whose `version` it does not implement.                                                                                                                                                                                                                          |
| `mode`          | `"full"` or `"hybrid"`: how the proof is recorded on chain, corresponding to the on-chain `mode` byte `0` or `1`. Batching is marked by `anchor.type`, not by `mode`.                                                                                                                                                                                                                                                                                                                                                        |
| `algorithm`     | an identifier from §Algorithms, e.g. `"ECDSA-secp256k1"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `hashAlgorithm` | `"SHA-256"`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `payloadHash`   | the 32-byte payload hash, hex                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `publicKey`     | the **full** public key in every mode (hybrid puts only its SHA-256 on chain), written per `encoding`                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `signature`     | the **full** signature, as the signer produced it, in every mode, written per `encoding`                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `encoding`      | how `publicKey` and `signature` are written: `"hex"`, or `"base64"` (RFC 4648 §4: standard alphabet, padded). It applies to those two fields only, and says nothing about the format of the bytes themselves. A reader MUST reject a value that is not valid in its encoding rather than decode what remains of it — for base64, a character outside the RFC 4648 §4 and §5 alphabets, padding anywhere but the end, or a length no byte string encodes. A reader MAY accept the §5 (URL-safe) alphabet and missing padding. |
| `proofHash`     | the 32-byte `SHA-256(canonicalBytes)`, hex                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `createdAt`     | `createdAtUnix` as an ISO 8601 UTC timestamp with milliseconds, e.g. `"2026-01-01T00:00:00.000Z"`. The milliseconds are always `000`, because only whole seconds enter the canonical bytes; a verifier recovers `createdAtUnix` as the whole seconds the timestamp denotes. Advisory only — see §Verification.                                                                                                                                                                                                               |
| `anchor`        | the object below                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |

For `ECDSA-secp256k1`, `publicKey` is 33 bytes (compressed) or 65 (uncompressed), and `signature` is 64 bytes (`r ‖ s`, each a 32-byte big-endian integer) or DER. A verifier reads a 64-byte `signature` as `r ‖ s`, and any other `signature` as DER, which must parse as DER. DER can itself be 64 bytes long — only when `r` and `s` are unusually short — and is then read as `r ‖ s` and does not verify; a signer holding such a signature sends it as `r ‖ s` instead. `S` is not normalised: the certificate commits, through `proofHash`, to the exact bytes the signer produced, so the malleated form of a signature is a different certificate rather than a forgery of this one.

`anchor` locates the on-chain record:

| member        | value                                                                                                                                                                                                                                                                               |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`        | `"direct"` if the record carries this proof (`mode` `0` or `1`); `"batch"` if it carries a Merkle root (`kind = 2`)                                                                                                                                                                 |
| `network`     | the chain the anchoring transaction is on: `"bsv-mainnet"` for BSV mainnet, `"bsv-testnet"` for BSV testnet. Other values are not interoperable. The field is descriptive: which chain the anchor is on is established by the block header the verifier obtains, not by this value. |
| `txid`        | the anchoring transaction's id, hex, in display order: `reverse(SHA256(SHA256(rawTx)))`                                                                                                                                                                                             |
| `vout`        | the index of the `OP_RETURN` output within that transaction                                                                                                                                                                                                                         |
| `blockHeight` | the height of the block that mined the transaction, or `null` until it is mined                                                                                                                                                                                                     |
| `blockTime`   | that block's timestamp in Unix seconds, or `null` until it is mined                                                                                                                                                                                                                 |

A batched certificate (`anchor.type` `"batch"`) additionally carries `merkle`, the proof that this proof is one of the batch's leaves:

| member      | value                                                                                                                                                                                                                                                                                                                                                          |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root`      | the 32-byte batch root, hex; equal to `merkleRoot` in the on-chain batch record                                                                                                                                                                                                                                                                                |
| `leafIndex` | this proof's position in the batch, from `0`                                                                                                                                                                                                                                                                                                                   |
| `leafCount` | the number of proofs in the batch; equal to `leafCount` in the on-chain batch record                                                                                                                                                                                                                                                                           |
| `path`      | the audit path, leaf → root, as an array of `{ "hash": <32-byte hex>, "side": "left" or "right" }`. `side` is the sibling's position relative to the running hash. Starting from `SHA256(0x00 ‖ proofHash)`, a `"left"` sibling folds as `SHA256(0x01 ‖ hash ‖ running)` and a `"right"` one as `SHA256(0x01 ‖ running ‖ hash)`. The result must equal `root`. |

In a batched certificate `mode` is not checked against the chain: the batch record carries neither the proof nor a mode byte.

A direct-anchored ECDSA certificate, before its SPV envelope is attached:

```json
{
  "protocol": "NotaryHash",
  "version": "1.0",
  "mode": "full",
  "algorithm": "ECDSA-secp256k1",
  "hashAlgorithm": "SHA-256",
  "payloadHash": "97ef50e782e55cfbfbfb0c6199b96837836b7f7b0fcae76f79a65e2466dc596b",
  "publicKey": "02375ac16df62a74475844721d6a180927f29314c3455eaa699f7dcf5237c36e52",
  "signature": "e65171edb82a702a8390fb90f005ba3d4e174ab945ab0bcaf5f1f8d18c3547426ca419ea252b142e863d2eeebdfc787a624bed5a06d23a671959b07b5cb12ba3",
  "encoding": "hex",
  "proofHash": "1b34fbeb640e63b366751a279aa749e4f279cfb7e1add1a96150d7fd7c2ff05d",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "anchor": {
    "type": "direct",
    "network": "bsv-mainnet",
    "txid": "460d19b875036e41d6dea392bfcb84508120e7b573080c45869eedbae39dec6a",
    "vout": 0,
    "blockHeight": 900000,
    "blockTime": 1767225600
  }
}
```

The `merkle` member of the last certificate in a five-proof batch:

```json
{
  "root": "abb53eb3b2e3530d51685c6813bb85c85892d2f214c2dc504029d071434ac074",
  "leafIndex": 4,
  "leafCount": 5,
  "path": [
    { "hash": "060ca4e4bbcb647ab0162bb027cd8f08c1577aaa7069166dd629a3df7e57befd", "side": "left" }
  ]
}
```

The anchoring transactions and blocks in these examples are test fixtures, not mainnet data. Everything else in them verifies: the signature, `proofHash`, the on-chain record in the transaction, and the batch inclusion.

#### 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.

`rawTx` is hex, and `blockHash` is hex in display order, like `txid`. In the `"TSC"` format the entries of `merkleProof.nodes` are hex in display order too, and an entry of `"*"` means "duplicate the working hash", the TSC convention for a missing right sibling. The verifier checks that the header it obtained has hash `spv.blockHash` and height `spv.blockHeight`.

### 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).

The canonical proof bytes are built from six of the certificate's fields — `algorithm`, `hashAlgorithm`, `payloadHash`, `publicKey`, `signature` and `createdAt` — together with the literal domain separator `"NotaryHash/1.0"` and `u8(version=1)`, which are constants of the format rather than values read from the document. Changing the decoded value of any of those six changes the canonical bytes, so the recomputed `proofHash` no longer equals `certificate.proofHash`: step 2 of §Verification catches every one of them, and a certificate whose `proofHash` field is rewritten to agree with the alteration no longer matches the record on chain, which step 3 catches. Step 1 catches only those it reads — `payloadHash`, `publicKey`, `signature`, and `algorithm`, which selects the verification algorithm — so `hashAlgorithm` and `createdAt` are bound by the integrity root alone and not by the signature. Two further fields are bound without entering the bytes as values: `proofHash`, to the canonical bytes by step 2 and to the anchor by the record step 3 reads; and `version`, by its single permitted value in §Certificate together with the requirement there that a verifier reject a `version` it does not implement — the `u8(version=1)` literal is a constant of the format, not a check on the field. `encoding` is a third case: it is not committed to at all. Verification operates on the decoded bytes, so a label rewritten together with the two values it governs is indistinguishable, while a label inconsistent with them is caught by §Certificate's rule that a reader reject a value that is not valid in its encoding, or by step 1.

**Everything else is outside the signed bytes.** `protocol`, `mode`, every member of `anchor`, and the additive `merkle` object and `spv` envelope are absent from the canonical proof bytes, so re-serialising a certificate with any of them altered leaves the signature, the recomputed `proofHash` and the on-chain record untouched: nothing in steps 1–2 of §Verification can detect the change. What binds them instead is step 3 and the field definitions in §Certificate — `anchor.txid` against `reverse(SHA256(SHA256(rawTx)))`; the pushes of the `OP_RETURN` read from `rawTx` against the certificate's fields; `merkle.root` and `leafCount` against the batch record on chain, and `merkle.path` by folding from the leaf to `root`; `mode` against the on-chain mode byte in a direct certificate; `protocol` and `version` against their single permitted values. Each of those is a check the verifier performs against evidence it authenticates itself, not a property the signature carries, and some members have no such check at all. That `network` is descriptive, and that the SPV envelope is outside the canonical bytes, are instances of this rather than exceptions to it.

A verifier MUST NOT report a certificate as valid while leaving a member that this document defines in that region both unexamined and unreported. For each of them it either performs the applicable check, refuses the certificate, or states in its result that the member was not checked; a verifier that silently ignores `spv.format`, `anchor.network` or `anchor.type` reports a verdict its caller will reasonably read as covering them. A result MAY still be valid while naming members it did not check — what it may not do is stay silent about them. The obligation is to the members defined here, which leaves intact §Certificate's rule that verifiers ignore members they do not recognise, and is what lets a later revision add a member without invalidating a certificate already issued. §Certificate already follows this practice once, for `mode`, by saying plainly that in a batched certificate it is not checked against the chain — that is the pattern, applied to the rest of the region. Where a claim cannot be checked because the verifier lacks the means — no header, no chain source, no mode byte on chain — the result for that member is *indeterminate*, which is distinct from both valid and invalid: "I could not look" is not "the answer is no".

### 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.
* A certificate is two documents in one: a signed proof, and an unsigned description of where that proof is anchored. Only the first is tamper-evident, and not by the signature alone: the signature authenticates the signer's claim about `payloadHash`, and the canonical bytes together with the record on chain cover the rest of it. The second holds only as far as the verifier checked it. Implementations SHOULD enumerate, in their result, which members of the unsigned region they checked.

## 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.

#### Batch-mode test vector

Five proofs, so the tree splits 4/1 and exercises both the largest-power-of-two split and the never-duplicate rule. For `i` from 0 to 4, written as one ASCII decimal digit: private key `SHA-256("BRC-220/batch-vector/key/" + i)` read as a big-endian integer, `payloadHash` `SHA-256("BRC-220/batch-vector/payload/" + i)`, `createdAtUnix` `1767225600 + 86400 * i` (seconds), `algorithm` `ECDSA-secp256k1`, `hashAlgorithm` `SHA-256`, compressed public key, 64-byte `r ‖ s` signature with an RFC 6979 nonce, normalised to low-S. (Two of the five signatures differ without that normalisation, so it is needed to reproduce the values below.)

| leafIndex | proofHash                                                          |
| --------- | ------------------------------------------------------------------ |
| 0         | `83f3f73ebc5146b96cce1ee0b6ed4fd251a933a5332fbe5e5ab80da7920fe2c9` |
| 1         | `29fca10808a3653efbe38abd5f0b926aa09f13bff7847475139a422869729e77` |
| 2         | `3c44828bea3ca01dfcd5305a839ce02cc1e855f5a7c7028bb4931d42dea3aa2b` |
| 3         | `0692e22da9b68634e5384bb012324c140c676d2301e078a67f17e335661a9565` |
| 4         | `1a9c6bc1a6cf8319344ca8c30ca9e9eef8ab3df732748f6be2c824bb84ec0ca9` |

* `merkleRoot` (`d = proofHash`): `abb53eb3b2e3530d51685c6813bb85c85892d2f214c2dc504029d071434ac074`
* audit path for leaf 4 (one node, left sibling): `060ca4e4bbcb647ab0162bb027cd8f08c1577aaa7069166dd629a3df7e57befd`
* root an implementation computes if it uses `d = canonicalBytes` instead: `4a59ade74941e4d6ca4b7c9f610e0a01a0c3f24cfa94efe816e53ef85f99e324`

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)


# Consent Envelope: Verifiable Multi-Party Document Signing with On-Chain Signing Order

Crumbs

## Abstract

This document specifies how several parties sign one or more documents, in a stated order, so that anybody later holding the signed file can establish who signed, in what order, under what authority and when. They can do so offline, with block headers and nothing else, and without the cooperation of the software or service that coordinated the signing. Four things follow from that, and an e-signature service whose evidence lives in its own database can offer none of them.

**The order is settled by the chain, not by the coordinator.** Each envelope is one on-chain token, spent once per step. Two conflicting histories of the same envelope cannot both confirm, and a party's signature commits to the exact state it was given, so it cannot be moved to another position in the order.

**The coordinator can delay, and can do nothing else.** It relays, funds and broadcasts. It cannot forge a step, reorder steps, fork the envelope or read the documents. Every way a signing can go wrong names the party responsible, in a fixed order of checks.

**Nothing guessable goes on chain.** Documents are committed under a per-envelope salt, so a party holding a standard template cannot search the chain for its use. Step keys are derived per envelope and are unlinkable to their owners unless the owners say otherwise.

**The proof travels with the document.** A signed PDF carries its definition, its step certificates, the observations of every organisation key it relied on, and an SPV proof of the whole history. It verifies after the coordinator, the vendor and the signer's organisation domain have all gone.

These follow from one design decision: **every step is a spend of the previous state, and every signature commits to the outpoint it spends.** The chain then does for signing order what it already does for coins, which is to make a second, conflicting successor invalid rather than disputed.

The document also specifies how an organisation publishes its signing key at its own domain, with a DNS fallback where it cannot change its website; how any email address doubles as a [BRC-169](/peer-to-peer/0169) handle; and how people who already hold a [BRC-100](/wallet/0100) wallet receive signing requests in it and sign with it. No new cryptographic primitive is introduced.

## Motivation

E-signature is a mature market, and its evidence model has not changed since it began. A platform records who opened a link, who clicked, in what order and from which address, in its own database, and issues a certificate of completion describing that record. Verifying a signature means asking the platform, or trusting a PDF it produced. When the platform is retired, acquired, compelled or compromised, the evidence is whatever it says it is.

Blockchain signing products shipped on this chain improve one link of that and leave the rest where it was. They hash the original and the final document, write the hashes on chain, and verify by recomputing them through the vendor's own tool. A timestamped hash proves a document existed. It does not prove who signed it, in what order, or that an authoriser approved before the next signer saw it: those facts are still in the vendor's database. The platform co-signs every document with its own key, so it can impersonate the flow it coordinates. And the raw hash is guessable, so anybody holding a common template can confirm from the chain alone that a given organisation used it.

That is not a criticism of the implementers, who built the strongest thing the available standards supported. Nobody had written down how signing order, identity and authority could be settled by data the verifier already holds, as opposed to the bare fact that a document existed.

There are three obvious responses to that, and none of them works.

Anchoring more hashes does not help. Hashing every audit-log line and anchoring them proves the log existed, not that its entries are true or in the right order. The coordinator writes the log, so it can write a different one and anchor that.

Having every party co-sign one final transaction is no better. It proves that everybody agreed at the end, and that is all it proves. The order is gone. The gate is gone, and an authoriser who approves before a later signer sees the document is the whole point of that role. It also needs every party present at once, which is the thing asynchronous signing exists to avoid.

Replacing the coordinator with a more trustworthy one is the third, and it is the one that sounds most reasonable. A notary, or a qualified trust service provider, is a better custodian than a startup, and in some jurisdictions that difference decides cases. Structurally nothing has moved. The evidence still sits with a single party, and that party can still be wound up, acquired or compelled.

What is left once those three are set aside is a state machine whose transitions the chain orders: one token per envelope, spent once per step, each step signed by the acting party against the outpoint it spends. A coordinator is still needed, to hold the token and pay for the transactions, but it is named in the evidence, it is replaceable, and there is nothing it can do that a verifier would not catch.

## Prior Art

This document exists because the pieces were already here. The step certificate is [BRC-220](/apps/0220)'s, the state token is a [BRC-48](/scripts/0048) PushDrop output, the key derivation is [BRC-42](/key-derivation/0042) and [BRC-43](/key-derivation/0043), the identity certificates are [BRC-52](/peer-to-peer/0052) and [BRC-53](/wallet/0053), key linkage revelation is [BRC-69](/key-derivation/0069) protected by [BRC-72](/key-derivation/0072), domain trust anchors are [BRC-68](/peer-to-peer/0068), handles, messageboxes and delegation are [BRC-169](/peer-to-peer/0169), the encrypted payloads are [BRC-2](/wallet/0002) and [BRC-78](/peer-to-peer/0078), the SPV proof is [BRC-95](/transactions/0095) verified per [BRC-67](/transactions/0067), and the overlay is [BRC-22](/overlays/0022) and [BRC-24](/overlays/0024). Each is cited where it is used, and none of them is restated here.

The one part that is new can be stated in a sentence: **each signature commits to the outpoint of the state it advances.** The rest of this section sorts the borrowed from the new in detail, because anyone deciding whether to implement this needs to know how much of it they have already built.

**Platform audit trails.** The incumbents record a signing ceremony in a database and issue a certificate of completion. Their envelope model (documents, recipients, roles, routing order, fields placed on pages) is the right model, and section 3 adopts it nearly unchanged, because senders and signers already understand it. What they cannot offer is evidence that outlives them. Every substantive difference here is about where the order and the identities are recorded, not about what a sender sees.

**Long-term validation of PDF signatures.** PAdES (ETSI EN 319 142) with long-term validation embeds certificates, revocation data and RFC 3161 timestamps in the PDF so that a signature stays verifiable after its certificates expire. This is the closest existing answer to "verifiable without the vendor", and section 11 deliberately lays its bundle out so that a PAdES signature can sit alongside it. Two things PAdES does not give: an order between independent signers that no party can rewrite (each incremental signature is ordered only by the file, which the last signer controls), and a timestamp authority that is not itself a party to trust. A block header chain stands in for the second, and the state token supplies the first.

**Transparency logs and hash anchoring.** Certificate Transparency ([RFC 6962](https://www.rfc-editor.org/rfc/rfc6962)) makes a log append-only and auditable, and anchoring services commit document hashes to a public chain. Both establish that something existed at a time. Neither establishes an order of actions by different people against one object, and anchoring a raw document hash is the guessability defect the Motivation describes.

**Signed-hash notarisation.** [BRC-220](/apps/0220) already proves that a specific key signed a specific hash, anchored at a block, verifiable offline, with post-quantum options. A step certificate here is a BRC-220 certificate whose payload hash is a step digest, and nothing in BRC-220 is changed. What BRC-220 does not have, and section 5 adds, is a relationship between certificates: two NotaryHash proofs over two signatures say nothing about which came first in a process or whether the second signer saw the first.

**State machines on chain.** [BRC-60](/state-machines/0060) observes that a UTXO spent in sequence is a natural event chain. The state token is that observation applied to a signing workflow, with the addition that the actor of each event is not the token's holder.

**Keys published in DNS.** DKIM ([RFC 6376](https://www.rfc-editor.org/rfc/rfc6376)) publishes a domain's signing keys in TXT records, and hosted services verify domain control the same way. Section 8.3's fallback is that pattern, and it inherits DNS's weaknesses, which the section states.

**Passkeys.** WebAuthn credentials live in secure hardware, and people who would never install a wallet already use them to sign in. Their P-256 keys are not secp256k1 keys. The PRF extension lets a credential release a secret that unlocks a secp256k1 key held encrypted on the device, which section 4.3 uses; section 6.4 covers authenticators without it, at a stated cost.

**What this document contributes.** The primitives above are borrowed. The six results below are not, and each is written so that a reviewer can go to the section named and check it.

**1. Signing order is a property of the chain.** Section 5 makes each step a spend of the previous state. A second successor to the same state is an invalid transaction, not a dispute, so the order a verifier reconstructs is the only order that can exist.

**2. A signature cannot be moved.** Section 6.1 commits every step to the outpoint it spends and to the digest of the step before it. A valid signature replayed into any other position fails verification. The Test Vectors include that negative case.

**3. The coordinator is untrusted for integrity, and its failures are attributable.** Section 7 limits it to delay. Section 15 runs the checks in a fixed order, and the Failure Modes table names, for every way a signing can go wrong, which party is responsible.

**4. The on-chain footprint is not searchable by content.** Section 2 salts every document commitment per envelope. A party holding a candidate document learns nothing from the chain without the salt, which only envelope parties and verifiers they choose ever see.

**5. Verification survives the coordinator, the vendor and the domain.** Section 11 puts everything a verifier needs in the file, and section 8.5 records, at signing time, what an organisation's domain published, so a lapsed or transferred domain does not invalidate a past signature.

**6. An email address and a wallet identity are the same recipient.** Section 9 maps every email address to a handle and lets a person who signs by email later link a wallet, after which requests to that address reach the wallet. A sender does not need to know which kind of recipient they are addressing.

What is genuinely not claimed: the cryptography, the certificate format, the transport, the identity system, the envelope model, which is the incumbents', or any legal signature level, which is a matter for law and is discussed separately below.

## 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.

**Versioning.** The definition, step certificates and evidence bundle each carry a `version` field, and `"1.0"` is the version this document defines.

1. A party MUST reject an object whose `version` has a major component it does not implement, and MUST treat the sender as non-conforming rather than guessing.
2. A party MUST ignore fields it does not recognise and MUST NOT fail on a minor version it does not implement. New fields arrive in minor revisions; the meaning of an existing field does not change within a major version.
3. On-chain records carry a one-byte version, `1` for this document, and a verifier MUST reject any other value.

**Notation.** `H(x)` is SHA-256. `lp(x)` is `u32be(len(x)) || x`. `u8`, `u16be`, `u32be` and `u64be` are unsigned big-endian integers. `JCS(o)` is the [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) canonical form of a JSON object. Hex is lowercase without a prefix. A transaction id is 32 bytes in display order, and an outpoint is `txid || u32be(vout)`, 36 bytes. Every digest is domain-separated by a length-prefixed ASCII tag beginning `consent envelope v1`, and no tag is a prefix of another.

### 1. Terminology

* **Envelope**: one to thirty documents signed under one definition and one state token.
* **Definition**: the private object listing the documents, parties, roles and order. Section 3.
* **Party**: a participant named in the definition, identified by a `partyId`, a 16-bit index unique within the envelope.
* **Sender**: the party that creates the envelope. Party 0.
* **Signer**: a party that agrees to the content.
* **Authoriser**: a party that approves a stage before later stages open. It places no visual signature.
* **Witness**: a party attesting that it observed a named signer act.
* **Delegate**: a party acting for a principal under a [BRC-169](/peer-to-peer/0169) section 9 delegation certificate.
* **Copy recipient**: a party that receives the final documents and MAY acknowledge them.
* **Controller**: the holder of the key that can spend the state token. The sender, unless it delegates the role. Section 7.
* **Coordinator**: software that stores ciphertext, relays messages, and funds and broadcasts transactions. It is usually the controller's delegate. It is replaceable and untrusted for integrity.
* **State token**: the PushDrop output that is the envelope's on-chain state. Section 5.
* **Origin**: the outpoint of the first state token. It is the envelope's identity on chain.
* **Step**: one action by one party, bound to exactly one spend of the state token.
* **Step key**: a secp256k1 key a party derives for one envelope and one party index. Section 4.1.
* **Evidence bundle**: the object embedded in each final document that lets it verify alone. Section 11.
* **Verifier**: anybody checking an evidence bundle.

### 2. Commitments

#### 2.1 Documents

1. The sender's client MUST generate a 32-byte `salt` from a cryptographically secure random source for each envelope. The salt is carried in the definition (section 3) and MUST NOT appear on chain.
2. For each document `i`, with original bytes `D_i`:

```
docCommit_i = H( lp("consent envelope v1 doc") || salt || u16be(i) || H(D_i) )
docsRoot    = H( lp("consent envelope v1 docs") || u16be(n) || docCommit_0 || ... || docCommit_{n-1} )
```

3. A raw `H(D_i)` MUST NOT appear in any on-chain record defined here. Without the salt, `docsRoot` reveals nothing about which document was signed, even to a party holding the candidate.

#### 2.2 The definition

```
definitionCommit = H( lp("consent envelope v1 def") || JCS(definition) )
```

The definition includes the salt, so `definitionCommit` is not searchable either.

#### 2.3 The final documents

When an envelope completes, each document's final bytes `F_i` are the original plus visual signatures and field values, appended as PDF incremental updates, and before the evidence bundle is attached (section 11.2).

```
finalCommit_i = H( lp("consent envelope v1 final") || salt || u16be(i) || H(F_i) )
finalRoot     = H( lp("consent envelope v1 finals") || u16be(n) || finalCommit_0 || ... )
```

### 3. The Definition

#### 3.1 Shape

```json
{
  "protocol": "consent-envelope",
  "version": "1.0",
  "envelopeId": "<32 random bytes, hex>",
  "createdAt": "2027-03-01T09:30:00Z",
  "expiresAt": "2027-03-31T23:59:59Z",
  "salt": "<32 bytes, hex>",
  "documents": [
    { "index": 0, "title": "Partnership agreement", "mediaType": "application/pdf",
      "sha256": "<H(D_0), hex>" }
  ],
  "parties": [
    { "partyId": 0, "role": "sender", "address": "@crumbs@nexus.app" },
    { "partyId": 1, "role": "signer", "address": "anna@acme.example",
      "requirements": { "level": "verified-role",
        "certificates": [ { "type": "<type id, section 6.7>", "certifier": "domain:acme.example",
                            "fields": ["organisation", "role"] } ] } },
    { "partyId": 2, "role": "authoriser", "address": "@cfo@nexus.app" },
    { "partyId": 3, "role": "copy", "address": "board@nexus.app" }
  ],
  "policy": {
    "stages": [
      { "stage": 0, "parties": [1], "threshold": 1 },
      { "stage": 1, "parties": [2], "threshold": 1, "gate": true }
    ],
    "allowDelegation": false,
    "publicDisclosure": "ask"
  },
  "fields": [ { "id": "f1", "partyId": 1, "document": 0, "page": 1, "kind": "signature",
                "rect": [72, 120, 260, 170] } ],
  "payment": null,
  "supersedes": null
}
```

| Field                     | Requirement | Meaning                                                                                                                                                                                  |
| ------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`, `version`     | MUST        | `"consent-envelope"` and `"1.0"`                                                                                                                                                         |
| `envelopeId`              | MUST        | 32 bytes from a cryptographically secure random source. Binds the definition, the keys and the step digests together. It is not the on-chain identity, which is the origin (section 5.2) |
| `createdAt`, `expiresAt`  | MUST        | ISO 8601 UTC, second precision. After `expiresAt` the controller MAY close the envelope with `EXPIRE`                                                                                    |
| `salt`                    | MUST        | Section 2.1                                                                                                                                                                              |
| `documents`               | MUST        | One to thirty entries, `index` ascending from 0                                                                                                                                          |
| `parties`                 | MUST        | At most 64 entries. `partyId` ascending from 0, party 0 the sender                                                                                                                       |
| `parties[].address`       | MUST        | A handle or an email address, used for routing and display only. Identity is established by step evidence (section 6.6), never by `address`                                              |
| `parties[].requirements`  | MAY         | `level` is `email`, `verified-person`, `verified-role` or `qualified`. A `certifier` is a hex public key, or `domain:<fqdn>` meaning the trust key that domain publishes under section 8 |
| `policy.stages`           | MUST        | Section 3.2                                                                                                                                                                              |
| `policy.allowDelegation`  | MUST        | Whether any party may act through a delegate (section 12)                                                                                                                                |
| `policy.publicDisclosure` | MUST        | `never`, `ask` or `allowed`. Governs export of identity evidence outside the envelope (section 11.3)                                                                                     |
| `fields`                  | MUST        | Field placements; may be empty                                                                                                                                                           |
| `payment`                 | MUST        | `null`, or section 13                                                                                                                                                                    |
| `supersedes`              | MUST        | `null`, or the origin of the envelope this one replaces, as `"<txid>.<vout>"`                                                                                                            |

#### 3.2 Order

1. Stages run in ascending order. A stage completes when `threshold` of its listed parties have acted with `SIGN`, `AUTHORISE` or `DELEGATED_SIGN`.
2. Parties within one stage MAY act in any order. A party MUST NOT act in a stage that has not opened, and MUST NOT act twice.
3. `gate: true` marks an authoriser stage. The state machine enforces the order of every stage regardless; `gate` tells interfaces to present the stage as an approval rather than a signature.
4. Copy recipients and witnesses are not listed in stages. A witness acts after the step it witnesses. A copy recipient MAY acknowledge only after `FINALISE`.

### 4. Keys

#### 4.1 Step keys

A party's step key for an envelope is derived per [BRC-42](/key-derivation/0042) and [BRC-43](/key-derivation/0043):

| Parameter      | Value                                   |
| -------------- | --------------------------------------- |
| `protocolID`   | `[1, "consent envelope step"]`          |
| `keyID`        | `<envelopeId hex>-<partyId in decimal>` |
| `counterparty` | `self`                                  |

1. With counterparty `self`, nobody but the party can compute the step key from the party's identity key. The step public key is therefore unlinkable to its owner until the owner reveals the linkage (section 6.6).
2. A [BRC-100](/wallet/0100) wallet obtains the step public key with `getPublicKey({ protocolID, keyID, counterparty: "self", forSelf: true })` and signs with `createSignature({ protocolID, keyID, counterparty: "self", hashToDirectlySign: stepDigest })`. The signature is DER-encoded ECDSA with a low `S`.

#### 4.2 The controller key

The controller key is derived the same way with `protocolID [1, "consent envelope control"]`, `keyID <envelopeId hex>` and counterparty `self`, from the controller's identity key.

#### 4.3 Device-held keys for people without a wallet

1. A signer without a BRC-100 wallet MAY hold an identity key generated on the device and stored encrypted under a secret released by the WebAuthn PRF extension of a passkey. Step keys are derived from it exactly as in section 4.1.
2. A client SHOULD offer to move such a key into a BRC-100 wallet later, through the onboarding surface of [BRC-137](/opinions/0137). Moving the key changes nothing about past steps, which verify against their step public keys.
3. Losing the device stops future signing under that identity. It does not affect any past step.

#### 4.4 Passkeys without PRF

Where the authenticator cannot release a PRF secret, a signer MAY act with the passkey's own P-256 key, using the form of section 6.4. Such a step has no secp256k1 step key, and section 5.4 rule 3 says how it is anchored and what that costs.

### 5. The State Token

#### 5.1 Locking script

The state token is a [BRC-48](/scripts/0048) PushDrop output: ten fields, each pushed with minimal encoding (an empty field or a single zero byte as `OP_0`, a single byte 1 to 16 as `OP_1` to `OP_16`), then five `OP_2DROP`, then the 33-byte controller public key and `OP_CHECKSIG`. Its value MUST be 1 satoshi.

| # | Field              | Encoding                                                                                                                                                                |
| - | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | Tag                | ASCII `consent envelope`                                                                                                                                                |
| 1 | Version            | `u8(1)`                                                                                                                                                                 |
| 2 | `envelopeId`       | 32 bytes                                                                                                                                                                |
| 3 | `stepIndex`        | `u32be`, `0` for the opening state                                                                                                                                      |
| 4 | `state`            | `u8`: `0` open, `1` completed, `2` declined, `3` voided, `4` expired, `5` superseded                                                                                    |
| 5 | `definitionCommit` | 32 bytes                                                                                                                                                                |
| 6 | `docsRoot`         | 32 bytes                                                                                                                                                                |
| 7 | `chainHash`        | 32 bytes, section 5.5                                                                                                                                                   |
| 8 | `progress`         | `u16be(current stage)` then a bitmap of parties that have acted, `ceil(parties / 8)` bytes, party `i` at bit `i mod 8` (least significant first) of byte `floor(i / 8)` |
| 9 | `finalRoot`        | 32 bytes once completed, otherwise empty                                                                                                                                |

#### 5.2 The identity of an envelope

1. An envelope's on-chain identity is its **origin**, the outpoint of the opening state token. It is not `envelopeId`.
2. Two opening transactions carrying the same `envelopeId` are two envelopes. A verifier MUST follow the token from the origin named in the evidence bundle, and a party MUST NOT act on an envelope whose origin it has not been given by the sender.

#### 5.3 Opening

1. The opening transaction creates the state token with `stepIndex = 0`, state open, empty `finalRoot`, and `chainHash_0` per section 5.5.
2. If `supersedes` is set, the opening transaction MUST also spend the superseded envelope's terminal state token, recreating it with state superseded. Only one successor version of an envelope can therefore ever exist.

#### 5.4 Transitions

Every step spends the current state token and creates its successor in one transaction, which MUST contain:

1. Exactly one input spending the current state token, and exactly one output creating the successor with `stepIndex + 1`, its `progress` and `state` being the deterministic result of applying the step to the predecessor under the definition.
2. A [BRC-220](/apps/0220) record in `hybrid` mode whose `payloadHash` is the step's `stepDigest` (section 6.1), and whose public key and signature are the acting party's step key and signature.
3. For a step taken under section 4.4, where the party has no secp256k1 step key, the BRC-220 record instead carries the controller's key and the controller's signature over `stepDigest`. The controller then attests that it anchored the step, not that it took it, and the party's proof is the WebAuthn assertion in the evidence bundle.

Optionally, the transaction carries payment outputs (section 13). Because the token can be spent once, two successors to one state cannot both confirm, and the order of steps is the order of the token's spends.

#### 5.5 The chain hash

```
chainHash_0 = H( lp("consent envelope v1 open") || envelopeId || definitionCommit || docsRoot )
chainHash_k = H( lp("consent envelope v1 chain") || chainHash_{k-1} || stepDigest_k )
```

A verifier recomputes `chainHash` from the ordered step certificates and MUST compare it with the value in each state token.

#### 5.6 Overlay

1. A coordinator SHOULD run a topic manager `tm_consentenvelope` and a lookup service `ls_consentenvelope`, named per [BRC-87](/overlays/0087) and operating as a strict overlay in the sense of [BRC-183](/overlays/0183).
2. The topic manager MUST admit a transaction only if it opens an envelope per section 5.3, or spends an admitted state token, creates exactly one successor whose `stepIndex` is one greater, and carries a well-formed BRC-220 record. It cannot check policy, because the definition is private. Policy is checked by parties before they act (section 6.5) and by verifiers (section 15).
3. Given an origin, the lookup service MUST return the current state outpoint and the ordered history, tracked per [BRC-64](/overlays/0064). A domain MAY advertise its lookup host per [BRC-180](/overlays/0180).

### 6. Steps

#### 6.1 The step digest

```
stepDigest_k = H( lp("consent envelope v1 step")
               || envelopeId
               || origin                      36 bytes
               || u32be(k)                    the successor's stepIndex
               || u8(stepType)
               || u16be(partyId)
               || definitionCommit
               || docsRoot
               || prevOutpoint                36 bytes, the state token being spent
               || prevStepDigest              32 bytes, zero for the first step
               || lp(fieldsHash)              H(JCS(this party's field values)), or empty
               || lp(evidenceHash)            H(JCS(identity evidence, section 6.6)), or empty
               || u64be(signedAt) )
```

Committing to `prevOutpoint` and `prevStepDigest` means a party signs against one specific state. Its signature fails in any other position.

#### 6.2 Step types

| Value | Name               | Taken by                                     | Effect                                                                                   |
| ----- | ------------------ | -------------------------------------------- | ---------------------------------------------------------------------------------------- |
| 1     | `SIGN`             | A signer                                     | Counts toward its stage                                                                  |
| 2     | `AUTHORISE`        | An authoriser                                | Counts toward its stage                                                                  |
| 3     | `WITNESS`          | A witness                                    | None on progress                                                                         |
| 4     | `DECLINE`          | Any signer or authoriser in an open stage    | State declined, terminal                                                                 |
| 5     | `ACKNOWLEDGE`      | A copy recipient, after `FINALISE`           | None on progress                                                                         |
| 6     | `DELEGATED_SIGN`   | A delegate, section 12                       | Counts toward the principal's stage                                                      |
| 7     | `VOID`             | The sender only, before `FINALISE`           | State voided, terminal                                                                   |
| 8     | `FINALISE`         | The controller, once every stage is complete | State completed, records `finalRoot`                                                     |
| 9     | `EXPIRE`           | The controller, after `expiresAt`            | State expired, terminal                                                                  |
| 10    | `TRANSFER_CONTROL` | The controller                               | Locks the successor to a new controller key, named in the field values. None on progress |

Steps taken by the controller (`FINALISE`, `EXPIRE`, `TRANSFER_CONTROL`) carry `partyId` 65535 and are signed with the controller key. A `FINALISE` step's field values are `{ "finalRoot": "<hex>" }`.

`VOID` MUST be signed with the sender's own step key (party 0). A delegated controller cannot void an envelope unless the sender signs the step, whatever the delegation says.

#### 6.3 The step certificate

A step certificate is a [BRC-220](/apps/0220) certificate with `payloadHash` equal to `stepDigest` and an `anchor` pointing at the record in the step's transaction. Its `algorithm` is `ECDSA-secp256k1`, or one of BRC-220's post-quantum identifiers; in the latter case the post-quantum public key MUST be bound to the party's identity key by a certificate in its evidence. It carries one additional member, which a verifier implementing only BRC-220 ignores:

```json
"consentEnvelope": {
  "version": "1.0",
  "origin": "<txid>.<vout>", "stepIndex": 1, "stepType": 1, "partyId": 1,
  "prevOutpoint": "<txid>.<vout>", "prevStepDigest": "<hex>",
  "fieldsHash": "<hex, or empty>", "evidenceHash": "<hex, or empty>", "signedAt": 1803461400
}
```

#### 6.4 The WebAuthn form

A step taken under section 4.4 carries, in place of a secp256k1 signature:

```json
"webauthn": { "rpId": "signing.example", "credentialPublicKey": "<COSE key, base64>",
              "authenticatorData": "<base64>", "clientDataJSON": "<base64>", "signature": "<base64>" }
```

The `challenge` in `clientDataJSON` MUST be `base64url(stepDigest)`. A verifier MUST report such a step as "signed with a passkey, anchored by the controller", never as a step signed by a wallet key.

#### 6.5 Before acting, a party verifies the history

The topic manager cannot check policy, so a controller could advance the token with a step that policy forbids, for example one signed by a key that is not a party's. Every later signature would then commit to a poisoned history.

1. Before producing any step, a party's client MUST obtain the full history from the origin to the current state, and MUST run checks 4 to 7 of section 15 on it. The envelope is open rather than completed at that point, so check 4's requirement that the last state be completed does not apply; every other part of it does.
2. If any check fails, the client MUST refuse to act and MUST tell the user which step failed and which party it names.
3. A party MUST NOT sign a step whose `prevOutpoint` is not the current state token as returned by a lookup service it chose, or by the chain.

#### 6.6 Identity evidence

A step MAY carry an identity evidence object, committed through `evidenceHash`:

```json
{
  "version": "1.0",
  "identityKey": "<33 bytes, hex>",
  "linkage": "<BRC-69 specific key linkage for the step key, encrypted per BRC-72 to each party>",
  "certificates": [ "<BRC-52 certificate, with a BRC-53 keyring for each party>" ],
  "domainObservations": [ "<section 8.5>" ],
  "delegation": "<BRC-169 section 9 certificate, for DELEGATED_SIGN>"
}
```

1. Where a party's `requirements.level` is above `email`, its step MUST carry evidence satisfying every listed requirement.
2. The linkage proves to the envelope's parties that the step key belongs to `identityKey`, without revealing that to the public. A [BRC-94](/key-derivation/0094) or [BRC-97](/wallet/0097) proof SHOULD be attached, so that the linkage is verified rather than asserted.
3. Certificate fields not named in the requirement SHOULD NOT be revealed.
4. The object is stored encrypted to the parties per [BRC-78](/peer-to-peer/0078). Only `evidenceHash` is public.

#### 6.7 Certificate types

Three [BRC-52](/peer-to-peer/0052) certificate types are defined. A type id is `base64(H(utf8(name)))`, and no name carries a BRC number.

| Name                           | Issued by                                                                     | Fields                                                                                      |
| ------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `metanet email certificate v1` | A coordinator that verified control of the address by a one-time code or link | `email`, `verifiedAt`                                                                       |
| `consent envelope role v1`     | An organisation's trust key, section 8                                        | `organisation`, `domain`, `role`, `limitAmount`, `limitCurrency`, `validFrom`, `validUntil` |
| `consent envelope seal v1`     | An organisation's trust key                                                   | `organisation`, `domain`, `purpose`                                                         |

Revocation follows BRC-52's revocation outpoint. A verifier MUST evaluate it as of the block time of the step that used the certificate, not as of verification.

### 7. Control and Relay

1. **Funding.** The controller funds every step transaction. A party never needs coins. A coordinator funding many envelopes MAY draw on pre-funded outputs as in [BRC-227](/apps/0227).
2. **Relay.** A party's client sends the controller its step certificate without the anchor, its encrypted evidence and its encrypted field values. Any alteration breaks the signature over `stepDigest`, so the controller can forward or refuse, and nothing in between.
3. **Liveness.** A controller that refuses to advance a valid step leaves the party holding a signed step it can show it offered. Later steps cannot be produced without later parties' signatures. A controller can delay. It cannot forge, reorder or fork.
4. **Delegated control.** The sender MAY delegate the controller role to a coordinator with a [BRC-169](/peer-to-peer/0169) section 9 certificate whose scope is `consent-controller` and which names the origin. The token is then locked to the coordinator's derived key, and the certificate goes in the evidence bundle. A delegated controller can `FINALISE` and `EXPIRE`, and can `VOID` only with the sender's signed step (section 6.2).
5. **Changing coordinator.** The current controller MAY move the token to a new controller key with a `TRANSFER_CONTROL` step whose field values are `{ "controller": "<33-byte hex key>", "delegation": "<certificate, or null>" }`. A sender that keeps control itself can therefore switch coordinators at any point without touching the parties.
6. **A coordinator that disappears strands the envelope.** If control was delegated and the coordinator is gone, nobody can spend the token. Every step taken so far remains verifiable as a partial history, and the sender can open a new envelope whose definition names the stranded origin in a field. It cannot supersede it, because section 5.3 rule 2 needs the stranded token. A sender for whom this matters SHOULD keep control and delegate only funding and relay.

### 8. Organisation Trust Keys

An organisation proves who it is by publishing a key at its own domain. Everything in this section is optional for an organisation and mandatory for a client that displays an organisation as verified.

#### 8.1 The manifest

1. An organisation publishes its trust key per [BRC-68](/peer-to-peer/0068) at `https://<domain>/manifest.json` under `metanet.trust`. That key is the certifier for `domain:<domain>` in a definition, and the issuer of section 6.7's role and seal certificates.
2. An organisation that issues handles to its people also publishes `metanet.handles` per [BRC-169](/peer-to-peer/0169) section 5.1. Its `resolve` endpoint MAY be a host the organisation does not run, such as a coordinator, so a coordinator can answer handle resolution on the organisation's behalf while the organisation's key remains the certifier.
3. It MAY add a `metanet.signing` object, and MAY advertise its lookup host per [BRC-180](/overlays/0180):

```json
{
  "metanet": {
    "trust": { "name": "Acme Example AG", "note": "Signing and handles for acme.example",
               "icon": "https://acme.example/icon.png",
               "publicKey": "0332430c198776f3fedac1ee3a6ea598d1a67e8fc83b6bc50b8d49a981025cee77" },
    "signing": { "version": "1.0", "seal": null, "rotation": null },
    "overlays": { "ls_consentenvelope": "https://signing.example/overlay" }
  }
}
```

| Field              | Requirement | Meaning                                                                   |
| ------------------ | ----------- | ------------------------------------------------------------------------- |
| `signing.version`  | MUST        | `"1.0"`                                                                   |
| `signing.seal`     | MAY         | A seal public key, also bound by a `consent envelope seal v1` certificate |
| `signing.rotation` | MAY         | `"<txid>.<vout>"` of the latest rotation record, section 8.4              |

#### 8.2 A manifest hosted by somebody else

1. A domain MAY serve `/manifest.json` as an HTTPS redirect to a copy a coordinator hosts. A client MUST follow at most three redirects, all over HTTPS, and MUST treat the final content as the domain's own statement.
2. The coordinator then serves the key. An organisation that hosts its manifest this way SHOULD publish every key change as a rotation record (section 8.4), so that a substituted key is detectable even though it cannot be prevented.

#### 8.3 The DNS fallback

Where an organisation cannot change its website, it MAY publish a TXT record at `_metanet-trust.<domain>`:

```
v=metanet-trust1; k=<66 hex characters, compressed secp256k1 key>; n=<organisation name, percent-encoded>
```

1. A client MUST prefer a valid manifest. If both exist and name different keys, the client MUST treat the domain as unverified and say so.
2. A client SHOULD validate DNSSEC and MUST record whether it did (section 8.5).
3. A domain verified only by DNS MUST be labelled "verified by DNS". It offers no handle resolution, because BRC-169 resolution needs the manifest.

#### 8.4 Rotation records

A key change is an `OP_FALSE OP_RETURN` output whose pushes are, in order:

```
"domain key rotation" | u8(1) | domain | oldKey (33) | newKey (33) | u64be(effectiveAt) | sigOld | sigNew
rotationDigest = H( lp("domain key rotation v1") || lp(domain) || oldKey || newKey || u64be(effectiveAt) )
```

`sigOld` and `sigNew` are DER ECDSA signatures over `rotationDigest` by the old and new keys. A client SHOULD warn when a domain's published key changes without a matching record.

#### 8.5 Domain observations

Domains lapse and change hands, so a past signature must not depend on what the domain publishes at the moment somebody checks it. Whenever a step relies on a `domain:` certifier, the signer's client MUST fetch the domain's key at signing time and include an observation in its evidence:

```json
{ "version": "1.0", "domain": "acme.example", "method": "manifest", "dnssec": null,
  "trustPublicKey": "<hex>", "fetchedAt": "2027-03-02T10:11:12Z",
  "contentSha256": "<H(manifest bytes, or TXT rdata)>", "finalUrl": "https://acme.example/manifest.json",
  "coObservations": [ { "observer": "<identity key>", "signature": "<DER over H(JCS(observation without coObservations))>" } ] }
```

1. The observation is committed through `evidenceHash`, so it is anchored with the step.
2. An observation made by the signer alone is the signer's claim. The coordinator and any counterparty SHOULD fetch the same domain independently and add a co-observation. A verifier MUST report how many independent observers there were.
3. A verifier MUST report the result as "acme.example published this key on 2027-03-02, observed by N parties, anchored in block H". It MAY also fetch the domain now and report agreement or disagreement, which is informative and never decisive.

### 9. Email Addresses as Handles

Every email address is also a handle, so that a sender never needs to know whether a recipient has a wallet.

#### 9.1 Verified domains

If a domain publishes `metanet.handles` (section 8.1), `local@domain` is resolved as `@local@domain` under [BRC-169](/peer-to-peer/0169) section 2.1 rule 7, and a plus-tag in the local part maps to a BRC-169 sub-handle tag. Nothing in this section changes BRC-169.

#### 9.2 Other domains

Where the domain publishes no handles, a coordinator MAY issue a hosted handle in its own namespace:

1. Lowercase the address. Split it at the last `@` into `local` and `domain`. Split any `+tag` off `local`.
2. Replace every character of `local` outside the BRC-169 handle grammar with `-`, append `_` and `domain`, and trim leading and trailing punctuation.
3. If the result exceeds 64 characters, use `e-` followed by the first 26 characters of the lowercase, unpadded base32 ([RFC 4648](https://www.rfc-editor.org/rfc/rfc4648)) encoding of `H(lowercased address)`.
4. The handle is `@<result>[+tag]@<coordinator domain>`, bound to the person's identity key by a `metanet email certificate v1`.
5. A client MUST display the email address and the label "email verified by ". It MUST NOT display such a handle as verified by the email's domain.

A recipient field MUST accept an email address and a handle interchangeably.

#### 9.3 Linking an email address to a wallet

1. A person who signs by email and chooses to sign with a wallet proves control of both: the coordinator verifies the address, and the wallet signs a [BRC-138](/peer-to-peer/0138) proof addressed to the coordinator.
2. The coordinator then issues a `metanet email certificate v1` to the wallet's identity key. Requests later addressed to that email MAY be delivered to the wallet under section 10.3.
3. The person MUST be able to unlink, which revokes the certificate.

### 10. Confidentiality and Delivery

#### 10.1 Encryption

1. The definition and each document are encrypted with AES-256-GCM under fresh random keys. Ciphertext MAY be stored anywhere and is addressed by its hash per [BRC-26](/overlays/0026).
2. Each object key is wrapped for each party that has an identity key with [BRC-2](/wallet/0002), `protocolID [2, "consent envelope document key"]`, `keyID <envelopeId hex>-<object index>`, counterparty the party's identity key.
3. A coordinator MUST NOT receive an unwrapped object key. Releasing a final document only once a condition is met, such as payment, MAY use [BRC-369](/peer-to-peer/0369).

#### 10.2 Parties known only by email, and what that costs

1. For a party with no identity key yet, the sender's client wraps the object keys under a 32-byte secret carried in the URL fragment of the emailed link, `#k=<base64url>`. The fragment is never sent to a server.
2. Once the party has an identity key, its client SHOULD ask the sender's client to rewrap for that key, and SHOULD then discard the link secret.
3. **The confidentiality of a document sent this way is the confidentiality of the recipient's mailbox.** Anybody who can read the email, including the mail provider, can open the documents. A client MUST say so to a sender who addresses a party by email only, and SHOULD offer to wait for that party to establish an identity key before releasing documents.

#### 10.3 Requests to people with a wallet

When a party's address resolves to an identity key, through a BRC-169 handle or a linked email (section 9.3), the sender's client MUST offer delivery as a BRC-169 section 7.2 envelope to the party's messagebox, box `consent_requests`, whose encrypted content is:

```json
{ "type": "consent-request", "version": "1.0", "origin": "<txid>.<vout>",
  "coordinator": "https://signing.example", "role": "signer", "partyId": 1,
  "definitionUrl": "uhrp://...", "definitionKeyWrap": "<BRC-2 ciphertext>",
  "documentUrls": ["uhrp://..."], "documentKeyWraps": ["<BRC-2 ciphertext>"],
  "expiresAt": "2027-03-31T23:59:59Z" }
```

A wallet that recognises `consent-request` SHOULD present it as a notification, and either open the coordinator's signing page or render the request itself. Email MAY be sent in parallel, as the recipient has chosen.

#### 10.4 What a wallet shows before signing

A wallet producing a step MUST show, before it signs:

1. The document titles and the number of pages of each.
2. The sender, as a verified identity or handle per BRC-169 section 2.4, or as "unverified".
3. The role and the step type, in words: "sign", "approve", "decline".
4. The first eight hex characters of `docsRoot`, so that a person comparing two devices can see they are signing the same thing.
5. Any payment the step carries, per section 13.

A wallet MUST NOT sign a `stepDigest` it has not recomputed from the definition and the history.

### 11. The Evidence Bundle

#### 11.1 Shape

```json
{
  "protocol": "consent-envelope", "version": "1.0", "kind": "evidence",
  "origin": "<txid>.<vout>",
  "definition": { "...": "section 3, including the salt" },
  "documents": [ { "index": 0, "finalLength": 70, "finalSha256": "<H(F_0)>" } ],
  "steps": [ "<step certificates, in order>" ],
  "evidence": [ "<per step: identity evidence as exported, section 11.3, or null>" ],
  "beef": "<BRC-95 Atomic BEEF of the last state transaction with every ancestor back to the origin, hex>",
  "controllerDelegation": null
}
```

#### 11.2 Embedding

1. The bundle MUST be attached to each final PDF as an embedded file named `consent-evidence.json`, with `AFRelationship /Data`, so that the file remains PDF/A-3 conformant.
2. It MUST be the last incremental update of the file. `finalLength` is the length of `F_i` before that update, and a verifier hashes exactly the first `finalLength` bytes.
3. A finalising client SHOULD also apply a PAdES signature in the last update before the bundle, so that common PDF readers show the document as signed. That signature is not needed for verification under this document.

#### 11.3 Exporting identity evidence

Identity evidence was encrypted to the parties. A party exporting a bundle:

1. MAY include revealed certificate fields and linkages re-encrypted to a named verifier.
2. MAY include them in clear only if `publicDisclosure` is `allowed`, or is `ask` and the evidence's owner consented at export time.
3. Otherwise includes only what `evidenceHash` commits to, and a verifier reports "identity evidence present, not disclosed".

Including the salt in the bundle is deliberate. Whoever holds the bundle holds the document, so the salt tells them nothing they could not already confirm.

### 12. Delegation

1. A `DELEGATED_SIGN` step carries a [BRC-169](/peer-to-peer/0169) section 9 delegation certificate in its evidence, with scope `consent-sign`, optionally restricted to named origins, template document hashes, roles, a maximum value and currency, and an expiry.
2. The step is valid only if `allowDelegation` is true or the definition names the delegate, the delegation is unrevoked at the step's block time, and the step falls within scope.
3. An autonomous agent acting as a delegate on steps that carry payments SHOULD also be bound by a [BRC-181](/wallet/0181) spend policy.

### 13. Payment at Signature

If the definition's `payment` is `{ "fromParty": 1, "toParty": 0, "amount": 50000, "currency": "BSV" }`, the paying party's step transaction MUST include the payment output, constructed per [BRC-29](/payments/0029) to the payee's identity key, in the same transaction as the state transition. The signature and the payment then confirm together or not at all. A fiat-denominated amount MUST record its rate source and time in the paying party's field values.

### 14. Air-Gapped and Threshold Signing

1. **Air gap.** A signing request MAY cross to an offline device as the object `{ "definition": ..., "history": [...], "display": {...} }` over [BRC-141](/peer-to-peer/0141) or [BRC-225](/peer-to-peer/0225), and the step certificate MAY return the same way. The offline device MUST recompute `stepDigest` itself, per section 10.4's last rule, rather than sign a digest it was handed.
2. **Thresholds.** "Two of three directors" is a stage with `threshold` 2 over three parties, each of whom presents a `consent envelope role v1` certificate. Backup of a signing key MAY use [BRC-140](/key-derivation/0140) shares.

### 15. Verification, in Order

Given a PDF, a verifier performs these checks in this order. Each has its own result code and fails independently of the rest, and the order is what makes a failure attributable to a party.

1. **`E-BUNDLE`.** Extract `consent-evidence.json` and parse it. Reject an unsupported `version`.
2. **`E-DOC`.** Hash the first `finalLength` bytes and compare with `finalSha256`. Recompute `finalCommit_i` and `finalRoot`. Recover `D_i` from the incremental-update structure, or take it as supplied, and recompute `docCommit_i` and `docsRoot`.
3. **`E-DEF`.** Recompute `definitionCommit`.
4. **`E-SPV`.** Verify the BEEF per [BRC-67](/transactions/0067) against block headers the verifier obtained itself. From the origin, follow the token through every spend to the last. Check that each transaction spends its predecessor's token and creates one successor, that each successor's fields match the recomputed values of section 5.1, and that the last state is completed.
5. **`E-STEP`.** For each step `k`, recompute `stepDigest_k` and `chainHash_k`, compare them with the BRC-220 record and the token, verify the signature per section 6.3 or 6.4, and check `prevOutpoint` and `prevStepDigest`.
6. **`E-POLICY`.** Replay the steps against the policy. Every acting party must be entitled to act in the open stage, every threshold must be met, no party may act twice, and nothing may follow a terminal state.
7. **`E-ID`.** For each step whose requirement is above `email`: verify disclosed certificates, their certifiers and their revocation at the step's block time; verify linkages; evaluate domain observations under section 8.5; report undisclosed evidence as such.
8. **`E-DELEG`, `E-PAY`.** Where present.

Each check reports `pass`, `fail` with a reason, `not-disclosed` or `not-applicable`. A document verifies if checks 1 to 6 pass. The time of each step is the block time of its transaction; `signedAt` is advisory.

### 16. What This Does Not Do

This document makes the order, the integrity and the identities of a signing checkable. It does not make a signing **wise**, and it does not settle a dispute about what a contract means. A party can sign something it did not read. A party who signs under duress produces a valid step.

It does not make an email-only signer more than an inbox. A step at level `email` proves that whoever read that mailbox, and held the link secret, acted. Section 10.2 says what that means for confidentiality too.

It does not protect a document from its own parties. Anybody who was given a document key can publish the document.

It does not stop a controller from delaying, and a coordinator that disappears with delegated control strands the envelope (section 7, rule 6).

It does not, by itself, confer any legal signature level. The Legal Considerations section says what it is designed to support.

A client that tells a sender more than this has sold them a guarantee nobody can deliver.

### 17. Not Specified Here

These are recorded so that the next author does not rediscover them, or ship one of them in a form that nothing else can read.

**Batch-only anchoring.** An earlier draft allowed high-volume envelopes to skip the state token and anchor steps in [BRC-220](/apps/0220) batches. Without the token there is no single-spend, so forks are possible, and a conformance claim covering both would be worthless because a verifier could not tell which it had. Acknowledgement at scale is better served by BRC-220 directly.

**Qualified signatures.** A qualified trust service provider's signature can be carried as evidence today. A format for embedding it so that a verifier can evaluate its qualified status needs a profile of ETSI's validation reports, and that is a document of its own.

**Rescinding a completed envelope.** A contract can be terminated, and a new envelope can record that. Nothing here reaches back into a completed one, and nothing should.

**Threshold signatures.** Section 14 counts several signatures. A single signature produced jointly by several keys would hide the signers' number and identities. It needs a threshold signing scheme no BRC specifies.

**Ordered multi-envelope transactions.** Closing several envelopes atomically, for example both halves of an exchange, is possible because two state tokens can be spent in one transaction. Its policy semantics are not specified.

### 18. Relationship to Other Documents

[BRC-220](/apps/0220) owns the signed-hash certificate. A step certificate is one, unchanged, plus an ignored member. A verifier that implements only BRC-220 can check every individual signature and timestamp, and learns nothing about order.

[BRC-169](/peer-to-peer/0169) owns identity, resolution, messageboxes and delegation. Every handle here is resolved by its section 5.7, every request travels in its section 7.2 envelope, and every delegate's authority is a certificate under its section 9. Section 9 of this document adds a coordinator namespace for email domains that publish no handles. It does not change BRC-169, and a BRC-169 revision could absorb it.

[BRC-68](/peer-to-peer/0068) owns the trust anchor at a domain. Section 8 adds a hosted-manifest rule, a DNS fallback, rotation records and observations, which are needed because a signature outlives the domain state it relied on.

[BRC-52](/peer-to-peer/0052) and [BRC-53](/wallet/0053) own certificates and selective revelation. Section 6.7 defines three types and no new mechanism.

[BRC-100](/wallet/0100) owns the wallet interface. Every key operation here is an existing call. Section 10.4 adds what a wallet shows, not what it can do.

[BRC-369](/peer-to-peer/0369) owns conditional key release, and section 10.1 defers to it for documents released on a condition.

The division holds in one direction: those documents are usable without this one, and this one is not usable without BRC-220, BRC-169 and BRC-100.

## What Is Checkable, and What Is Taken on Trust

| Fact                                                        | Who can check it                       | How                                                                           |
| ----------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- |
| The final document is the one signed                        | Anybody holding it                     | `finalSha256` and `finalRoot`, section 15 check 2                             |
| The original document is the one the envelope was opened on | Anybody holding it and the bundle      | `docCommit` and `docsRoot` with the salt, section 2.1                         |
| Each signature is the acting party's step key               | Anybody                                | Section 6.3, against the step public key                                      |
| The steps happened in this order, and no other order exists | Anybody                                | Single-spend of the state token, section 5.4, and `prevOutpoint`, section 6.1 |
| A signature was not moved from another position             | Anybody                                | Section 6.1, and the negative vector below                                    |
| Each step happened no later than its block                  | Anybody with headers                   | Section 15 check 4                                                            |
| The policy was followed                                     | Anybody holding the definition         | Section 15 check 6                                                            |
| A step key belongs to a given identity                      | The parties, and verifiers they choose | BRC-69 linkage with a BRC-94 or BRC-97 proof, section 6.6                     |
| A signer holds a role at an organisation                    | The parties, and verifiers they choose | A role certificate issued by the domain's key, section 6.7                    |
| A domain published a key at signing time                    | Anybody, as a claim by N observers     | Section 8.5                                                                   |
| The coordinator could not read the documents                | The sender                             | Its own client wrapped the keys, section 10.1                                 |

| Claim                                                  | Why it cannot be checked                                                         | What stands in for a check                                                     |
| ------------------------------------------------------ | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| The signer read and understood the document            | No mechanism reaches a mind                                                      | The interface rules of section 10.4                                            |
| The person at the device is the person named           | A key proves possession, not presence                                            | Identity level, passkey user verification, and the certificate's own assurance |
| An email-only signer is who the address suggests       | A mailbox is not a person                                                        | The `email` label, which a client MUST show                                    |
| A domain observation is true                           | Observers can lie, and a domain can serve different content to different clients | Co-observations by independent parties, section 8.5                            |
| A hosted manifest serves the organisation's chosen key | The host controls the bytes                                                      | Rotation records, section 8.4, which detect a swap afterwards                  |
| The coordinator will advance the next step             | A refusal is indistinguishable from an outage                                    | The party keeps its signed step, section 7 rule 3                              |
| The document is legally binding                        | A question of law                                                                | The Legal Considerations section                                               |

**Everything in the first table is checkable from the file and block headers. Nothing in the second is.** The document moves trust about *what happened in what order* into arithmetic, and leaves trust about *who was at the keyboard* exactly where it always was. The identity level is how a verifier learns how much of it is in play.

## What It Buys, and What It Costs

| Party        | Gains                                                                                                  | Pays                                                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| Sender       | Evidence that outlives its vendor; a coordinator it can replace; documents its coordinator cannot read | A transaction per step, a fraction of a cent each, funded by the controller; a definition it must keep, because only parties hold it |
| Signer       | A signature nobody can move, a key nobody else holds, and no coins or wallet needed to start           | A device key or passkey it can lose; a level of identity it must prove where the sender asks                                         |
| Organisation | A verified name on every request, provable authority for its people, one-action revocation of leavers  | A key to guard; a manifest or DNS record that is now a security-relevant artifact                                                    |
| Coordinator  | A service to run and charge for                                                                        | It cannot read documents or rewrite history, so it cannot sell either, and its users can leave at any step                           |
| Verifier     | A complete answer from one file and block headers                                                      | It must obtain headers it trusts, and it sees only the identity evidence it was given                                                |

The coordinator's row is the one to read twice. Every property that makes it untrusted also takes away what an incumbent platform sells: the archive, the audit trail, the lock-in. A coordinator under this document competes on service, which is the intended result rather than a side effect.

One cost is missing from the table because it falls on everybody at once. Every step is public as an event. Its content is not, but the existence, number and timing of steps are, and the Privacy Considerations section says what that discloses.

## Legal Considerations

This section is informative.

The document provides integrity, order, time and identity evidence. It does not confer a legal signature level, which depends on jurisdiction and on facts outside any specification.

With a step key held under the signer's sole control and `verified-person` evidence, a step is designed to support the four requirements of an advanced electronic signature in Article 26 of Regulation (EU) No 910/2014 as amended by Regulation (EU) 2024/1183, and the equivalent level under the Swiss Federal Act on Electronic Signatures. A qualified electronic signature requires a qualified certificate and a qualified signature creation device, which come from a qualified trust service provider, and that provider's signature can be carried in a step's evidence. A block time is not a qualified electronic timestamp. Implementers should take advice for their jurisdiction.

## Security Considerations

**The controller can delay and can do nothing else, provided parties verify before they act.** Section 6.5 is what makes the "nothing else" part true. A client that signs against whatever state it is handed lets a controller poison the history, and every later signature then attests to it.

**Header source.** As in BRC-220, SPV trust reduces to the verifier's block header source. A verifier SHOULD use a proof-of-work-validated header chain or several independent sources.

**Device keys are as strong as the device.** A PRF-unlocked key is as strong as the passkey and the device that holds it. Losing either stops future signing, and changes nothing about past steps.

**A passkey-only step is anchored by the controller.** Under section 6.4 a malicious controller could withhold the step but not forge it, because the WebAuthn assertion is the party's proof. It MUST be reported as such.

**Hosted manifests and DNS.** A host serving an organisation's manifest can substitute the key, and DNS without DNSSEC can be spoofed for one resolver. Rotation records and co-observations detect the first after the fact, and recording DNSSEC status lets a verifier discount the second. An organisation that wants neither risk self-hosts its manifest.

**Phishing.** A signing request is a link arriving by email asking for a signature. Clients MUST render sender identity under BRC-169 section 2.4 and MUST NOT display an organisation as verified without a section 8 check, because "verified via acme.example" is exactly what a phishing message will try to imitate.

**Quantum.** Contracts meant to stay verifiable for decades SHOULD use BRC-220's post-quantum algorithms for their step certificates, with the post-quantum key bound to the identity key.

## Privacy Considerations

An observer of the chain sees, per envelope: an origin, the number of steps, their timing, salted commitments, the 1-satoshi token's path, and step keys that are unlinkable to their owners. Such an observer learns neither the documents nor the parties. Envelopes whose step count is itself sensitive MAY be padded with `ACKNOWLEDGE` steps.

The coordinator sees metadata the chain does not: who is messaged, from which addresses, and when. That exposure is BRC-169 section 7's.

No personal data is written on chain. Regulators may nevertheless treat salted hashes as personal data in some circumstances. A coordinator SHOULD carry out a data protection impact assessment, and SHOULD delete salts and ciphertext it holds on request, which leaves the on-chain record unresolvable.

## Implementations

* The primitives are in the maintained `@bsv/sdk`: BRC-42 and BRC-43 derivation, `ProtoWallet` and `WalletClient` for step keys and signatures, `PushDrop` for the state token, BRC-2 encryption, BRC-52 certificates, BEEF and Merkle path verification. The step key derivation, step signature and state token script in the Test Vectors were each reproduced byte for byte with `@bsv/sdk` `ProtoWallet` and `PushDrop`.
* Overlay hosting is `overlay-express` in `bsv-blockchain/ts-stack`. Messagebox delivery is BRC-33, resolved per BRC-169 section 5.2.
* No implementation is released at the time of writing.
* Every value in the Test Vectors section is reproducible from the formulas in the sections it names, using a SHA-256 implementation and any secp256k1 library.

### Conformance levels

Three levels, each a superset of the one before. An implementation should say which it reaches. "Supports BRC-222" is not a claim a counterparty can act on.

**Verifier.** Extracts a bundle and runs section 15 in order, against headers it obtains itself. This is the whole of what an auditor, a court's expert or a counterparty needs, it holds no keys, and it is where most implementations should stop.

**Signer.** Adds step keys under section 4, the history check of section 6.5, the display rules of section 10.4, and producing step certificates. A wallet reaches this level with no new interface methods.

**Coordinator.** Adds opening, funding and broadcasting transitions, the overlay of section 5.6, encrypted storage and delivery under section 10, and the evidence bundle of section 11. A coordinator MUST also support `TRANSFER_CONTROL`, or it has shipped a dependency its users cannot leave.

Independently optional at every level: organisation trust keys (section 8), email handles (section 9), delegation, payment, air gap and post-quantum algorithms.

### The smallest useful conforming implementation

A verifier is a few hundred lines over `@bsv/sdk`: parse JSON, hash bytes, verify ECDSA, fold Merkle paths, and replay a policy. It needs no network beyond headers, holds no secrets and cannot be wrong in a way that harms anybody but its user. It can be built today, against the Test Vectors below, before any coordinator exists. Until somebody has built one, nothing else here can be checked by the people who would need to check it.

## Failure Modes

Claim 3 of the Prior Art section is that failure here is attributable. This is that claim written out, and it doubles as the error surface an implementation owes its user.

| What went wrong                                  | How it shows                                                           | Detected by                            | Attributable to                                                |
| ------------------------------------------------ | ---------------------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------- |
| The final file was altered after completion      | `finalSha256` does not match                                           | Anybody                                | Whoever supplied the file                                      |
| A different original was substituted             | `docCommit` does not match with the salt                               | Anybody with the bundle                | Whoever supplied the original                                  |
| The definition was altered                       | `definitionCommit` does not match the token                            | Anybody                                | Whoever supplied the bundle                                    |
| A forked history is presented                    | A state token is spent twice, or a presented successor is not on chain | Anybody with headers                   | The controller, or whoever assembled the bundle                |
| A signature was moved to another position        | Signature fails against the recomputed `stepDigest`                    | Anybody                                | Whoever assembled the history                                  |
| A step was signed by a key that is not a party's | Policy replay fails, or linkage does not verify                        | Parties before acting; verifiers after | The controller that admitted it                                |
| A stage was skipped                              | Policy replay fails                                                    | Anybody holding the definition         | The controller                                                 |
| A party's identity requirement was not met       | `E-ID` fails                                                           | Parties and chosen verifiers           | The controller that advanced the step                          |
| An organisation's key changed without a record   | Observed key differs from the published one, no rotation record        | Anybody                                | The domain or its host                                         |
| A certificate was revoked before it was used     | Revocation outpoint spent before the step's block                      | Anybody                                | The signer                                                     |
| The coordinator stopped advancing                | No successor; the party holds a signed step                            | The party                              | The coordinator                                                |
| A wallet signed a digest it did not compute      | Not detectable after the fact                                          | Nobody                                 | The wallet. Section 10.4 makes this a MUST NOT for that reason |

A signature moved from another position, and a step signed by a key that is not a party's, are the only two ways a history can be dishonest, and section 15's order is what tells them apart. Check 5 catches the first before policy is consulted at all. A failure at check 6 with check 5 passing therefore points at one thing only, which is what the controller chose to admit.

## Test Vectors

Every value below was computed from the rules in the sections named. The step key derivation, the step 1 signature and the state token script were independently reproduced byte for byte with `@bsv/sdk` `ProtoWallet` and `PushDrop`. Private keys are `H(utf8("consent envelope vector key " || name))` read as a big-endian integer. Transaction ids marked as fixtures are test inputs, not mainnet data, in the manner of BRC-220.

### Parties

```
crumbs (sender, party 0)     022fa826f910c75641dcadaeb22c7073b656d21ea16145d080b258e132b6e85c36
anna (signer, party 1)       026007b8d3dd6678f910f83771022d386b8e9ddfee84a79cf15ef74957e62551db
cfo (authoriser, party 2)    03e8d7715471399738d792f241f28aae9d249ab5fe3d6b128334cca9ef9765b70a
acme.example trust key, old  0332430c198776f3fedac1ee3a6ea598d1a67e8fc83b6bc50b8d49a981025cee77
acme.example trust key, new  026566e415e2567fa6d1628a95ffc661d4359aed9c15fe42ffd05b086a208fd792
```

### Commitments, section 2

```
envelopeId        = H("consent envelope vector envelope") = 9057a77c1d7c924223b24c4af6858c519fb634ced1df2c71dc87b24d8c0d2428
salt              = H("consent envelope vector salt")     = b8eef93baa1b0d7020b0ef5207fbcb3c98ab6f84710d9ff591213f9868fc1dab
D_0               = "Consent Envelope test vector document 0\n" (40 bytes, UTF-8)
H(D_0)            bf3451faa3cba331caf6cfec3430635f8878c8b23ac9f486276570f0e126d168
docCommit_0       a4ef1d26db65987d748f8921a4bfd9178cc7ff6d83a426f2a415145d392f7f26
docsRoot          256e4b365d802c3913b2f851d47b629877057ce12fc2c4a6d6d7148c183c2caa
```

The definition, in canonical form (829 bytes):

```json
{"createdAt":"2027-03-01T09:30:00Z","documents":[{"index":0,"mediaType":"application/pdf","sha256":"bf3451faa3cba331caf6cfec3430635f8878c8b23ac9f486276570f0e126d168","title":"Test agreement"}],"envelopeId":"9057a77c1d7c924223b24c4af6858c519fb634ced1df2c71dc87b24d8c0d2428","expiresAt":"2027-03-31T23:59:59Z","fields":[],"parties":[{"address":"@crumbs@nexus.app","partyId":0,"role":"sender"},{"address":"anna@acme.example","partyId":1,"role":"signer"},{"address":"@cfo@nexus.app","partyId":2,"role":"authoriser"}],"payment":null,"policy":{"allowDelegation":false,"publicDisclosure":"ask","stages":[{"parties":[1],"stage":0,"threshold":1},{"gate":true,"parties":[2],"stage":1,"threshold":1}]},"protocol":"consent-envelope","salt":"b8eef93baa1b0d7020b0ef5207fbcb3c98ab6f84710d9ff591213f9868fc1dab","supersedes":null,"version":"1.0"}
```

```
H(JCS(definition))  4bc9169cede122b8ad88d6bcfb8463e64873cf12d50ab37b1b4ee0752779932e
definitionCommit    a0984d5718bdb0c5b7ba5cf35c8cd86360b2e74a2ce64e4768f154bac3c2d219
chainHash_0         fbbff36a75950aed8d460ff6b557e6b4cf8e98d6b233988fd71dd527176ae936
```

### Keys, section 4

```
controller invoice     1-consent envelope control-9057a77c1d7c924223b24c4af6858c519fb634ced1df2c71dc87b24d8c0d2428
controller key (crumbs) 03c33d518bc9d54f47f3ba538053edbf8609c314da770715561592083eae94ca1c
step key, party 1      038a86a19b251483297209914553530847176537bd4acf3d782bf080c0cd3bf6cc
step key, party 2      02a0fe8cfb363ff086a56216fce7901e0e6b0043ab6955fabd0c8a44bef40219b7
```

The party 1 invoice number is `1-consent envelope step-<envelopeId hex>-1`.

### Steps, sections 5 and 6

```
origin (fixture)            25529c8906518496d9cd6959c681edd157f815c423a7f2b22fa7772628004a8f.0
step 1 tx (fixture)         669f6807fd7d4b781714f3711a7322380d484633ba2b450d212e02bddb449112

step 1: SIGN by party 1, prevOutpoint = origin, prevStepDigest = 32 zero bytes,
        fields {"f1":"Anna Example"}, no evidence, signedAt 1803461400
fieldsHash                  3f42418f22909602af3def9decd18db1fe9b10acc4054bae1a8e4d0eb90a19aa
stepDigest_1                7abbb0987d0208deba172349ee062e9fee1d5816b9f1ae2869762ecc3dd8ef58
signature (DER)             3044022067020fd8a79668a8daab6b62ad85f0fed727dac52f6a6f86aad1893cb6eed19802202fc3bdf0b17e0709bf447298416fa22576b297447b3695be2fa27bc26c086b44
chainHash_1                 7fdd3fdaf3b915fccef21f628c70eb25160b5b3bc84d19534f2c46d006019d2a

step 2: AUTHORISE by party 2, prevOutpoint = step 1 tx output 0, prevStepDigest = stepDigest_1,
        no fields, no evidence, signedAt 1803465000
stepDigest_2                3afd6a394496791efb715b4c8b3bc7c7c1c931e9406378082dc8c2fbbb97abbd
signature (DER)             3045022100de07e1a7299bcdd1e43504d83a20e5c87e3c7a10f8c4be7607f1fbb10a5d2cad022051178f4b2ed03d2d289037b98087cedf9f42ddf6e31ab33ee78a4ef1acee87c7
chainHash_2                 2a0f1fe24eeb2e9d1a566ec872931bfeb6f0bfeff500a46e9212467d66ce0dfb
```

The state token after step 1: stage 1, party 1's bit set (`progress` = `000102`), locked to the controller key:

```
10636f6e73656e7420656e76656c6f706551209057a77c1d7c924223b24c4af6858c519fb634ced1df2c71dc87b24d8c0d242804000000010020a0984d5718bdb0c5b7ba5cf35c8cd86360b2e74a2ce64e4768f154bac3c2d21920256e4b365d802c3913b2f851d47b629877057ce12fc2c4a6d6d7148c183c2caa207fdd3fdaf3b915fccef21f628c70eb25160b5b3bc84d19534f2c46d006019d2a03000102006d6d6d6d6d2103c33d518bc9d54f47f3ba538053edbf8609c314da770715561592083eae94ca1cac
```

### Finalisation, section 2.3

```
F_0               D_0 followed by "%% visual signatures appended\n"
finalLength_0     70
H(F_0)            b6d024741503dd3f0f735eae55d5d946e39aab7c74c3729063656626a2b929c9
finalCommit_0     9c39b1382c8cda9a6b35408cd4c3dfb3db41b127fd0050d37697493c262599ef
finalRoot         1e80f5fed64542d6e3be407b1e4a8d3b2adcddf4e5f7933262f72952fc5b1fa5
```

### Certificate type ids, section 6.7

```
metanet email certificate v1   xf7pt2T3EqMkjC972AoXzAyLBItre/RLdTG7LNtQIGs=
consent envelope role v1       0EgYLclY6ywovrAgJdwVfHiQEwiGVNohrcQvY5sVklk=
consent envelope seal v1       ypRm5KC/OLuPgOP8lyAQTL06F5WSigdIibbsmp96ssY=
```

### Rotation record, section 8.4

acme.example rotates from the old to the new trust key, effective 1806000000.

```
rotationDigest    3a32b497f9f11ba220472eb17c5eacb34f23d51008cc25e44f560fdfccde6e21
sigOld (DER)      3045022100fa403ae48ce5761577bca9d219c519ea248d27ab73f0eb2bf54386c494ec9860022025633d6b45f2e80af5e98dfe0401d5f41832a11fc0bf3a162fd79745d3526770
sigNew (DER)      3045022100d9833ace54607b0afd8d90750b5ef6c3c71d35db4def127809a799b9cc070fc902205d3b7318a33c6ca890777e2c7dc304f1375e8843e2f8e22c0e6f29e3a37e84b8
output script     006a13646f6d61696e206b657920726f746174696f6e510c61636d652e6578616d706c65210332430c198776f3fedac1ee3a6ea598d1a67e8fc83b6bc50b8d49a981025cee7721026566e415e2567fa6d1628a95ffc661d4359aed9c15fe42ffd05b086a208fd79208000000006ba55f80473045022100fa403ae48ce5761577bca9d219c519ea248d27ab73f0eb2bf54386c494ec9860022025633d6b45f2e80af5e98dfe0401d5f41832a11fc0bf3a162fd79745d3526770473045022100d9833ace54607b0afd8d90750b5ef6c3c71d35db4def127809a799b9cc070fc902205d3b7318a33c6ca890777e2c7dc304f1375e8843e2f8e22c0e6f29e3a37e84b8
```

### Email handles, section 9

```
anna+legal@acme.example (domain publishes handles)   @anna+legal@acme.example
Anna.Smith@Mail.example                              @anna.smith_mail.example@signing.example
o'brien@example.org                                  @o-brien_example.org@signing.example
a.very.long.mailbox.name.for.testing.purposes@subdomain.department.example.org
                                                     @e-z7ttn3aiwenhyprtbhulwujqfn@signing.example
```

The coordinator domain in these examples is `signing.example`.

### Negative vectors

Each of these is a failure that section 15 has to catch.

1. **A moved signature.** Step 2's signature, presented as if step 2 had spent the origin rather than step 1's output, has `stepDigest` af6097f2dd4c7055176ff38aed34d99562ab2bf780997d1eeca2b5e49765a92b. The signature above does not verify against it. Check 5 fails.
2. **A wrong salt.** `docCommit_0` computed under `H("consent envelope vector wrong salt")` is 1f5cf42e281e4d8093699fca861736266243c19a7e250f342424ee8abb91de2b, which does not match `docsRoot`. Check 2 fails.
3. **A fork.** Any second transaction spending the origin and carrying a valid BRC-220 record is rejected by the chain as a double spend. If presented in a bundle unconfirmed, check 4 fails because no header commits to it.

## Appendix A: An Envelope, End to End

### A.1 The parties

Crumbs sends a test agreement to Anna at Acme Example AG, who must sign it, after which the CFO at nexus.app must approve it. Acme has verified its domain. The CFO uses a BRC-100 wallet. Anna has never used one. The keys and digests are those of the Test Vectors, which omit identity evidence and the copy recipient to keep the values short; this walkthrough adds them back.

### A.2 Opening

Crumbs's client generates `envelopeId` and `salt`, builds the definition above, encrypts it and the document, wraps the keys for the CFO (who has an identity key, resolved from `@cfo@nexus.app`) and for Anna under a link secret (section 10.2), and asks the coordinator to broadcast the opening transaction. The token now holds `chainHash_0`. The coordinator stores ciphertext it cannot read.

### A.3 Anna signs

Anna opens the emailed link. The fragment carries the link secret, so her browser decrypts the definition and the document locally. She confirms with a passkey whose PRF secret unlocks a device key. Her client fetches the history from the origin (one state, no steps), runs section 6.5, derives her step key, computes `stepDigest_1` over the origin and her field value, and signs. Because Acme publishes its key, her client fetches acme.example's manifest and adds an observation; the coordinator adds a co-observation. Anna's evidence carries her role certificate, revealed to the parties only. The coordinator spends the origin and creates the next token with `chainHash_1`.

### A.4 The CFO approves

A `consent-request` arrives in the CFO's messagebox, and the wallet shows it: one document, sender `@crumbs@nexus.app`, role "approve", fingerprint `256e4b36`. The wallet fetches the history, finds one step by party 1 against the origin, verifies it, recomputes `stepDigest_2` against step 1's output, and signs with `createSignature` and `hashToDirectlySign`. The coordinator spends step 1's token.

### A.5 Finalising

Every stage is complete. The controller computes `finalRoot` over the documents with visual signatures appended, takes a `FINALISE` step, and attaches the evidence bundle, with its BEEF, to each PDF.

### A.6 Five years later

The coordinator has closed and acme.example belongs to somebody else. An auditor drops the PDF onto an offline verifier with a header chain. Checks 1 to 6 pass. Check 7 reports Anna's role certificate as not disclosed, because the bundle was exported without consent to publish it, and reports that acme.example published key `0332430c...ee77` at signing time, observed by two parties. Asked for the role, Anna re-exports her evidence to the auditor alone, and check 7 passes.

### A.7 The branch where the coordinator cheated

Suppose the coordinator, after step 1, spends the token with a step signed by a key of its own, claiming to be the CFO. The chain accepts it, because the topic manager cannot see policy. When the real CFO's wallet fetches the history to approve, section 6.5 replays it, finds a step by a key with no linkage to any party, refuses to sign, and names the step and the controller. The envelope is stuck, and the evidence of why is on chain.

## References

* [BRC-2: Data Encryption and Decryption](/wallet/0002)
* [BRC-22: Overlay Network Data Synchronization](/overlays/0022)
* [BRC-24: Overlay Network Lookup Services](/overlays/0024)
* [BRC-26: Universal Hash Resolution Protocol](/overlays/0026)
* [BRC-29: Simple Authenticated BSV P2PKH Payment Protocol](/payments/0029)
* [BRC-33: PeerServ Message Relay Interface](/peer-to-peer/0033)
* [BRC-42: BSV Key Derivation Scheme (BKDS)](/key-derivation/0042)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-48: Pay to Push Drop](/scripts/0048)
* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-53: Certificate Creation and Revelation](/wallet/0053)
* [BRC-60: Simplifying State Machine Event Chains in Bitcoin](/state-machines/0060)
* [BRC-64: Overlay Network Transaction History Tracking](/overlays/0064)
* [BRC-67: Simplified Payment Verification](/transactions/0067)
* [BRC-68: Publishing Trust Anchor Details at an Internet Domain](/peer-to-peer/0068)
* [BRC-69: Revealing Key Linkages](/key-derivation/0069)
* [BRC-72: Protecting BRC-69 Key Linkage Information in Transit](/key-derivation/0072)
* [BRC-78: Serialization Format for Portable Encrypted Messages](/peer-to-peer/0078)
* [BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](/overlays/0087)
* [BRC-94: Verifiable Revelation of Shared Secrets Using Schnorr Protocol](/key-derivation/0094)
* [BRC-95: Atomic BEEF Transactions](/transactions/0095)
* [BRC-97: Extensible Proof-Type Format for Specific Key Linkage Claims](/wallet/0097)
* [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100)
* [BRC-137: Device-Aware Wallet Onboarding and Fallback Login for BRC-100 Applications](/opinions/0137)
* [BRC-138: Single-Use Signed Proofs for Request Authentication](/peer-to-peer/0138)
* [BRC-140: Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme](/key-derivation/0140)
* [BRC-141: Fountain-Coded Air-Gap Transport for Arbitrary Payloads](/peer-to-peer/0141)
* [BRC-169: Universal Handle Addressing and Resolution for the Metanet](/peer-to-peer/0169)
* [BRC-180: Overlay Service Discovery at an Internet Domain](/overlays/0180)
* [BRC-181: Wallet-Enforced Autonomous-Agent Spend Policy](/wallet/0181)
* [BRC-183: A Framework for Strict and Federated Overlays](/overlays/0183)
* [BRC-220: NotaryHash, Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates](/apps/0220)
* [BRC-225: Animated-QR Air-Gap Transport for Arbitrary Payloads (TKQR1)](/peer-to-peer/0225)
* [BRC-227: Frictionless On-Chain Onboarding via Pre-Funded Claimable Tokens](/apps/0227)
* [BRC-369: Keyed Content and Conditional Key Release](/peer-to-peer/0369)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [RFC 4648: The Base16, Base32, and Base64 Data Encodings](https://www.rfc-editor.org/rfc/rfc4648)
* [RFC 6376: DomainKeys Identified Mail (DKIM) Signatures](https://www.rfc-editor.org/rfc/rfc6376)
* [RFC 6962: Certificate Transparency](https://www.rfc-editor.org/rfc/rfc6962)
* [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785)
* [W3C Web Authentication Level 3, including the PRF extension](https://www.w3.org/TR/webauthn-3/)
* ETSI EN 319 142-1: PAdES digital signatures, building blocks and baseline signatures
* ISO 32000-2: Document management, Portable document format, PDF 2.0
* Regulation (EU) No 910/2014 (eIDAS), as amended by Regulation (EU) 2024/1183
* Swiss Federal Act on Electronic Signatures (ZertES), SR 943.03


# 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-226[<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-226[<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-226 §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-226 §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-226) 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-226 (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 coordinate is encoded as exactly 32 big-endian bytes, including leading zero bytes, to create the AES-256-GCM symmetric encryption key. No additional hash of the X or Y coordinate is applied
* 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 returned encrypted value is the concatenation `IV || ciphertext || authenticationTag`, with a 32-byte initialization vector and a 16-byte AES-GCM authentication tag. The client returns this byte array to the application through the wallet interface.

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, encoding its X coordinate as exactly 32 big-endian bytes as the AES-256-GCM symmetric key, without additional hashing
* The recipient separates the first 32 bytes as the initialization vector, the last 16 bytes as the authentication tag and the intervening bytes as ciphertext. Values shorter than 48 bytes are invalid
* The recipient uses the symmetric key to decrypt and authenticate the ciphertext with the provided initialization vector and authentication tag. Authentication failure MUST produce an error; unauthenticated plaintext MUST NOT be returned

### Compatibility Clarification

The X-coordinate key and `IV || ciphertext || authenticationTag` framing above describe the current reference implementation and the existing encryption test vector in this document. Earlier prose incorrectly instructed implementations to hash the shared point's X and Y values and did not explicitly describe the authentication tag. This correction does not change the reference implementation's key derivation or stored ciphertext. Ciphertext created by a different interpretation of the earlier prose is not automatically compatible; applications must identify the construction used before migrating such data, rather than silently changing keys or discarding authentication tags.

### Confidentiality and Key Linkage Disclosure

BRC-2 confidentiality depends on keeping the relevant private keys and shared key-derivation material confidential. [BRC-69](/key-derivation/0069) Method 1 intentionally discloses a counterparty pair's root ECDH shared point. For known identity public keys and derivation labels, that disclosure suffices to calculate this scheme's child ECDH point and encryption key without recovering either private key. A recipient who also possesses the ciphertext can therefore decrypt data under that identity-key pair across protocols and key IDs, including earlier ciphertext. Protocol permissions and a revelation timestamp do not restrict this mathematical capability. The disclosure does not provide shared points for unrelated counterparty pairs.

BRC-69 Method 2 reveals only an individual derivation offset and has a narrower scope under its stated assumptions. Neither method guarantees confidentiality when the needed key material is otherwise known; encryption to `anyone`, whose private key is publicly `1`, MUST NOT be represented as confidential from the public. Protecting a linkage payload in transit under [BRC-72](/key-derivation/0072) protects its delivery to the intended verifier, but does not constrain the verifier's use of the disclosed material.

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 the encrypted byte array `IV || ciphertext || authenticationTag`, with a 32-byte initialization vector and a 16-byte authentication tag.

### 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 `IV || ciphertext || authenticationTag` in the same format as the encryption response.

### 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/sdk` BRC-100 wallet interfaces and wallet permission enforcement. Older Babbage SDK references are retained only for historical context. The maintained [KeyDeriver](https://github.com/bsv-blockchain/ts-stack/blob/3e41d6a220783b268b6240ae654f27628c4af557/packages/sdk/src/wallet/KeyDeriver.ts#L255-L272) uses the child ECDH X coordinate as the key, and [SymmetricKey](https://github.com/bsv-blockchain/ts-stack/blob/3e41d6a220783b268b6240ae654f27628c4af557/packages/sdk/src/primitives/SymmetricKey.ts#L152-L179) serializes the 32-byte IV, ciphertext and 16-byte tag.

## Revision History

* **2026-09-30**: Reconciled the symmetric-key and authenticated ciphertext framing prose with the existing reference implementation and published encryption vector. Clarified BRC-69 disclosure consequences; no reference ciphertext or derivation change is introduced.

## 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 described 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="https://378209567-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWRMRIqu4ZsU4vODzhZwR%2Fuploads%2Fgit-blob-cacef8e1b31ed019e8c580b75db7cc7785a9f6b8%2FWindow_CWI_Diagram.png?alt=media" 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 coordinate is encoded as exactly 32 big-endian bytes, including leading zero bytes, as in [BRC-2](/wallet/0002), to create the HMAC key. No additional hash of the X or Y coordinate is applied
* The resulting 32-byte value is used as the key to compute the HMAC-SHA256 of 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, encoding its X coordinate as exactly 32 big-endian bytes, including leading zero bytes, as the HMAC key, without additional hashing
* The recipient then uses the HMAC key to compute the HMAC of the message, checking that the computed value matches the provided value

#### Compatibility Clarification

The 32-byte X-coordinate key above is the key [BRC-100](/wallet/0100) specifies for HMACs, which derives "symmetric keys as in [BRC-2](/wallet/0002)", and it is the key BRC-2 uses for encryption. Earlier prose here instructed implementations to hash the shared point's X and Y values; no known implementation does that, and this correction does not change any implementation's HMAC for a key whose X coordinate has no leading zero byte.

Implementations differ when the X coordinate begins with one or more zero bytes, about one derived key in 256. Some encode the key as a minimal-length big-endian integer, without those leading zero bytes. HMAC-SHA256 pads a short key with zero bytes on the right, so that key is not the 32-byte key and gives a different tag, which an implementation using the 32-byte key rejects, and the reverse. Implementations that must interoperate with tags already issued in that form may verify a tag against both encodings while such tags remain in use. A value derived from an HMAC that is stored and recomputed later, such as an interactive certificate issuance serial in [BRC-189](/wallet/0189), is only reproducible under the encoding that produced it, so implementations migrating such data must identify the encoding first rather than silently recomputing it under the other.

#### HMAC Test Vectors

These vectors use the BRC-42 key derivation and BRC-43 invoice numbers specified above, for a test root key derived from a public label (never use it for value):

| Item                    | Value                                                                                                                                       |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Root private key        | `f0efba88c117ca2614554bb676d743f413822a31e3c8d70cfcc5fff119d20a07` (SHA-256 of the UTF-8 text `BRC-56 HMAC key test vectors: root`)         |
| Root public key         | `035818cd7cc614fea816c8b39eebe6e43cafc635451ec5a873f8757b418aaae91a`                                                                        |
| Counterparty public key | `03458d551018414b8fe1adbcef2d0afc17e1074bfab3fdde3e1b0750bdde58a9a7` (private key: SHA-256 of `BRC-56 HMAC key test vectors: counterparty`) |
| Protocol ID             | `[2, "hmac key vectors"]`                                                                                                                   |
| Data                    | `4252432d353620484d41432074657374` (the UTF-8 text `BRC-56 HMAC test`)                                                                      |

Each row gives the counterparty, the key ID, how many zero bytes the shared point's X coordinate begins with, that X coordinate (the HMAC key, 32 bytes), the HMAC-SHA256 under that key, and, where it differs, the tag the minimal-length key gives, which is not interoperable with the first.

| Counterparty     | Key ID   | Leading zero bytes | HMAC key (X coordinate)                                            | HMAC-SHA256                                                        | Tag from the minimal-length key                                    |
| ---------------- | -------- | ------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| `self`           | `k0`     | 0                  | `b1273850ff41cee365a4d590e3c323822129f66c5a9b2eacb8b9e790e000e396` | `99455edf49ea2156f6109466fb344c3027d4c14485581a50bf5ff0850328a269` | same                                                               |
| `self`           | `k95`    | 1                  | `000c82ac78dc853158f24883ccfda332be6ba34f821765357cc4ef2a190a28fd` | `054a0fc6a3b0e21b31dc627fb1d645f2eea4127dff71eac95b1f71b7cb13c787` | `3a968ebaa25eaa4b1cfad138f96105bb1eb738defc066d01e37b32595900d48b` |
| `self`           | `k91538` | 2                  | `00009dc11102cc72eca3c6e4e232460eac581e9b2cee1bb35c395b635d3f6393` | `ff43127cb1ddfbd19776d01ce34b8c3e83916b51fa55ac045a355b731e6999f5` | `5a8b84fb2a7d9ce7b12feb8319f14dc6169e0be005a1a8e58d9b9b2d1ac29d47` |
| counterparty key | `k0`     | 0                  | `1896e0210afac8dbb4ca3bd4ea25c8377d0352ab83e62fe6ffabeca78c1a6113` | `986af579272261acf3e890bb1650124b0dfbc0b1edacda202f8ed0c3b876c072` | same                                                               |
| counterparty key | `k497`   | 1                  | `001829e1dd58fc2c45b6edcbb277eecdecbab1c884b42fd779e131fea5881b45` | `aa39467c48a116ffb00fd7b56ff158bd9af37d1298df2f031c0125458c30f574` | `3a5ea2bf5bcc9bc1461041737384bf258b0a3e5a475fb281a19c2972cdb1cff6` |
| counterparty key | `k29510` | 2                  | `0000a52a35523175d6c181e90f463a27cdbf70f4ecf055e912d6fc24ac7d0e3c` | `2677674035f1aaac3219b3405cf8569ec07922a3ceffbd4f7c8a7948b2d3c553` | `ecca769bb17b1775ce8ae4326fe721ba0f7ba272300741bf4a5b068cf0cebd60` |

#### 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 defines 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 specification 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 specification 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 specification 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 specification 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.
* `ExactModuleBalance`: A canonical nonnegative decimal string for BRC-112 P-basket balance queries only; ordinary output counts and non-P balances retain their types.
* `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

/**
 * A BRC-112 P-basket balance: canonical decimal text matching 0|[1-9][0-9]*.
 * Other listOutputs queries retain their existing numeric bounds.
 */
export type ExactModuleBalance = string

/**
 * @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), at most 1000 UTF-8 bytes unless a supported module explicitly defines a scoped exception, such as [BRC-207](./0207.md). Strings exceeding the applicable limit must be rejected.
   * @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<{
    // ExactModuleBalance only for a supported BRC-112 P-basket balance query.
    totalOutputs: PositiveIntegerOrZero | ExactModuleBalance
    BEEF?: BEEF
    outputs: Array<{
      outpoint: OutpointString
      satoshis: SatoshiValue
      lockingScript?: HexString
      // false only in an explicitly defined module inventory view; ordinary lists return true.
      spendable: boolean
      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                       | `1` for ordinary spendable outputs; `0` only for a module inventory record as defined below. |
| `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).                                                               |

For a supported [BRC-112 P-basket balance query](/wallet/0112#exact-module-balance-representation), the `totalOutputs` field instead uses **UTF-8 String** (VarInt byte length plus canonical decimal text), and its API/JSON type is `string`; `outputs` is empty and `BEEF` absent. Codecs select this format from the query contract. Ordinary P-basket output counts and every non-P response retain the formats above.

An explicitly specified BRC-99 module inventory view, such as [BRC-207 asset discovery](/wallet/0207#asset-discovery-and-balance-queries), MAY include actual reserved or pending output records with `spendable:false` (wire Int8 `0`). Ordinary output listings retain `spendable:true` (wire Int8 `1`). The module MUST define the inventory scope and status semantics; listing a non-spendable record never authorizes its consumption. No synthetic summary record may be presented as a Bitcoin outpoint.

#### 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 specification 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. P-basket module balances use the separate exact representation below.

## 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.

### Exact Module Balance Representation

Every supported `p <module> balance <basket>` query MUST use this profile. The module defines its units, eligible records, permissions and tag filters. The result has `outputs: []`, no `BEEF`, and an exact nonnegative aggregate in `totalOutputs`, represented as a **string** matching `0|[1-9][0-9]*` at both the API and JSON boundaries, including zero and small balances. No sign, whitespace, leading zeroes, decimal point or exponent is allowed. There is no uint64 bound on the aggregate; limits on individual outputs remain the module's concern.

In the BRC-100 binary response, this field is a **VarInt byte length followed by UTF-8 decimal text**, using BRC-100's string encoding. It is not a numeric VarInt. The remaining response fields are unchanged. Codecs MUST select this format from the supported query contract, never by guessing from response bytes. Ordinary output counts, including P-basket inventories, and all non-P basket responses retain their existing API types and wire formats.

Compute sums exactly. Unsupported module profiles, incapable adapters or exceeded implementation resource limits MUST produce explicit errors, never rounded, wrapped, saturated or partial balances. Receivers MUST reject noncanonical or wrong-type results and validate lengths before allocation. Implementations MUST document any resource limits; these do not make larger holdings invalid. Adoption requires compatible codecs and validators along the selected connection; this specification does not assert compatibility with earlier P-basket codecs or require an enumeration fallback.

### 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.

For the distinct role of optional protocol, basket and certificate-type descriptions, see the [BRC-184 metadata-registry framework](/wallet/0184). Registry publisher selection is not itself a permission grant or certificate-issuer endorsement. That companion proposal describes fallback and stewardship expectations; it does not change this specification's permission lifecycle.

***

## 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.

## Relationship to descriptive metadata

This scheme-ID registry is distinct from BasketMap, which publishes optional friendly descriptions of basket identifiers. The [BRC-184](/wallet/0184) explains descriptive metadata and its limited stewardship role. A BasketMap listing does not register a `p` scheme, install a permission module or override the module-support rules in this specification.

## 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       |
| --------- | ------------ | ----------------------- | ------------------------------------------------------------ | -------------------- |
| `color`   | 1 — Asset    | [BRC-207](/wallet/0207) | Colored Token custody, authorization and per-asset discovery | `p color <assetTag>` |
| `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 specification 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.

### Related: spend labels ([BRC-165](/tokens/0165))

[BRC-165](/tokens/0165) (P1Sat collectables) uses the bare `<key>` — not the full `id:<key>` tag string — in `createAction` labels of the form `p 1sat input id <key>`. The holding wallet resolves that key by querying tag `id:<key>` under basket `1sat`. Other asset schemes MAY use the same pattern under their own scheme ids. This BRC does not define those labels; it only supplies the list-key vocabulary they reference.

### 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
6. [BRC-165](/tokens/0165) — P1Sat Permission Scheme (spend labels: `p 1sat input id <key>`)


# Wallet-Enforced Expiry for noSend Actions

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines the BRC-111 module label `p nosend expiry` for bounded, prefunded BRC-100 `noSend` actions and dependent action batches. Finite modes reclaim an unpublished root after a deadline. The explicit `never` mode permanently treats released payment value as sent and never reclaims it. The wallet publishes protected actions only on an explicit, authorized `sendWith` request; otherwise a recipient or application may publish them.

Expiry may be expressed as seconds, a Unix timestamp, a block height, or `never`. Two-phase signing and token-aware chained actions use the existing BRC-100 methods. Atomic wallet persistence does not imply atomic network or overlay settlement.

## Motivation

A `noSend` transaction may be handed to an application, recipient, or other broadcaster and remain unbroadcast indefinitely. During that time its inputs are reserved. If ordinary wallet coin selection gave the transaction a large input and expected significant change, most of that input's value can remain unavailable even though only a small payment was intended.

Applications can implement timers, but an application timer does not release the wallet's reservation and cannot invalidate a signed transaction already given to another party. Bitcoin timelocks provide a lower bound on when a transaction may be mined; they do not make a transaction invalid after a deadline.

This specification uses the UTXO model directly. A small, purpose-built funding output bounds the value reserved by the protected action. Spending that output in a reclaim transaction makes the protected action a double spend. No new wallet method, transaction field, or script template is required.

## Normative Language

The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT**, and **MAY** in this document are to be interpreted as described in RFC 2119 and RFC 8174 when they appear in all capitals.

## Specification

### Label namespace

This specification defines BRC-111 module ID `nosend` and the following exact action-label forms:

| Label                                     | Meaning                                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------------------- |
| `p nosend expiry seconds <duration>`      | Expire `<duration>` seconds after activation                                            |
| `p nosend expiry timestamp <unixSeconds>` | Expire at the stated Unix time                                                          |
| `p nosend expiry blockheight <height>`    | Expire when the wallet observes a best-chain height greater than or equal to `<height>` |
| `p nosend expiry never`                   | No deadline or reclamation after release                                                |

An optional exact label `p nosend batch` enables the protected batch rules below. It carries no parameter and may appear at most once. Without it, the single-action no-change isolation rule remains. `never` takes no numeric value. Unknown forms and duplicate lifecycle or batch labels fail before funding.

Examples:

```
p nosend expiry seconds 300
p nosend expiry timestamp 1798761600
p nosend expiry blockheight 1000000
```

The fixed words are lowercase ASCII and separated by one space. Each value MUST be a canonical unsigned base-10 integer: digits only, no sign, and no leading zero unless the value is zero. `duration` MUST be greater than zero. `unixSeconds` is the number of elapsed seconds since 1970-01-01T00:00:00Z, ignoring leap seconds.

A protected root MUST contain exactly one `p nosend expiry ...` label. A batch child MAY omit it and inherit its roots' policies as specified below. A wallet MUST reject malformed forms, unknown expiry modes, duplicate expiry labels, or values outside its safely supported integer and scheduling range. Other ordinary and supported module labels MAY appear on the action.

Per BRC-111, a wallet that does not support module ID `nosend` MUST reject the request rather than treating the label as ordinary metadata.

In a distributed wallet, the component that owns durable action state and deadline monitoring MUST advertise or otherwise establish BRC-177 support before prefunding begins. A client MUST NOT create the funding transaction when its authoritative storage or monitor cannot persist and enforce the protected lifecycle.

### Protected action

An action with a valid expiry label, or a batch child inheriting registered root policies, is a **protected action**. A **root** directly spends dedicated wallet-controlled funding, its **revocation anchor**; children depend on that anchor through their ancestors. A member is **released** once a wallet signature capable of authorizing its spend has left the wallet. A root releases its anchor signature. A transaction that spends a finite root's anchor back to the wallet is its **reclaim transaction**; `never` roots have no reclaim transaction.

Construction MUST set `options.noSend:true` and MUST NOT use `returnTXIDOnly`. `noSendChange` is allowed under the protected batch rules. Construction and publication are separate calls: construction MUST NOT include `sendWith`. A subsequent batch-only `createAction` with `sendWith` may explicitly authorize publication as specified below. An implicit batch commit, an unrelated action, or `signAction` is not publication authorization.

All ordinary BRC-100 validation, spending authorization, and permission checks still apply. This module adds expiry behavior; it does not grant permission to spend.

### Prefunding

Before it returns any signed or signable representation of a protected root, the wallet MUST:

1. Determine the value required by the requested outputs, supplied explicit inputs, and a fee-safe serialization of the protected action.
2. Create a normal funding transaction with one dedicated output controlled by the wallet and tracked in its internal `default` basket. The output MUST fund the protected action under the wallet's current fee and output policies. Finite roots also require an economical reclaim output after its fee; `never` roots require no reclaim-fee allowance.
3. Broadcast the funding transaction immediately and obtain acceptance from at least one transaction processor under the wallet's ordinary immediate-broadcast policy.
4. Construct the protected action with the revocation anchor as its only automatically selected wallet input.

Except for the bounded batch budget below, the protected action MUST contain no automatically generated wallet change. A wallet SHOULD size the anchor to avoid material overpayment and MUST fail if safe isolation is impossible. A root may reuse an already accepted, dedicated funding output with sufficient verified Bitcoin evidence, exclusive ownership and applicable authorization instead of creating new funding. This permits preparation before disconnection; it does not permit an unavailable remote store or unknown funding state to be treated as valid.

The funding transaction itself MAY have ordinary change because it is broadcast before the protected action is released. That change is not reserved by the protected action.

The anchor MUST be reserved exclusively for the protected action until either the protected action is observed on the network or the anchor is reclaimed. The BEEF returned for the protected action MUST contain the funding transaction and the supporting data required by BRC-62 and BRC-100.

When the protected action is finalized, the wallet's signature for the anchor input MUST commit to the complete input and output set, normally with `SIGHASH_ALL`. Caller-supplied inputs MAY still require external unlocking scripts.

If funding acceptance cannot be established, the wallet MUST fail before release. An invalid or conflicting funding transaction invalidates its descendants; the wallet MUST reconcile them rather than present them as available for settlement.

### Protected action batches

These rules specify the public behavior analogous to Wallet Toolbox's action batch planner; storage RPCs, lease lengths, allocation algorithms and manifests are implementation details, not BRC-100 additions.

1. A root has one expiry label and `p nosend batch`. The wallet isolates a dedicated satoshi budget, including any authorized batch fee headroom, and discloses its maximum encumbered value before approval. Only that budget and explicitly designated token/resource inputs may be encumbered. Surplus in unrelated satoshi or fungible-token holdings MUST first be released through an authorized, processed preparation/split, or construction fails. Resources and indivisible rights MUST NOT be split as fungible funding.
2. A child joins by an explicit input or `noSendChange` outpoint referencing a fully signed staged parent, and carries `p nosend batch`. It MAY omit the expiry label and inherit the roots' persisted policies; a supplied label MUST match every inherited policy exactly. Time adjacency, originator equality or an unrelated `noSend` call does not join a graph. Unrelated work remains separate.
3. Parents' signed bytes and TXIDs MUST be fixed before children are signed. Validate a topologically ordered DAG, exact sources, scripts, satoshis, fees, output positions and all applicable token/profile rules. Reject cycles, conflicting representations and duplicate spends within the graph. A changed ancestor invalidates dependent signatures/IDs; reconstruct and reauthorize affected descendants rather than patch their references silently.
4. Bounded wallet-owned satoshi remainder may travel through `noSendChange` within the graph. It remains designated batch funding, outside ordinary available balances. Token dependencies use explicit outpoints and the token module's plan/evidence rules; `noSendChange` does not make tokens generic fee funding. Extra funding requires an accepted dedicated root, consent and graph-wide reservation updates. Joining graphs preserves every root and policy; a `never` promise MUST NOT be made for a descendant of a reclaimable root.
5. Every protected member MUST depend on at least one registered root anchor. Record which members depend on each anchor. Finite roots retain their own immutable deadlines; a descendant expires if any still-unaccepted finite ancestor root reaches its deadline. Evaluate each time or height predicate in its own units; do not numerically compare heights with timestamps. Same-policy graphs may join; mixed policies are allowed only when the caller omits an overriding expiry label and authorizes all effective restrictions.
6. Before returning any signed member, atomically persist its exact bytes, dependency graph, funding/token reservations, consent, root policies and released-signature exposure in authoritative durable storage. In-memory staging MAY optimize planning but MUST NOT be its only record after release. Lease expiry, destruction or restart MUST NOT free an input with a usable released signature. Persisted exact bytes and records must support recovery without requiring the original client session or creating a second payment.
7. `sendWith` requests publication of named retained actions and their unpublished ancestor closure. Before any submission the wallet MUST authenticate that closure, establish publication authority, obtain all applicable consent, check deadlines and reconcile known conflicts. Missing parents, conflicting roots, unsupported profiles or missing authorization fail the request before publishing a ready subset. Imported foreign ancestors may be carried as authenticated transaction evidence; naming an unknown TXID is not an import or authority to publish it. Every independent requested transaction needs its own ordinary authorization; membership cannot silently capture other work.
8. Persist the exact submission intent before external effects. Publish complete consensus-final members in dependency order or through a processor accepting their dependency package. Return normal per-TXID `sendWithResults` for the explicitly requested IDs; retain ancestor outcomes in durable action state. Immediate mode reports the attempted result. Delayed mode durably queues the intent but MUST recheck finite root deadlines before first submission; merely queued work does not cancel reclamation. Non-final members additionally obey BRC-207's processor-coordination contract; this is not permission to bypass it.
9. An atomic storage commit is not all-or-nothing Bitcoin/overlay publication. Track each member's attempted, accepted, admitted, confirmed, failed or uncertain facts separately. An accepted root cancels its reclaim schedule; its descendants may still fail. Resume authorized recovery of that accepted root's descendants after its former deadline: the deadline limits first root publication, not completion of every descendant. Independent finite roots still retain their own deadlines. Never claim rollback of a published prefix.
10. Retries use the same signed bytes and reconcile processor/overlay evidence before resubmission. A lost response is uncertain, not proof of rejection. A winning anchor reclaim invalidates only descendants of that anchor whose root did not win. Release other reservations only after their separate exposure is reconciled. An already spent anchor cannot revoke descendants; independently expiring a later offer requires its own dedicated anchor. A signature usable without that anchor remains separate exposure; do not claim it expired or free its input merely because the anchored graph lost.

Cross-wallet delivery carries complete Bitcoin and applicable token dependency evidence and authenticated lifecycle metadata. The receiver independently validates, authorizes, persists and extends its own graph; it does not share the sender's workspace or inherit the sender's permissions. [BRC-207](/wallet/0207)'s declared attested-admission profile permits token descendants before overlay settlement. Without an applicable profile, ordinary token admission still applies.

### Activation and deadline

The root's expiry becomes active when the wallet durably commits it, immediately before returning the first signed or signable representation to the caller. The wallet MUST persist the label, action reference, anchor outpoint, computed deadline (or `never`), whether an anchor signature has been released, and lifecycle state before returning. It MAY also persist the activation time; the computed deadline remains authoritative. Children persist their root references and inherited absolute deadlines; they neither activate a new duration nor require an already accepted root's former deadline to remain in the future.

For `seconds`, the wallet MUST add `duration` to the activation time and persist the resulting absolute Unix-second deadline. Restarting the wallet MUST NOT restart the duration.

For `timestamp`, the supplied time MUST be strictly later than the wallet's current time at activation. For `blockheight`, the supplied height MUST be greater than the wallet's current best-chain height at activation. If an absolute deadline is no longer in the future after prefunding, the wallet MUST fail without releasing the protected action; the anchor remains ordinary wallet-controlled value.

Finite deadlines are immutable after activation. Extending a deadline or switching between finite and `never` requires a new action and safe resolution of the old exposure. Inherited child references never restart a relative duration.

### Never-expiry and sent accounting

`p nosend expiry never` has no deadline, hidden timeout or reclaim transaction. It is optional support: unsupported wallets MUST reject it before funding and MUST NOT substitute a finite deadline. The authoritative wallet/storage must support this lifecycle, including backup and multi-device synchronization.

Upon release the wallet MUST permanently debit the authorized payment and designated funding as sent/committed, exclude its inputs from ordinary selection, and MUST NOT schedule, retain for execution or submit an anchor reclaim. It MUST NOT reclaim because of inactivity, delivery failure, lease expiry, restart, loss of an application, `abortAction` or a later change in wallet policy. Dependent designated change remains batch-only until actually accepted under the applicable Bitcoin/token rules. It is neither a second debit nor available ordinary change. A verified winning spend may settle or reconcile the recorded facts; reconciliation is not permission to manufacture a reclaim.

This is the wallet's irrevocable disposition policy, not proof of broadcast, mining, overlay admission or protection against another party's conflicting spend. Normal BRC-100 statuses retain their meanings; use durable sent/committed accounting without inventing a `broadcast` result. Explicit authorized publication and status/proof tracking remain available. If no signature capable of consuming the funding was released, cancellation may safely release that funding.

Wallets SHOULD use a clock that is resistant to simple application manipulation. A block-height deadline MUST use the wallet's canonical best-chain tracker. Once a height deadline has triggered, a later reorganization MUST NOT reactivate the protected action.

### `signAction`

A protected `createAction` MAY return `signableTransaction` when caller-supplied inputs require unlocking scripts. The root's expiry is already active; it does not wait for `signAction`. A signable root MUST withhold its anchor signature until `signAction`. A signable child MUST withhold at least one wallet-controlled input signature needed to complete that child; its ancestors' previously released signatures remain exposed. If no such withholding is possible, record the member as released before returning it and apply the same lifecycle protections.

For a protected action, `signAction` MUST:

* inherit the original expiry label and anchor reservation through the BRC-100 action reference;
* retain `options.noSend: true`;
* reject `sendWith` on this signing call, and reject completion at/after a still-applicable finite deadline;
* reject completion after reclamation has begun; and
* preserve the committed input and output set.

Before returning a completed transaction, the wallet MUST durably record release. Finite modes use the reclaim rules below; `never` forbids reclaim. Batch descendants inherit their roots' persisted lifecycle and cannot silently reset it.

The wallet MUST apply the same monitoring and reclamation behavior whether the protected action is completed directly by `createAction` or later by `signAction`, regardless of which caller supplies the non-anchor unlocking scripts.

### Before expiry

Without an explicit authorized `sendWith` publication request, the wallet MUST NOT broadcast or enqueue the protected action. Merely creating, signing or committing a batch does not request publication. Explicit publication follows the same deadline, observation and conflict rules as external publication.

The wallet MUST monitor the protected transaction when its txid is known and MUST monitor the revocation anchor in all cases. For each root independently, if the wallet establishes that root's mining or processing acceptance before expiry, it MUST cancel that root's reclamation and move it into ordinary BRC-100 broadcast/proof tracking. A child-only response, an orphan acknowledgement or overlay admission alone does not establish an unresolved root's processing acceptance. Expiry is a deadline for broadcast, not a deadline for confirmation.

If the anchor is observed spent but the wallet cannot obtain or validate the spending transaction, it MUST stop treating the anchor as reclaimable and investigate through its normal transaction-status process. It MUST NOT report successful reclamation merely because the protected txid is unknown.

### Expiry and reclamation

For a finite root only, when its deadline is reached without observed processing acceptance, the wallet MUST reconcile any in-flight submission before choosing the following actions. It MUST NOT knowingly submit an original and a reclaim concurrently; uncertain external races remain possible. The wallet MUST:

1. Reject further `signAction` and wallet-broadcast requests for the protected action.
2. Check the protected action and anchor through its configured transaction-status services.
3. If no valid anchor signature has ever been released, abort the unsigned action and return the still-unspent anchor to ordinary `default`-basket coin selection. No reclaim transaction is needed because the caller cannot produce a valid spend of the anchor.
4. Otherwise, if the anchor still appears unspent, construct or retrieve a previously signed reclaim transaction that spends it to a fresh wallet-controlled output in the internal `default` basket.
5. Broadcast the reclaim transaction immediately, without delayed-broadcast behavior.
6. Persist and retry reclamation after transient failures until the reclaim transaction is proven, the protected action is observed, or another transaction is proven to have spent the anchor.

The reclaim transaction SHOULD use the anchor as its only input. Its output equals the anchor value less its network fee. Funding and reclamation fees are borne by the wallet owner.

A wallet MAY construct, sign, validate, and durably store the reclaim transaction before it releases a finite root. This permits an authoritative storage monitor without wallet keys to enforce the deadline after a restart or while wallet clients are offline. When a deployment relies on such a keyless monitor, it SHOULD pre-sign the reclaim before release. A pre-signed reclaim MUST remain internal to the wallet or its trusted storage services until reclamation is authorized, and the wallet MUST verify that it spends only the anchor to wallet-controlled value under its recorded fee policy.

Once the reclaim transaction is mined, the protected action conflicts with an already-spent outpoint and is invalid on that chain. Before then, expiry and reclaim broadcast do not create consensus finality: a protected action broadcast at nearly the same time may race the reclaim transaction. Wallets MUST report the transaction that actually wins and MUST NOT describe a pending or merely submitted reclaim as final.

If the protected action is first observed after a reclaim submission may already have occurred, the wallet MUST stop initiating or retrying reclaim submissions but MUST retain proof tracking for both transactions. Neither an observation nor a processor response resolves the race; only the wallet's normally validated proof evidence may expose the winning transaction's outputs as spendable.

Normal proof depth and reorganization handling apply to the reclaim transaction. Applications and recipients SHOULD broadcast with enough margin for the wallet to observe acceptance before the deadline.

### Abort and early revocation

For a released finite root, `abortAction` starts the reclaim process if the root has not been accepted. A child abort MUST NOT silently reclaim a shared ancestor and cancel sibling payments: obtain consent for the complete affected set first, or reject the requested cancellation. Accepted ancestors cannot be reclaimed. For a released `never` action, abort MUST return an explicit error; it cannot revoke the disposition or release its inputs. Unreleased children may be discarded without freeing any still-exposed ancestors. Local cleanup never revokes bytes.

If a member has not been released, `abortAction` MUST permanently prevent `signAction` for that reference. Its reservation may be released only after reconciling all dependent exposure. Returning an anchor to ordinary selection without reclamation is allowed only for an unreleased root whose anchor cannot be spent by any released member; aborting a child never frees an exposed ancestor.

Before activation, an implementation MAY discard an unreleased candidate and return an accepted funding output to ordinary `default`-basket coin selection without creating a reclaim transaction, because no protected transaction has been exposed.

### Persistence and coordination

Lifecycle state MUST survive restarts, backup and synchronization. Multiple devices/storage writers MUST coordinate anchors, graph reservations and submission intents. Every writer enforces the same finite deadline or permanent `never` disposition and the prohibition on unsolicited publication.

Only the authoritative active storage or monitor SHOULD initiate reclamation. Synchronized lifecycle merges MUST NOT regress an action from a state in which expiry or reclamation has already begun merely because another device reports a later wall-clock update. A reclaim output MUST remain unavailable for wallet coin selection until the wallet has validated proof that the reclaim won; synchronization alone is not proof.

This specification introduces no new BRC-100 method or response field and no new public action status. Implementations MAY keep detailed states such as `active`, `broadcast-observed`, `reclaiming`, `reclaimed`, and `conflicted` internally. Existing `listActions` behavior and BRC-111 permission checks apply to queries using the module label.

## Application Communication

Action labels are wallet metadata; they are not committed into the Bitcoin transaction and are not automatically delivered in BEEF. An application that gives the transaction to a recipient MUST communicate the deadline separately using its own protocol. If the recipient must authenticate that deadline, the application SHOULD bind it to the transaction or anchor outpoint with an authenticated message or signature.

Delivery MUST identify each unresolved root's network, anchor and root TXID, plus either `never` or its effective absolute timestamp/block-height deadline. Relative seconds MUST be converted from the authoritative activation record, not restarted on receipt. The application binds these facts and the dependent transaction bytes through its authenticated delivery protocol. Missing lifecycle facts prevent a claim that a received chain is non-reclaimable. `never` is a sender-wallet promise, not a Bitcoin script condition.

### Portable lifecycle statements

For cross-wallet protected graphs, a wallet MAY issue a portable statement of its own authoritative root policy. Issuance requires the complete root with all unlocking scripts fixed and its final TXID; a provisional/signable root cannot yet receive a statement. The exact `statement` object has these fields, serialized in this order as compact UTF-8 JSON without whitespace or escapes:

```json
{"version":1,"network":"mainnet","rootTXID":"6de6d9100fce8da9a58057c1aa06d1e1e2e9ab2d4f73219e2b75d4d9e24acd41","anchor":"3a70c52b24687a6d197ef362ad11f68c0e5b8419457df6fc1dc06a5851baefb4.0","mode":"never","value":null}
```

This is a shape example, not transaction evidence. `network` is `mainnet` or `testnet`; TXIDs are lowercase display-order hex and the anchor is canonical BRC-36 dot notation. `mode` is `timestamp`, `blockheight` or `never`. `value` is a nonnegative safe integer absolute deadline for the first two and exactly null for `never`. Relative seconds are converted from the persisted activation record. The root's actual bytes MUST directly spend the anchor. Duplicate or unknown fields, mismatched source facts and noncanonical values fail.

The message is UTF-8 `BRC177-LIFECYCLE-V1\n` (one LF) followed by that canonical JSON. `createSignature.data` is exactly those domain-separated UTF-8 bytes, not JSON alone. Hash them once with SHA-256 before ECDSA, under BRC-42/43 protocol `[2,"p nosend lifecycle"]`, key ID `"1"`, counterparty `"anyone"`. The carrier is exactly `{statement, signer, signature}`, with a lowercase compressed identity key and strict-DER signature hex; both valid S forms are accepted. Verifiers derive that signer's child publicly, as in BRC-207's attestation construction. The signing identity is the wallet responsible for the root's lifecycle.

This reserved BRC-98 protocol belongs to the `nosend` module. A supporting wallet MUST keep its keys and linkages internal and permit `createSignature` only for this exact statement about its own persisted root, matching every fact; `hashToDirectlySign` and other signing data are rejected. Reject auxiliary encryption, HMAC, private-key/linkage disclosure and arbitrary signing through this namespace. Public verification requires no signing permission. Wallet delivery adapters can construct the statement from authoritative storage; the existing action response shape is unchanged. Generic protocol permission cannot authorize arbitrary statements.

Persist the statement and its signature before handoff. Forward original statements for every unresolved protected root when extending a graph; a recipient cannot rewrite a prior wallet's promise under its own identity. Validate signatures, chain, root/anchor dependencies and consistent policy before relying on them. The recipient's delivery/counterparty policy must accept the responsible identity's promise; bundle presence alone is not trust. A lifecycle statement is not an admission attestation, proof of current root unspentness, proof of the signer's implementation, or an on-chain restriction. An unknown upstream policy MUST be disclosed as unknown, never inferred as `never`. A known protected root with missing required lifecycle evidence fails protected delivery. Conflicting statements fail rather than selecting the later or more favorable one. Accepted roots retain their historical statements but no longer have a pending finite reclamation deadline.

### Capability and compatibility

The optional [BRC-205](/wallet/0205) attribute `brc177-nosend=2` advertises this revision, including batches and `never`. Its absence does not prove non-support; actual requests still require authoritative lifecycle support and authorization. Older implementations reject the new forms. Existing finite single-action labels retain their meaning. BRC-100 signatures, call codes and result shapes are unchanged.

## Security Considerations

* **Race at expiry:** This mechanism cannot guarantee that a reclaim transaction wins against a concurrent external broadcast. Only the transaction actually mined determines which spend is valid.
* **Observation gaps:** A recipient may have broadcast through a service the wallet has not yet observed. Applications should use a safety margin and, where useful, communicate processor acceptance through their own protocol.
* **Clock integrity:** A manipulated or badly skewed clock can trigger timestamp expiry early or late. Block height avoids wall-clock dependence but remains subject to ordinary chain reorganizations.
* **Fee changes:** The anchor must remain economical to reclaim under plausible fee conditions. Wallets should include a conservative reclaim-fee allowance and keep retrying transient failures.
* **Reservation recovery:** Clearing only local `spentBy` or equivalent state does not invalidate valid transaction bytes held elsewhere. Released finite roots require an actual anchor spend; cancelling a child cannot revoke an exposed ancestor. Released `never` roots cannot be reclaimed.
* **Bounded exposure:** Prefunding limits the reserved wallet value to the action's intended value and fees. Caller-supplied inputs remain governed by their own owners and are not recovered by spending the anchor.
* **No on-chain expiry claim:** The label is a wallet instruction, not a script condition, miner rule, or consensus timestamp.

## Example Flow

An application requests a five-minute protected action with:

```json
{
  "description": "Offer valid for five minutes",
  "outputs": [
    {
      "satoshis": 1000,
      "lockingScript": "...",
      "outputDescription": "Offer payment"
    }
  ],
  "labels": ["p nosend expiry seconds 300", "offer 42"],
  "options": { "noSend": true }
}
```

The wallet accepts dedicated funding, returns the protected action and persists its absolute deadline. It does not publish unless explicitly asked through `sendWith`; the recipient may instead publish before the deadline.

If the recipient broadcasts the action and the wallet observes acceptance within five minutes, normal proof tracking takes over. Otherwise the wallet spends the anchor to a fresh `default` output. Once that reclaim is mined, the recipient's transaction can no longer be valid on the same chain.

## Conformance cases

| Request or event                                                    | Required behavior                                                                                                                          |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `p nosend expiry never`, `noSend:true`                              | Isolate funding, persist release, debit as sent; no automatic publication or reclaim.                                                      |
| Unknown `never 0`, duplicate expiry, unsupported batch mode         | Reject before funding; never substitute another mode.                                                                                      |
| Two `noSend` children spend one staged output                       | Reject the second spend, preserving the first exposure.                                                                                    |
| Batch root has a 500-satoshi approved budget                        | Internal fee remainder stays within that budget; unrelated change is released before protection.                                           |
| Token surplus would remain hostage to an offline payment            | Prepare an admitted split first or reject; do not hide the encumbrance.                                                                    |
| Child joins two finite roots with different deadlines               | Preserve both; fail when either still-applicable deadline predicate is met, including mixed height/time predicates.                        |
| Child claims `never` but an ancestor is reclaimable                 | Reject that claim; an inherited mixed-policy child must disclose its effective restrictions.                                               |
| `signAction` completes a protected child                            | Persist exact signed bytes and exposure; do not broadcast.                                                                                 |
| Explicit `sendWith` names the leaf                                  | Validate and process its unpublished ancestor closure, returning the ordinary result for the named leaf.                                   |
| Root accepted, child rejected                                       | Cancel root reclaim, retain the child failure and retry/reconcile; never claim atomic rollback.                                            |
| Delayed request reaches its finite deadline before first submission | Reconcile, then expire/reclaim as applicable; queuing did not satisfy the deadline.                                                        |
| Sender disconnects after online prefunding                          | Construct only with adequate local evidence and durable authoritative reservations; no assumed remote success.                             |
| Restart or reservation lease expires after signed handoff           | Restore protected exposure; never make those inputs available merely due to expiry of a storage lease.                                     |
| Finite root reclaim wins                                            | Invalidate its dependent graph and reconcile separately reserved assets; no claim that their value was itself recovered.                   |
| Released `never` action is aborted                                  | Explicit failure, unchanged permanent debit/exposure, no reclaim.                                                                          |
| Portable lifecycle statement                                        | Authenticate the responsible wallet, exact root, anchor, network and persisted absolute policy; retain and forward its original signature. |
| Edited deadline, wrong key or missing protected-root statement      | Fail protected delivery; no invented `never` promise.                                                                                      |
| Lost response after batch submission                                | Query/reconcile exact TXIDs and retry idempotently; do not create replacement payments.                                                    |

## References

* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062)
* [BRC-65: Transaction Labels and List Actions](/wallet/0065)
* [BRC-100: Unified Wallet-to-Application Interface](/wallet/0100)
* [BRC-111: P Labels](/wallet/0111)
* [BRC-98: P Protocols](/wallet/0098), [BRC-205](/wallet/0205), [BRC-207](/wallet/0207)
* [BRC-175: 1Color Offline Peer-to-Peer Payments](/payments/0175)
* RFC 2119 and RFC 8174: Key words for use in RFCs to indicate requirement levels


# Signed Capability Manifest for Wallet and Tool-Server Method Exposure

RexStarBSV (<RexStarBSV@protonmail.com>)

## Abstract

This BRC defines a **capability manifest**: a declarative, risk-tiered, operator-gated document that enumerates *exactly which* SDK methods and tool-server (MCP) methods a wallet or wallet-adjacent product exposes, together with a runtime **enforcer** that refuses any method not affirmatively declared and enabled. Each exposed method is a **Capability** with a globally-unique id of the form `product.surface.method`, a **risk tier** drawn from an ordered taxonomy (`READ`, `VERIFY`, `OPS`, `CRYPTO`, `IDENTITY`, `NETWORK`, `SPEND`, `ADMIN`), and an optional operator **allow-overlay**. Read/verify tiers are exposed by default; every tier that mutates state, uses a key, moves value, asserts identity, or reaches the network is withheld by default until an operator affirmatively allows it. The set of `(id, tier, effective-enabled)` triples is reduced to a domain-separated SHA-256 **fingerprint** that is stable under documentation edits but changes the instant a capability is added, re-tiered, or toggled. That fingerprint is baked into the build as a **pin**: at construction the enforcer refuses to run if the loaded manifest does not fingerprint to the pin, so a swapped or edited manifest file cannot widen the exposed surface at runtime; Section 9 states precisely what that does and does not cover. Decisions are **fail-closed**: an undeclared method is denied, never silently allowed. Where BRC-100 governs *per-session* application requests and BRC-103/104 govern *who is calling*, this specification governs the orthogonal supply-side axis (*which methods are callable at all*) and it is designed to be sealed by a content-anchor code-provenance ordinal.

## Motivation

The BSV wallet-interface stack answers three questions well and one question not at all.

* **BRC-100** (Wallet-to-Application Interface) standardizes the *request/permission* surface: an application asks the wallet to `createAction`, `createSignature`, `getPublicKey`, etc., and the wallet may prompt the user per session/basket/protocol. This governs what a *given counterparty* is permitted to ask *at request time*.
* **BRC-103 / BRC-104** (Peer-to-Peer Mutual Authentication and Certificate Exchange, and its HTTP transport) govern *identity of the caller*, mutual authentication and certificate presentation so the wallet knows *who* is on the other end of the connection.

None of these answer the **supply-side** question: *of the entire method surface a wallet SDK or an agent/MCP tool-server compiles in, which methods are exposed at all, and how does an operator see, scrutinize, and withhold individual methods before shipping?* In practice an MCP server or SDK ships with a large method table; a new method added to the server is live the moment it is registered. BRC-100 will still prompt for it, and BRC-103 will still authenticate the caller, but neither the operator nor a downstream auditor has a signed, machine-checkable statement of the exact exposed set, nor a mechanism that makes *"this build exposes exactly these methods and no others"* tamper-evident. This is acute for **autonomous agent wallets and tool-servers**, where the caller is software, spend authority is delegated under policy, and "which tools the agent can even reach" is a primary containment boundary.

This BRC fills that gap. It is deliberately *complementary*, not competing: the manifest is consulted **before** BRC-100 permissioning and BRC-103 authentication run, if a method is not in the manifest and enabled, the call is refused before any session or identity logic executes. The fingerprint is designed to be the value a build's code-provenance seal commits to, so that widening the surface necessarily changes the build's published fingerprint. It pairs with a content-anchor code-provenance ordinal (the running-code-fingerprint-as-ordinal principle): a build's seal is minted over `build_identity || manifest_fingerprint`, making surface-widening publicly evident.

## Specification

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

### 1. Terminology

* **Capability**: one exposed SDK method or tool-server method, described by a record (Section 3).
* **Manifest**: the complete declared set of Capabilities for a build, plus header fields (Section 6).
* **Effective-enabled**: the resolved boolean exposure state of a Capability after applying the operator overlay to the tier default (Section 5).
* **Fingerprint**: the domain-separated SHA-256 over the trust-bearing projection of the manifest (Section 7).
* **Pin**: a fingerprint value fixed into a build against which the loaded manifest is checked (Section 9).
* **Enforcer**: the runtime object that renders and enforces per-Capability decisions (Sections 10-11).

### 2. Domain constant

Implementations MUST use the exact domain-separation string:

```
bsv-capability-manifest/v1
```

This value is denoted `DOMAIN` below. It appears both as the manifest `schema` field (Section 6) and inside the fingerprint preimage (Section 7). A manifest whose `schema` is not byte-equal to `DOMAIN` MUST be rejected at load.

### 3. Capability record

A Capability is the tuple of fields in the table below. Fields are marked *trust-bearing* if they participate in the fingerprint (Section 7); the rest are documentation.

| Field         | Type            | Required            | Trust-bearing                | Meaning                                                                                                  |
| ------------- | --------------- | ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------- |
| `id`          | string          | yes                 | **yes**                      | Globally-unique id, MUST equal `product.surface.method` (Section 4).                                     |
| `product`     | string          | yes                 | no (bound via `id`)          | Owning product, e.g. `wallet`, `marketplace`, `messaging`, `content-anchor`, `signer`, `media`.          |
| `surface`     | string          | yes                 | no (bound via `id`)          | The SDK or tool-server surface, e.g. `agent-mcp`, `wallet-connect`, `marketplace-mcp`, `messaging-core`. |
| `method`      | string          | yes                 | no (bound via `id`)          | The exposed method / tool name.                                                                          |
| `tier`        | enum            | yes                 | **yes**                      | One of the risk tiers in Section 5.                                                                      |
| `description` | string          | no (default `""`)   | **no**                       | Plain-language description. Documentation only.                                                          |
| `grounded_in` | string          | no (default `""`)   | **no**                       | Repo-relative file(s) proving the method exists. Documentation only.                                     |
| `allow`       | boolean or null | no (default `null`) | indirectly (via `effective`) | Operator override: `true` = expose, `false` = withhold, `null` = use tier default.                       |

Note the deliberate design: `product`, `surface`, and `method` are individually non-trust-bearing because their concatenation `id` is trust-bearing and is checked to equal them (Section 4); `description` and `grounded_in` are **never** trust-bearing (Section 7.3); and `allow` influences the fingerprint only through the derived **effective** boolean (Section 5), so two manifests that resolve to the same effective exposure fingerprint identically regardless of whether exposure came from an explicit `allow:true` or a tier default.

### 4. Capability id grammar

The `id` MUST be exactly the three dot-joined components:

```
id = product "." surface "." method
```

An implementation constructing or loading a Capability MUST reject it (with a structural error) if `id` is not byte-equal to the string formed by joining `product`, `surface`, and `method` with `.`. This makes the id self-describing and prevents a manifest from labeling a capability with a misleading id. Components SHOULD be treated as opaque tokens, but their character set is constrained: every component MUST consist solely of printable ASCII (U+0020 to U+007E), and an implementation MUST reject an `id` containing any other character. Implementers SHOULD additionally avoid embedding `.` inside a component to keep the id parseable back into its three parts. The ASCII restriction is not decoration, and it is what makes two statements elsewhere in this document true. Section 7.2 escapes non-ASCII as `\uXXXX` and emits ASCII bytes while RFC 8785 JCS emits raw UTF-8, so the two canonicalizations agree on every id this grammar admits and cannot disagree on one it does not. And because §7.2 specifies no Unicode normalization, a non-ASCII id would let NFC and NFD variants of one visually identical name be two distinct ids under the uniqueness rule below, declared with different `allow` values and producing two fingerprints that no reviewer could tell apart by eye. Restricting the character set removes both hazards rather than adding two more rules to §7.2. Within a single manifest every `id` MUST be unique; a duplicate id MUST be rejected at construction.

### 5. Risk-tier taxonomy and default-exposure rule

There are exactly eight tiers, in this canonical order from least to most sensitive:

```
READ, VERIFY, OPS, CRYPTO, IDENTITY, NETWORK, SPEND, ADMIN
```

| Tier       | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Default exposure |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| `READ`     | Read-only data (balances, listings, history, chain tip).                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | **on**           |
| `VERIFY`   | Pure verification, no keys, no state mutation, no network.                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | **on**           |
| `OPS`      | Mutates persistent state without creating or broadcasting an outgoing value-moving action. Membership is heterogeneous and MUST be reviewed per capability, not inferred from the tier name: in the reference inventory four of the five members write local management state (triage items, status advancement, checklist sign-offs), while the fifth is the money-in path, which derives a wallet private key and credits the spendable default basket. Implementers MUST NOT assume this tier is free of keys or of value. | **off**          |
| `CRYPTO`   | Uses a key to sign / HMAC / encrypt / decrypt.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | **off**          |
| `IDENTITY` | Issue / prove / relinquish certificates, reveal key linkage.                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | **off**          |
| `NETWORK`  | Triggers outbound network (broadcast, relay, fetch-by-id, onion connect).                                                                                                                                                                                                                                                                                                                                                                                                                                                     | **off**          |
| `SPEND`    | Creates or broadcasts a value-moving action.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | **off**          |
| `ADMIN`    | Configuration / operator / webhook-registration surfaces.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | **off**          |

The **default-exposure rule** is: a tier is on-by-default **iff** it is in the set `{READ, VERIFY}`. All other tiers are off-by-default. Formally:

```
default_enabled(tier) = tier ∈ {READ, VERIFY}
```

An implementation MUST reject a Capability whose `tier` is not one of the eight listed values. The tier ordering is normative and is used by tooling for grouping and for tier-scoped overlay selectors (Section 12); it does not by itself imply that a higher tier is "more denied", every off-by-default tier is equally off until the operator allows it.

### 6. Manifest document format

A serialized manifest is a JSON object with these fields:

| Field              | Type   | Required               | Meaning                                                                                                                                                   |
| ------------------ | ------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `schema`           | string | yes                    | MUST equal `DOMAIN` (Section 2); else reject.                                                                                                             |
| `manifest_version` | string | no (default `"0.0.0"`) | Human version of the inventory. Not fingerprinted.                                                                                                        |
| `note`             | string | no (default `""`)      | Free-text note. Not fingerprinted.                                                                                                                        |
| `fingerprint`      | string | no                     | If present, MUST equal the recomputed fingerprint (Section 8); else reject as tampering.                                                                  |
| `capabilities`     | array  | yes                    | Array of Capability records (Section 3), each serialized with fields `id`, `product`, `surface`, `method`, `tier`, `allow`, `description`, `grounded_in`. |

A Capability row missing any of the required fields `id`, `product`, `surface`, `method`, `tier` MUST cause a load error. `description`, `grounded_in`, and `allow` MAY be omitted and default to `""`, `""`, and `null` respectively. The `manifest_version` and `note` fields are metadata and MUST NOT influence the fingerprint.

### 7. Canonical fingerprint

The fingerprint is the single value that certifies "this build exposes exactly these capabilities." It is computed over a **trust-bearing projection** only.

#### 7.1 Per-capability projection

For each Capability, form the object with exactly these three keys:

```
{ "id": <id>, "tier": <tier>, "enabled": <effective-enabled> }
```

where `effective-enabled` is resolved per Section 5:

```
effective(cap) = default_enabled(cap.tier) if cap.allow is null
 = bool(cap.allow) otherwise
```

`enabled` is a JSON boolean. No other field of the Capability appears.

The Required column of Section 3 describes the **record**, which always carries every field once constructed. Section 6 governs the **document**, where `description`, `grounded_in` and `allow` MAY be omitted and default to `""`, `""` and `null`. The two are not in tension: a document may leave them out, and the record that results still has them. An implementation whose record type makes them mandatory arguments is conformant, provided its document parser supplies the defaults.

`allow` is a JSON boolean or `null`, and an implementation MUST reject any other value as a structural error (Section 13) rather than coercing it. The effective-state rule resolves a non-null `allow` by truthiness, so a value such as the JSON string `"false"` would resolve to **enabled**, exposing the capability that whoever wrote it plainly meant to withhold. The fingerprint would then record `enabled: true` faithfully, so the mistake is invisible to an auditor comparing fingerprints and survives every downstream check. Reject at load.

#### 7.2 Assembly and canonicalization

1. Sort the Capabilities by `id` in ascending **Unicode code point** order. Code point order and UTF-8 byte order agree, so either may be used to implement it. UTF-16 code unit order does NOT agree and MUST NOT be used: it places characters above U+FFFF before those in U+E000 to U+FFFF, which a language whose default string comparison is over UTF-16 code units will do silently.
2. Build the list `body` of per-capability projection objects, in that sorted order.
3. Build the preimage object:

```
{ "domain": DOMAIN, "capabilities": body }
```

4. **Canonicalize** the preimage to a string by serializing as JSON with: keys sorted ascending at every object level, no insignificant whitespace (item separator `","`, key/value separator `":"`), and all non-ASCII characters `\uXXXX`-escaped (ASCII-only output). Three further rules are REQUIRED, because each is a degree of freedom that changes the digest and each has a common serializer that chooses the other way:
   * The solidus `/` is **NOT** escaped. `DOMAIN` contains one, so it appears in every preimage ever computed, and a serializer that escapes it by default produces a different fingerprint for every manifest in existence rather than only for unusual inputs.
   * Hexadecimal digits in a `\uXXXX` escape are **lowercase**.
   * A character above U+FFFF is escaped as the **UTF-16 surrogate pair**, two `\uXXXX` escapes. A six-digit or `\U`-style escape is not this format. Under this canonicalization the outer object serializes with keys in the order `capabilities`, `domain`, and each projection object serializes with keys in the order `enabled`, `id`, `tier`.
5. Encode the canonical string as ASCII bytes and compute `SHA-256`. The fingerprint is the lowercase hex digest.

Only JSON strings and booleans appear in the preimage, so the canonicalization is unambiguous without a number-formatting rule; implementations that use a different canonical-JSON library MUST reproduce the exact byte sequence described here (this is the specific form emitted by a sorted-keys, tight-separator, ASCII-escaped JSON serializer, it is *not* required to be RFC 8785 JCS. The two agree on any preimage whose capability ids are ASCII, which is every id in the reference inventory, and they **disagree** when an id contains non-ASCII: JCS emits raw UTF-8 and does not escape, while this canonicalization is ASCII-only. Section 4 permits non-ASCII ids, so an implementation MUST follow the rules above rather than delegate to a JCS library.)

#### 7.3 What the fingerprint covers and deliberately excludes

* **Covered (any change re-fingerprints):** adding a capability, removing a capability, changing a capability's `id`, changing a capability's `tier`, and any change to a capability's `effective-enabled` (whether caused by an `allow` toggle or, indirectly, by a tier default).
* **Deliberately excluded (changes do NOT re-fingerprint):** `description`, `grounded_in`, `manifest_version`, `note`, and the distinction between "enabled by tier default" vs "enabled by explicit `allow:true`". Descriptions and file references are documentation and are **not trust-bearing**; an implementation MUST NOT let them influence the fingerprint. This is a security property: an auditor comparing two builds by fingerprint is comparing *exposed behavior*, and cannot be misled by prose, nor forced to re-mint a provenance seal for a wording fix.

The consequence, called **update coupling**: a build's SDK/tool-server contract version is a function of the capability fingerprint, not of the application version. An app update forces a manifest + provenance-seal re-mint **iff** it moves the fingerprint. The fingerprint is the machine test for "did the exposed contract change."

### 8. Load-time integrity

When loading a serialized manifest:

1. Reject if `schema` != `DOMAIN`.
2. Parse every Capability row, enforcing the id-grammar (Section 4), the tier check (Section 5), and uniqueness of ids.
3. Recompute the fingerprint (Section 7).
4. If the document carries a `fingerprint` field, it MUST byte-equal the recomputed value; otherwise the load MUST fail (a stored fingerprint disagreeing with its own body is tampering and MUST be caught at load).

### 9. Build-pin enforcement

The runtime enforcer is constructed with a `pinned_fingerprint` value fixed by the build rather than derived from the manifest it is handed. An implementation MUST embed it in the build artifact and MUST NOT read it from any runtime-mutable source in a production build. A build MAY permit it to be overridden at run time **only** for a review or testing harness, on the same terms as the null pin below, and such an override MUST be reported on startup, because a pin nobody can see changing is not a pin. The two cases are stated with the same force deliberately: a runtime-supplied pin and a null pin defeat the check identically, since an enforcer that recomputes the fingerprint of the manifest it was handed and compares it to a value supplied beside that manifest is verifying nothing.

* At construction the enforcer MUST recompute the fingerprint of the manifest handed to it and, if `pinned_fingerprint` is non-null and does not byte-equal the recomputed fingerprint, MUST refuse to construct (raise a pin error). **The guarantee this provides, stated precisely.** The only manifest an enforcer will run is one that fingerprints to the pinned value, so a swapped or edited manifest file cannot widen the exposed surface. That is the whole of it, and the threat it answers is a caller of the exposed surface, which controls neither the host nor the build.

It is **not** a guarantee against an attacker who already controls the host. Whoever can set the environment or edit the source that carries the pin can re-pin to a widened manifest, and no check inside the process can prevent that: the process would be verifying itself with a value the attacker chose. An implementation MUST NOT describe this mechanism as protection against host compromise. Against that threat the anchor has to be outside the build, an independently published fingerprint of the running artifact that a third party can compare, which Section 14 describes and this section deliberately does not claim to be.

* A `pinned_fingerprint` of null disables the check. Implementations MUST support this **only** for review/testing harnesses and MUST NOT ship a production build with a null pin.

**A pin covers the surface whose enforcer holds it, and no other.** A manifest may declare capabilities for many surfaces, and declaring them is documentation. Enforcement happens only where a process constructs an enforcer over that manifest and routes its calls through it. In a deployment where one of several declared surfaces does so, the pin protects that surface's capabilities and the rest are described but unguarded.

**Such a deployment is not conformant, and this paragraph must not be read as blessing one.** Section 11 requires every exposed method to call the gate before performing any work, so a declared surface whose methods are reachable without an enforcer violates that MUST however completely the manifest describes it. Two readings are conformant and no third is: either a declared surface constructs an enforcer and routes its calls through it, or the methods of that surface are not exposed in this build at all, in which case Section 15 applies and they should not be in the manifest. An implementation MUST state which declared surfaces construct an enforcer, because a reader counting declared capabilities would otherwise overcount what is enforced, and a deployment that cannot make that statement for every surface it declares MUST NOT claim conformance.

This mirrors the issuer-key build-pin discipline used elsewhere in the suite (reachability and update-channel pins): the build pin is authoritative over anything supplied at runtime.

### 10. Decision semantics (fail-closed)

For a requested capability id, the enforcer renders exactly one of four decisions:

| Decision            | Condition                                                                                                  |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| `ALLOW`             | The id is declared **and** its `effective-enabled` is true.                                                |
| `DENY_UNKNOWN`      | The id is **not declared** in the manifest.                                                                |
| `DENY_TIER_DEFAULT` | The id is declared, not enabled, and `allow` is `null` (off by tier default; operator has not enabled it). |
| `DENY_OPERATOR`     | The id is declared, not enabled, and `allow` is `false` (operator explicitly disabled it).                 |

The decision algorithm MUST be:

```
decide(id):
 cap = manifest.get(id)
 if cap is None: return DENY_UNKNOWN # fail-closed
 if effective(cap): return ALLOW
 if cap.allow is False: return DENY_OPERATOR
 else: return DENY_TIER_DEFAULT
```

There MUST be no "unknown = allow" path. An undeclared id is **never** allowed. A method added to a server without a corresponding manifest entry is therefore dead until it is declared and reviewed.

### 11. Enforcement boundary

Every exposed SDK method and tool-server method MUST call the enforcer's fail-loud gate with its own capability id **before performing any work** (before argument-dependent side effects, before BRC-100 permissioning, before BRC-103 authentication). The gate MUST:

* return normally (permit execution) iff `decide(id) == ALLOW`;
* otherwise raise/return a loud error carrying the id and a human-readable reason. Reason strings SHOULD be:
* `DENY_UNKNOWN` → `not declared in the capability manifest (fail-closed)`
* `DENY_TIER_DEFAULT` → `disabled by default for its risk tier, operator has not enabled it`
* `DENY_OPERATOR` → `explicitly disabled by the operator`

The gate MUST NOT fall through to execution on any denied decision.

### 12. Operator allow-overlay

The operator review surface is an **overlay**: a mapping from capability id to `true` / `false` / `null`. Applying an overlay to a manifest yields a *new* manifest in which each referenced capability's `allow` is replaced by the overlay value (and unreferenced capabilities keep their prior `allow`). Overlay application MUST fail loud if it references any id **not declared** in the manifest, an operator cannot toggle a capability that does not exist. Because the resulting manifest's fingerprint reflects the new effective states, the operator's decisions are load-bearing (they change the fingerprint the build's provenance seal must commit to), not advisory. Tooling MAY expose selectors that expand an id-prefix (a product or `product.surface`) or a tier name to the set of matching ids, so that "withhold everything that spends" is a single `deny SPEND` action; such expansion is a tooling convenience and MUST resolve to explicit per-id overlay entries before application.

### 13. Error taxonomy

Implementations MUST distinguish at least:

* **Structural / manifest error**: unknown tier, id-grammar violation, duplicate id, missing required field, schema mismatch, or an overlay referencing an undeclared id.
* **Pin / tamper error**: a stored `fingerprint` that disagrees with the recomputed body (Section 8), or a manifest whose fingerprint does not match the build pin (Section 9). These are distinct from generic structural errors because they signal a *possible swap*, not merely malformed input.
* **Decision error**: the fail-loud result of gating a denied capability (Section 11).

### 14. Relationship to BRC-100 / BRC-103 / BRC-104, the reserved namespaces, and provenance sealing

* The manifest gate runs **first**. A capability that is `DENY_*` never reaches BRC-100 permission prompts or BRC-103 authentication, it is refused at the supply boundary.
* BRC-100 continues to govern *per-session, per-counterparty* requests for capabilities that the manifest exposes; the manifest does not replace user consent, it bounds the menu consent can be asked about.
* BRC-103/104 continue to authenticate the caller; the manifest is caller-independent (it constrains the server's own surface).
* The fingerprint (Section 7) is the value a code-provenance ordinal seals: mint over `build_identity || manifest_fingerprint`. This makes "this build exposes exactly these capabilities and no others" a publicly verifiable, tamper-evident claim, and forces any surface-widening to change the published seal.
* **BRC-98 modules and this manifest act on different axes, and they compose.** BRC-98 reserves protocol identifiers beginning with `p` and lets a supported scheme define behaviour at that dispatch point. BRC-229 is the worked example: it defines the `ecpm` scheme and, in its own words, "uses that reserved dispatch point to specialize the meaning of an existing BRC-100 method". A BRC-98 module therefore changes what an already-exposed method *means* for a caller who names the reserved identifier. This specification governs whether that method is exposed at all, evaluated before any caller is consulted and independently of what any caller asks for. A wallet implementing both evaluates the manifest first: if the underlying method is `DENY_*`, the module's dispatch point is unreachable, and the module's presence does not widen the surface. Implementers SHOULD give a BRC-98 module's underlying method its own capability id, so the exposure decision is recorded explicitly rather than inherited.
* **BRC-111 labels are likewise orthogonal.** A label describes or permissions an individual action; a capability id names a method on a server's surface. Neither namespace is claimed here, and a manifest MUST NOT be read as reserving any identifier under `p` .

### 15. What is deliberately absent from a manifest

A manifest lists **exposed** capabilities only. Air-gap co-signer internals (key generation, cosigning, seed handling), raw key material, egress choke points, deniability containers, subscription/issuer keys, and admin/issuer operations MUST NOT be listed merely to document them: appearing in the manifest is what makes a method eligible to be enabled and executed. Anything that must remain non-exposed is simply absent, and if it ever needs exposure it MUST be *added and reviewed*, never assumed.

## Test Vectors

All fingerprints below are lowercase SHA-256 hex digests reproduced from the reference implementation. `DOMAIN = bsv-capability-manifest/v1`.

### Vector 1, tier defaults, no operator overlay

Two declared capabilities, both with `allow = null`:

| id                               | tier    | allow  |
| -------------------------------- | ------- | ------ |
| `example.agent-mcp.getBalance`   | `READ`  | `null` |
| `example.agent-mcp.createAction` | `SPEND` | `null` |

Effective states (Section 5): `getBalance` → **true** (READ default-on); `createAction` → **false** (SPEND default-off).

Canonical fingerprint preimage (Section 7.2), exact bytes:

```
{"capabilities":[{"enabled":false,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":true,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}
```

Fingerprint:

```
f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc
```

Decisions (Section 10): `decide("example.agent-mcp.getBalance")` → `ALLOW`; `decide("example.agent-mcp.createAction")` → `DENY_TIER_DEFAULT`; `decide("example.agent-mcp.sweepBsv")` (undeclared) → `DENY_UNKNOWN`.

### Vector 2, operator allows the SPEND capability

Same two capabilities, but the operator sets `allow = true` on `createAction`:

| id                               | tier    | allow  |
| -------------------------------- | ------- | ------ |
| `example.agent-mcp.getBalance`   | `READ`  | `null` |
| `example.agent-mcp.createAction` | `SPEND` | `true` |

Now `createAction` → **true**. Preimage:

```
{"capabilities":[{"enabled":true,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":true,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}
```

Fingerprint:

```
5f1cbe881b0df89e4c7f37f5e6d0b9cfe0156e1a8f7e7776e318e615e35a3d4e
```

The fingerprint differs from Vector 1, the single overlay toggle re-fingerprints the build, which would force a provenance-seal re-mint. `decide("example.agent-mcp.createAction")` is now `ALLOW`.

### Vector 3, operator explicitly disables a default-on capability

The operator sets `allow = false` on the READ capability (`createAction` back to `null`):

| id                               | tier    | allow   |
| -------------------------------- | ------- | ------- |
| `example.agent-mcp.getBalance`   | `READ`  | `false` |
| `example.agent-mcp.createAction` | `SPEND` | `null`  |

Both effective states are **false**. Preimage:

```
{"capabilities":[{"enabled":false,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":false,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}
```

Fingerprint:

```
058a0c229fbd0a3830a45dfefecd0343e282f7200406ef58c62586c3679cdffa
```

`decide("example.agent-mcp.getBalance")` → `DENY_OPERATOR` (distinct from Vector 1's `DENY_TIER_DEFAULT` for the same effective-off state, the reason preserves *why* it is off).

### Vector 4, descriptions are not trust-bearing

Take the Vector 1 manifest and change the `description` and `grounded_in` of `getBalance` to arbitrary different strings, leaving `id`, `tier`, and `allow` unchanged. The per-capability projection (Section 7.1) is unchanged, so the fingerprint is **identical** to Vector 1:

```
f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc
```

This demonstrates Section 7.3: documentation edits do not move the fingerprint and do not require a provenance re-mint.

### Vector 5, build-pin swap refusal

Construct an enforcer with `pinned_fingerprint = f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc` (the Vector 1 pin) but hand it the **Vector 2** manifest (fingerprint `5f1c…`). Because `5f1c…` != the pin, the enforcer MUST refuse to construct with a pin/tamper error and MUST NOT enter service. Handing the same enforcer the Vector 1 manifest constructs normally. This is the runtime guarantee that a swapped manifest cannot widen the surface.

## Implementations

A reference implementation exists (not a public path):

* A stdlib-only manifest module (Python) implementing the `Capability` record with id-grammar and tier validation, the `CapabilityManifest` container (dedup, canonical fingerprint, overlay, load-time integrity check, JSON round-trip), the four-valued `Decision` enum with fail-closed semantics, and the `CapabilityEnforcer` with build-pin refusal and a fail-loud `enforce()` boundary gate.
* A populated suite-wide JSON inventory carrying the `schema`, `manifest_version`, `note`, `fingerprint`, and `capabilities` fields described in Section 6, spanning the suite's products and surfaces (wallet agent-MCP, onion connect, marketplace client/MCP, messaging core, seal cores, air-gap signer verify surfaces).
* A review/toggle/check/diff CLI implementing the operator overlay (Section 12), including product/surface/tier selector expansion, an integrity/CI check mode, and a build-to-build fingerprint diff.
* A conformance test module covering fail-closed denial, tier defaults, fingerprint stability under documentation edits, build-pin swap refusal, overlay-of-undeclared-id refusal, and stored-fingerprint tamper detection.

Repo-relative `grounded_in` references inside each capability row point at the concrete file that proves the corresponding method exists, so the inventory is auditable against the real code.

## Mathematical and cryptographic basis

The fingerprint is a binding commitment to one exposed surface. It is SHA-256 over a canonical preimage that contains the domain string and the sorted list of (id, tier, enabled) projections and nothing else (Section 7). Canonicalization is injective on that projection: capabilities sorted by id, keys sorted, tight separators, and only strings and booleans present, so a given exposed surface maps to exactly one preimage and one digest. Any change to a trust-bearing element (adding or removing a capability, re-tiering it, or flipping its effective-enabled bit) changes the preimage and therefore the digest. Presenting a different exposed surface under an unchanged fingerprint would require a SHA-256 collision, which is infeasible.

Two properties follow from what the preimage includes and excludes. Documentation fields are outside the preimage, so a wording fix cannot move the fingerprint, and an auditor comparing two builds by fingerprint is comparing exposed behavior rather than prose. The domain string is folded in as the first element, so a manifest digest cannot coincide with a digest any other protocol computes over similar JSON, which closes cross-protocol confusion. The build pin supplies enforcement: at load the enforcer recomputes the fingerprint and refuses to run unless it equals the pinned value, so the commitment is checked against tampering before a single method is exposed, and the decision is fail-closed.

## Security Considerations

* **Fail-closed is the whole point.** The only path to execution is an id that is both declared and effective-enabled. An undeclared id (a newly registered tool, a typo, an injected id) is denied. Implementers MUST NOT add any default-allow or wildcard path.
* **Build-pin authority.** The pin must be embedded in the build artifact, not read from any runtime-mutable source. If an attacker can supply the pin (env/config/file) they can defeat the check; the pin's value is precisely that it is *not* runtime-suppliable. A production build with a null pin (review mode) silently disables the protection and MUST be prevented by release tooling.
* **Trust-bearing projection.** Because only `(id, tier, effective)` are fingerprinted, an attacker cannot smuggle behavior through `description`/`grounded_in`. Conversely, implementers MUST NOT be tempted to "help" by folding descriptions into the fingerprint, doing so would make every prose edit re-mint the provenance seal and erode the auditor's ability to diff behavior.
* **id ≡ product.surface.method.** The self-consistency check prevents a manifest from mislabeling a spend method with a read-looking id. Enforcement callers MUST pass the *method's own* id; a shared or hard-coded id at multiple call sites would let one enabled capability gate an unrelated method.
* **Overlay integrity.** Overlay application refuses undeclared ids, so an operator config cannot pre-authorize a not-yet-declared capability. The overlay changes the fingerprint, which is the feature: silent scope creep is impossible without a visible fingerprint change.
* **Canonicalization determinism.** Two independent implementations MUST produce byte-identical preimages or their fingerprints (and therefore their pins and seals) will disagree. The preimage contains only strings and booleans; implementers MUST use sorted keys, tight separators, and ASCII escaping, and MUST NOT emit trailing whitespace, BOMs, or reordered keys.
* **Scope boundary vs. authorization.** This BRC bounds *which methods are exposed*; it does not by itself authenticate callers (see BRC-103/104) or capture per-request user consent (see BRC-100). It is a necessary lower bound, run first, and MUST be composed with those layers, never used as a substitute for them.
* **Absence is protection.** Sensitive internals (air-gap key handling, issuer keys, egress choke points, deniability containers) are protected by being *absent* from the manifest. Listing such a method (even disabled) makes it eligible to be toggled on; implementers MUST keep permanently-non-exposed surfaces out of the manifest entirely.

## References

* BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface, the per-session request/permission surface this specification is consulted ahead of.
* BRC-103: Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol, caller authentication, complementary to this supply-side control.
* BRC-104: HTTP Transport for BRC-103 Mutual Authentication.
* BRC-2: Data Encryption and Decryption; BRC-3: Digital Signature Creation and Verification, example CRYPTO/VERIFY-tier methods referenced by manifest rows.
* BRC-42: BSV Key Derivation Scheme (BKDS); BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties, example key-derivation methods behind READ/CRYPTO rows.
* BRC-52: Identity Certificates / BRC-53: Certificate Creation and Revelation, example IDENTITY-tier methods.
* BRC-65: Transaction Labels and List Actions, example READ-tier history method.
* BRC-98: P Protocols: Allowing Future Wallet Protocol Permission Schemes, the reserved protocol-identifier dispatch point; orthogonal axis, see Section 14.
* BRC-111: P Labels: Allowing Future Wallet Action Label Permission Schemes, reserved label namespace; not claimed by this specification.
* BRC-229: Wallet-Native Elliptic Curve Point Multiplication as a BRC-98 Module, worked example of a module specializing an existing method.
* FIPS 180-4: Secure Hash Standard (SHA-256), the fingerprint hash.
* RFC 8785: JSON Canonicalization Scheme (JCS), referenced for comparison; this BRC's preimage form agrees with JCS on the string/boolean-only preimage but is defined independently in Section 7.2.
* RFC 2119: Key words for use in RFCs to indicate requirement levels.
* BRC-145, Registry-Free Typed Content Anchor with On-Chain Code Provenance (merged 2026-08-02): its code-provenance seal is the running-code-fingerprint-as-ordinal mechanism this manifest's fingerprint is minted into.


# Wallet-Enforced Autonomous-Agent Spend Policy

RexStarBSV (<RexStarBSV@protonmail.com>)

> The `type` domain separator in §3.1 carries this standard's number and sits **inside** the signed payload, so the test vectors below are signed over `brc-181/agent-policy/1` and reproduce exactly as printed. Changing the number is therefore not a search-and-replace: every digest and signature in Test Vectors is derived from it.

## Abstract

This BRC specifies a wallet-enforced spend policy that lets an autonomous software agent (e.g. an LLM-driven MCP client) spend from a dedicated, isolated wallet account *without a human in the loop for every transaction*, while guaranteeing that the agent can never move funds outside a signed, operator-authorized envelope. It defines: (1) a signed, canonicalized `PolicyRecord` binding an agent identity to per-transaction / rolling-period / lifetime caps, a canonical destination allowlist, rate and circuit-breaker limits, and an expiry; (2) a canonical destination format over both P2PKH `hash160` and arbitrary script-hash, closing the raw-script allowlist bypass; (3) the exact, ordered set of checks a wallet MUST perform per `createAction`, with an atomic check→reserve→debit ledger that is safe under concurrent (high-frequency) spends; (4) fail-closed rules for broadcast uncertainty, freezes, and malformed requests; and (5) instant revocation. The policy is enforced entirely by the wallet; nothing in the agent's request is trusted except as *input to be validated against* the policy. A second implementer can build an interoperable enforcement engine from the Specification and Test Vectors alone.

## Motivation

Machine-to-machine nanopayments are a defining use case for a low-fee BSV wallet: agents paying per-query metered endpoints that settle to a stable address, bidding within a budget on a marketplace, anchoring inscriptions, and paying other agents. One prominent 402 profile is **not** in that list, and the boundary is stated under *Relationship to related work* rather than left for an implementer to discover. The wallet-to-application substrate for this already exists, **BRC-100** defines `createAction`/`signAction` and the per-application permission model, and **BRC-1** defines the underlying transaction-creation primitive. What is missing is a way for the wallet to authorize *headless* spends safely.

The closest existing authorization mechanism, **BRC-116 (Wallet Permissions and Counterparty Trust)**, lets an application declare a `spendingAuthorization` in its manifest: an `{amount, description}` pair documented as a *"Monthly spending limit in satoshis"*, which the wallet grants through a permission prompt, optionally bundled with other declared permissions into a single group prompt. It is a **ceiling plus a grant**, not an enforcement envelope: within the granted amount it constrains how much, not to whom, how fast, or under what conditions, and it carries no rate limit, no destination allowlist, no circuit breaker, and no signed record a wallet can verify independently of the session that granted it.

1. **A per-transaction prompt defeats autonomy.** An agent paying 1 sat per query thousands of times per minute cannot stop for a human confirmation on each spend. A monthly ceiling alone, without a per-tx cap, destination allowlist, rate limit, and circuit breaker, is too blunt to be safe for headless operation, a single prompt-injected request could drain the whole monthly budget to one attacker output.
2. **An LLM-driven agent is an untrusted requester.** An agent will eventually be prompt-injected into *trying* to send everything to an attacker's address. The security boundary therefore cannot be the agent's good behaviour; it must be a wallet-side policy engine that treats every field of the agent's request as adversarial input to validate, never as an instruction to trust.
3. **Autonomy needs isolation and fine-grained bounds.** Autonomous authority must be scoped to a dedicated, capped account (blast radius = the account balance, never the main wallet) and bounded on several independent axes at once (per-tx, period, lifetime, destination, rate, anomaly), because any single axis is individually bypassable.

The risk this addresses is not hypothetical, and it is not only the author's view. **BRC-151** (*BRC-100 Risk Assessment and Best Integration Practices*), an opinion paper merged into this repository, devotes its Section 9 to AI agents operating with human funds and reaches the same two conclusions independently: that *"small spending limits do not eliminate the danger"* because an agent *"may make repeated micropayments, accumulate losses"* within any single-axis ceiling, and that the danger *"becomes especially severe when the AI agent's operating wallet is also the human user's primary"* wallet. It observes that a human *"may believe that a spending authorization defines the purpose for which money can be used"* when it does not.

BRC-151 states the problem and declines to specify a remedy, which is proper for an opinion. This standard is one remedy: multi-axis limits so a ceiling cannot be drained by repetition, and a required isolated account so the agent's blast radius is never the user's primary balance. This BRC complements BRC-116 rather than replacing it. BRC-116 answers *may this application spend, and up to how much this month*; this standard answers *may this specific action proceed right now, unattended, given everything this agent has already spent*. The two compose: a wallet MAY require a BRC-116 grant before an agent policy is honoured at all.

## Relationship to related work

This standard sits alongside the Wallet Permission Prompt Liveness Contract (merged as BRC-219). The two cover different cases. That standard governs the attended case: an application sends a request that needs user consent, and the wallet may keep the request pending until the person grants or denies it, however long that takes. This standard governs the case where there is no person to ask. The wallet holds signed, bounded limits and checks them on every action, so an autonomous agent can spend inside the envelope with no prompt per transaction.

The same division applies to BRC-116. Its `spendingAuthorization` bounds a total over a period; it does not describe per-action enforcement, and its policy-gating and whitelist provisions (§ *Policy gating*, *Whitelist handling*) already contemplate a wallet allowing an operation without prompting. What is absent there, and specified here, is the signed, wallet-verified envelope that makes such an allowance safe to grant to software that no human is watching.

### Bounded agent spending without a wallet: BRC-172

BRC-172 (*Giving an AI Agent Control of a Delivery Vehicle Without Giving It a Wallet*) addresses the same danger from the opposite premise. It defines a covenant that carries its own fuel and enforces its own successor state, so the agent holds at most a credential that advances the covenant and, in that standard's phrase, has "control without custody". Where there is no key, there is nothing for a prompt-injected request to redirect.

The two standards apply to different topologies of value, and neither subsumes the other.

|                      | BRC-172                                          | This standard                                                         |
| -------------------- | ------------------------------------------------ | --------------------------------------------------------------------- |
| Where the value sits | pre-committed into a covenant                    | in a wallet account the agent may spend from                          |
| What the agent holds | a credential that advances a script              | an authenticated request to a wallet                                  |
| Destinations         | fixed by the script when the covenant is created | any destination on a signed allowlist, revisable without moving funds |
| Bound enforced by    | the locking script, at consensus                 | the wallet, per `createAction`, against a stateful ledger             |

A covenant battery is the stronger construction wherever the payment graph can be committed in advance. It does not reach the case this standard is written for: an agent paying varying external counterparties whose identities are not known when authority is granted, such as metered per-query endpoints, marketplace bids, or payments to other agents discovered at run time. Committing that graph into a covenant ahead of time is not possible, so the bound has to be applied at the moment of spending by the party that holds the key.

BRC-172 is explicit about its own boundary. Its Scope section states: "Does not define what the covenant computes, or how its state is encoded", and it warns that "A battery does not make an agent correct, honest or safe to deploy." This standard makes no broader claim: it bounds what an untrusted requester can cause a wallet to do, and it does not make the agent trustworthy either.

An implementer serving both cases can use both. A covenant battery for the committed portion of an agent's work, and a wallet-enforced policy for the portion that must remain open.

### The method an agent may reach, and the amount it may move: BRC-179

BRC-179 (*Signed Capability Manifest for Wallet and Tool-Server Method Exposure*) bounds the same agent from the other side of the call. It defines a risk-tiered manifest of exactly which methods a wallet or tool-server exposes, in which every tier that moves value is withheld until an operator affirmatively allows it, and a fingerprint of the declared set that is pinned into the build so an edited manifest cannot widen the surface at run time. Its Section 11 places that gate before any work is done, "before BRC-100 permissioning, before BRC-103 authentication", so a method that is not declared and enabled is never reached by a caller at all.

That gate and this policy are sequential, not alternative, and each is blind to what the other decides.

|                   | BRC-179                                        | This standard                                    |
| ----------------- | ---------------------------------------------- | ------------------------------------------------ |
| Question answered | is `createAction` exposed on this build at all | may *this* action proceed, now, unattended       |
| Unit of decision  | one method, a boolean                          | one request, against accumulated state           |
| Bound to          | the build                                      | one `(agent_account, agent_identity)` pair       |
| Revised by        | a rebuild, which moves a published fingerprint | an operator signature, which moves an envelope   |
| Evaluated         | once, before any caller is consulted           | every request, after the caller is authenticated |
| Cannot see        | amounts, destinations, rates, spend history    | whether the method should have been exposed      |

BRC-179 is a precondition of this standard rather than a component of it. Because its spend-bearing tier is default-off, an operator who wants an agent to spend must affirmatively expose the method; what this standard supplies is the reason that exposure can be safe to grant. A manifest cannot express an amount, a destination or a rate, because a capability decision is caller-independent by construction, and BRC-179 Section 14 draws the same line from its own side: the manifest "does not replace user consent, it bounds the menu consent can be asked about". A policy record, conversely, cannot prevent a method from existing on a surface it never sees. Neither reduces to the other, and their failure modes are independent: widening a manifest moves a fingerprint that is published off-box, while widening a policy requires an operator signature that is carried in the envelope.

An implementation deploying both MUST evaluate them in that order and MUST NOT let either stand in for the other. If the manifest denies, this standard never runs and no policy check is reached. If the manifest allows, every spend through the exposed method remains subject to Section 5 in full. A wallet MUST NOT treat an enabled spend capability as evidence that any policy permits a spend, and MUST NOT treat a valid policy as grounds to expose a method the manifest withholds.

### A payee whose address changes per invoice: BRC-166

BRC-166 (*P2PKH Payments for HTTP 402*) specifies a publicly-verifiable profile for HTTP `402 Payment Required` on BSV, and its Section 5.4 is categorical: the origin "MUST issue a **fresh receiving address for every invoice**, and MUST NOT reuse an address across invoices". The derivation is "an **origin-local matter** and is deliberately not specified", and the payer "neither knows nor needs to know" it.

**This standard cannot bound such a payee, and an implementer should know that before designing around it.** Section 3.1's `dest_allowlist` is a signed enumeration of canonical destinations. A BRC-166 origin presents a destination that is fresh per invoice and that the payer cannot derive, predict or verify, so there is nothing to enumerate at signing time. Every invoice would fail check 5 as off-allowlist, and on a policy that escalates rather than rejects, every invoice would refer to a human, which is the opposite of the unattended operation this standard exists to provide. Nor does the narrow admission path of Section 5.4 reach it: that admits an address the wallet has verified as controlled by an identity **already in** `dest_allowlist`, and a BRC-166 address is derived from a secret the origin does not share.

The gap is real rather than editorial, and closing it needs a bound this version does not define: a signed entry naming the **payee** rather than the destination, so that an address arriving inside an invoice from that payee is admitted under a per-invoice cap, a period cap and a rate limit. That moves the boundary from *which address* to *which counterparty, how much, how fast*, and it widens what a signature authorizes, so it belongs in a revision with its own vectors and threat analysis rather than in a late addition to this one. Until then, a wallet MUST NOT admit a per-invoice destination by any looser reading of Section 5.4, and an operator whose agent must pay a BRC-166 origin should treat that as outside this standard's scope.

### Why this is not a reserved-namespace module

BRC-98 (*P Protocols*) and BRC-111 (*P Labels*) reserve the `p` prefix so future schemes can extend wallet behaviour without colliding, in protocol identifiers and in action labels respectively. BRC-177 uses the second of these, defining the BRC-111 module ID `nosend` and labels of the form `p nosend expiry seconds <duration>`. That is a working precedent for module-defined wallet behaviour, and it is worth stating plainly why this standard does not follow it.

A BRC-111 label is carried by one action and describes that action. BRC-111 itself frames labels as organizational metadata whose permissioning a module may extend. The subject of this standard is not an action. It is a signed record with a lifecycle of its own, granted, versioned, superseded and revoked independently of any transaction, and evaluated against accumulated state that spans every action an agent has taken inside a period. Expressing it as a label would attach a standing authorization to one of the many events it governs, and would leave the record itself with no defined home.

The permission lifecycle that this standard needs is the one BRC-116 already specifies: declaration, grant, renewal, revocation and persistence. That is why this standard extends BRC-116 rather than claiming a reserved namespace. The reserved namespaces remain available and are not foreclosed: a wallet that wishes to expose policy-governed actions to `listActions` filtering MAY register a BRC-111 module and label such actions, and this standard neither requires nor prevents it. Nothing in the enforcement semantics of § 5 depends on a label being present, and a wallet MUST NOT treat the presence or absence of any label as evidence that a policy applies.

## Specification

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

### 1. Roles

* **Operator**: the human wallet owner. The only party who may author, sign, or revoke a policy. All operator actions are *attended* (require the wallet's normal unlock/confirm).
* **Agent**: autonomous software presenting a bearer token to the wallet's local interface. Untrusted; may *request* anything.
* **Wallet (enforcement point)**: holds keys, evaluates every request against the policy, performs the atomic ledger accounting, and signs/broadcasts only permitted spends.
* **Issuer**: the operator's wallet identity key that signs a `PolicyRecord`, making it tamper-evident and attributable to an attended operator action rather than to the agent.
* **Agent Account**: a BRC-42/43-derived, isolated account funded with a capped allowance, dedicated to exactly one agent. Its balance is the hard blast-radius cap.

### 2. Architecture

Each agent is bound to one isolated agent account and one signed `PolicyRecord`. The wallet exposes a loopback (or onion-fronted) BRC-100 interface. An agent authenticates with a per-agent **bearer token** (Section 4) presented in the `X-Agent-Token` header; a valid token resolves to a binding `(agent_id, account_id, allowance)`. A `createAction` carrying a valid agent token is routed to the **policy-gated headless spender** instead of the attended path. The wallet MUST enforce the policy for every such request (Section 5); the agent is never trusted to self-limit.

### 3. The signed PolicyRecord

A `PolicyRecord` is an immutable policy for one `(agent_account, agent_identity)` pair. Wherever it is stored or carried it is represented as a **signed envelope**. An implementation is not required to accept one over a network interface, and one that reads its envelope only from a path the operator controls is conformant: what makes an envelope trustworthy is its signature, never the channel it arrived on. The envelope is: a canonical JSON *payload* (the fields below) plus an issuer signature over that payload. The payload fields, their canonical destination values, and every enforcement semantic in Section 5 are normative and are implemented by the reference engine; the JSON canonicalization and BRC-3 signature envelope (Sections 3.3-3.4) are the normative interoperable serialization that makes the record tamper-evident and portable between implementations.

#### 3.1 Payload fields

A row marked MUST is required **on the wire**. Where such a row also names a value, that value is what a conforming producer writes when it has no better one, and NOT a default a verifier supplies for an absent field: a payload missing any MUST field is rejected before any default could apply. The exact-fields rule of this section and the defaults are therefore not in tension.

All satoshi and time values are non-negative integers. Times are integer Unix epoch seconds. Durations are integer seconds.

| Field                    | Type                        | Req. | Meaning / constraint                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------------ | --------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                   | string                      | MUST | Domain separator. MUST equal the exact literal `"brc-181/agent-policy/1"`, which carries this standard's number zero-padded to three digits. A verifier MUST reject any other value, and the string MUST NOT change without a new standard, because every signature ever issued is bound to it.                                                                                                                                                                                                                                      |
| `origin_token`           | string                      | MUST | The agent identity this policy authorizes. MUST equal the `agent_id` of the bearer-token binding and the `origin` argument the enforcement engine receives. This is the pinned agent identifier, NOT the secret bearer token.                                                                                                                                                                                                                                                                                                        |
| `account_id`             | integer ≥ 0                 | MUST | The isolated agent account this policy may spend from. Its numbering is wallet-internal and carries no cross-implementation meaning, but it is **not inert**: an engine MUST refuse to open a ledger for this envelope against any account other than the one named here, and MUST refuse to debit any other account under it. This is the check that makes the isolation requirement of Section 10 enforceable rather than advisory. A wallet that does not number accounts MAY use any stable non-negative integer, including `0`. |
| `issuer`                 | string (hex)                | MUST | 33-byte compressed secp256k1 public key (66 lowercase hex chars) that signed this payload. MUST equal the wallet's pinned issuer key (Section 3.4).                                                                                                                                                                                                                                                                                                                                                                                  |
| `issued_at`              | integer ≥ 0                 | MUST | Issuance time (epoch seconds). Used for supersession ordering (Section 9).                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `nonce`                  | string (hex)                | MUST | ≥ 8 bytes of issuer-chosen entropy (lowercase hex). Makes two otherwise-identical policies distinct and defeats replay of a superseded policy.                                                                                                                                                                                                                                                                                                                                                                                       |
| `per_tx_cap`             | integer ≥ 0                 | MUST | Max satoshis a single action's **payout** may spend. `0` denies all payouts; note that a zero-payout action still incurs a fee, so a wallet MUST also apply `max_fee` (below) rather than treating `0` as a total freeze.                                                                                                                                                                                                                                                                                                            |
| `period_cap`             | integer ≥ 0                 | MUST | Max satoshis spent (payout + fee) within the rolling `period_window_s`.                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `max_fee`                | integer ≥ 0                 | MUST | Max satoshis of **miner fee** a single action may incur. Checks 4, 6, 7 and 9 test the payout; the fee is not known until the transaction is built, so it is bounded separately here and re-checked after `build_tx` (Section 5.3). `0` forbids any fee-bearing action.                                                                                                                                                                                                                                                              |
| `period_window_s`        | integer ≥ 1                 | MUST | Rolling-period width in seconds.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `total_budget`           | integer ≥ 0                 | MUST | Lifetime allowance ceiling (payout + fee). Hard stop.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `dest_allowlist`         | array                       | MUST | Canonical destinations (Section 3.2) permitted at the global `per_tx_cap`. MAY be empty (deny all un-capped destinations). Sorted ascending by Unicode code point, de-duplicated.                                                                                                                                                                                                                                                                                                                                                    |
| `rate_limit`             | integer ≥ 0                 | MUST | Max actions per `rate_window_s`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `rate_window_s`          | integer ≥ 1                 | MUST | Rate window width in seconds. Default 60.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `expiry_epoch`           | integer ≥ 0                 | MUST | Policy auto-revokes at this epoch. `0` = no expiry.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `purpose`                | string                      | MUST | Free-form, human-readable UTF-8 shown in the audit log. MUST NOT affect enforcement. MAY be empty.                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `cb_burst_per_s`         | integer ≥ 0                 | MUST | Circuit breaker: more than this many actions within any 1-second window trips a freeze. `0` disables.                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `cb_max_new_dests`       | integer ≥ 0                 | MUST | Circuit breaker: more than this many first-seen destinations within the current period trips a freeze. `0` disables. A destination present in `dest_allowlist` or `dest_caps` at the time of the request MUST NOT be counted: the operator curated it into the signed grant, so it is not a novel destination and counting it would let ordinary on-policy fan-out latch a freeze only a human can clear. The breaker exists to notice destinations the policy did not anticipate.                                                   |
| `escalate_over_per_tx`   | boolean                     | MUST | If `true`, an over-per-tx-cap request yields ESCALATE (attended fallback) instead of REJECT. Default `false` (reject-fast).                                                                                                                                                                                                                                                                                                                                                                                                          |
| `escalate_off_allowlist` | boolean                     | MUST | If `true`, an off-allowlist request yields ESCALATE instead of REJECT. Default `false`.                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `dest_caps`              | array<\[string, integer≥1]> | MUST | Per-destination cap overrides: `[canonical_dest, tighter_per_tx_cap]` pairs. A listed destination IS allowlisted, but under its own, typically tighter, ceiling. This admits a lower-trust destination (unverified contact / raw address) with a bounded blast radius. It can only TIGHTEN (the global `per_tx_cap` still applies first). Sorted ascending by the `canonical_dest` element; each `canonical_dest` unique.                                                                                                            |
| `can_create_children`    | boolean                     | MUST | Whether this grant authorizes the agent to create child agents at all. When `false` an engine MUST refuse every delegation regardless of engine-side configuration, and `child_budget_ceiling` MUST be `0`, since a ceiling on children that cannot be created states a bound an auditor would read as a grant.                                                                                                                                                                                                                      |
| `delegation_depth`       | integer ≥ 0                 | MUST | **This record's own depth** in the delegation tree: `0` is the top-level policy-governed agent, and a child created under it carries `1`. It is authority because it decides whether this record may delegate further, and an engine MUST refuse `can_create_children` on a record already at the deepest level it supports. Signing it is what stops an engine relocating a child to depth `0` and handing it an authority the issuer never granted.                                                                                |
| `child_budget_ceiling`   | integer ≥ 0                 | MUST | Maximum `total_budget` any child record derived under this grant may carry. A child whose record exceeds it MUST be refused at creation, never silently clamped.                                                                                                                                                                                                                                                                                                                                                                     |

A payload MUST contain exactly these fields, no more, no fewer. An enforcement engine MUST reject a payload with an unknown field, a missing required field, a wrong JSON type, a negative numeric, a value outside the range its row states, or a `type` it does not recognize (fail-closed). The range clause is not redundant with the type clause: `period_window_s` and `rate_window_s` are specified as `integer ≥ 1`, and a signed payload carrying `0` for either is a well-typed non-negative integer that prunes its window to nothing and switches that check off, so an engine that tests only type and sign accepts a policy with two of its six enforcement axes silently disabled.

An engine MUST reject a payload whose `dest_allowlist` or `dest_caps` carries more than **1024** entries, and MUST reject one whose canonical bytes exceed **256 KiB**. Both are stated as fixed numbers rather than as advice because a policy is signed: an engine that finds one too large cannot trim it and re-sign it, so a bound an implementer chooses privately is a bound at which an otherwise valid grant becomes unloadable on one engine and fine on another. A request is separately bounded: an engine MUST reject a `createAction` carrying more than **1024** outputs before it parses them, since Section 6 holds the per-account lock across the whole parse-and-check pipeline and an unbounded output array is therefore a denial of service against every other spend on that account.

#### 3.2 Canonical destination format

Destination matching is performed over a **canonical destination string**, not over a raw locking script or an address. This closes the non-P2PKH allowlist bypass: an operator can allowlist a specific non-standard script by its hash, and any unknown script simply fails to match. Given a locking script, an engine MUST compute the canonical destination as follows.

**Script normalization.** The locking script MAY be presented as raw bytes, an array of byte-valued integers, a hex string, or a standard base64 string. An engine MUST normalize to raw bytes by: bytes/array → the bytes; string → attempt hex decode first, and if that fails, base64 decode. Any other form MUST be rejected as malformed.

**Canonicalization.** Let `b` be the normalized script bytes.

```
if len(b) == 25
 and b[0]==0x76 and b[1]==0xA9 and b[2]==0x14
 and b[23]==0x88 and b[24]==0xAC: # OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG
 canonical = "p2pkh:" + lowerhex(b[3..23]) # the 20-byte hash160
else:
 canonical = "script:" + lowerhex(SHA256(b)) # single SHA-256 of the whole script, 32 bytes
```

The `p2pkh:` form is the P2PKH `hash160` (40 hex chars). The `script:` form is the single SHA-256 of the entire locking script (64 hex chars). An engine MUST use exactly one SHA-256 (not double-SHA-256) for the `script:` form.

**OP\_RETURN data outputs.** A data output whose script begins with bare `OP_RETURN` (`0x6a`) or with `OP_FALSE OP_RETURN` (`0x00 0x6a`) is data, not a payout: it contributes **no destination** and MUST NOT be matched against the allowlist.

**Such an output MUST carry `satoshis` equal to `0`, and an engine MUST REJECT a request in which any OP\_RETURN-prefixed output carries a non-zero value** (`"value on a data output"`). The value still counts toward the request total per Section 5.1, so the caps bound the amount, but the caps are not the control at issue here: an output that contributes no destination is invisible to the allowlist, to `dest_caps` and to the new-destination breaker of check 10, so without this rule an agent could move the whole budget to destinations the operator never approved and trip no anomaly detector. The form matters too. Post-Genesis, `OP_RETURN` terminates script evaluation and the outcome is decided by what the unlocking script left on the stack, so `OP_FALSE OP_RETURN` is provably unspendable and a bare `OP_RETURN` output is **redeemable by whoever spends it first**. Value on a bare-`0x6a` output is therefore not burned, it is payable to an anonymous third party, which is precisely the destination-free payout this standard exists to prevent.

#### 3.3 Payload canonicalization (for signing and verification)

The signature preimage is the payload serialized deterministically as UTF-8 bytes:

1. A single JSON object containing exactly the Section 3.1 fields.
2. Object member keys sorted ascending by Unicode code point.
3. No insignificant whitespace: `,` between members, `:` between key and value, nothing else.
4. **Integers**: minimal decimal, no leading zeros (except the literal `0`), no `+`, no exponent, no fractional part. All numeric policy fields are integers in canonical form, and every one MUST be in `[0, 2^53 - 1]`. The upper bound is interoperability, not thrift: a JSON parser that represents numbers as IEEE-754 doubles, which is the default in the language most BRC-100 wallets are written in, cannot round-trip an integer above 2^53, so a payload carrying one canonicalizes to different bytes on two conforming engines and its digest does not reproduce. A payload with any numeric field outside that range MUST be rejected. (Where a reference implementation stores `expiry_epoch` or `cb_burst_per_s` as a float internally, it MUST canonicalize to the equivalent integer: `expiry_epoch` = whole epoch seconds, `cb_burst_per_s` = whole actions-per-second; the enforcement comparisons are unaffected.)
5. **Booleans**: `true` / `false`.
6. **Strings**: RFC 8259 encoding with the mandatory escapes (`\"`, `\\`, `\b`, `\f`, `\n`, `\r`, `\t`) and every non-ASCII code point and other control character escaped as `\uXXXX` with **lowercase** hex digits (i.e. ASCII-only output). Three further rules are REQUIRED, because each is a degree of freedom a common serializer resolves the other way and each changes the digest:
   * The solidus `/` is **NOT** escaped. Every `type` value contains two, so a serializer that escapes it by default produces a different digest for every policy ever signed rather than only for unusual ones.
   * A character above U+FFFF is escaped as the **UTF-16 surrogate pair**, two `\uXXXX` escapes. A six-digit or `\U`-style escape is not this form.
   * Strings are compared and canonicalized as the exact code-point sequence received. No Unicode normalization is applied. `purpose` and `origin_token` are the fields that carry arbitrary text, and an engine that normalized either would produce a digest the issuer's signature does not cover.
7. **`dest_allowlist`**: array of canonical-dest strings, sorted ascending by Unicode code point, de-duplicated.
8. **`dest_caps`**: array of 2-element arrays `[canonical_dest, cap]`, sorted ascending by the `canonical_dest` element; `canonical_dest` values unique.

Let `C` be these canonical bytes. The **payload digest** is `SHA256(C)` (single SHA-256, 32 bytes).

#### 3.4 Signature envelope

A signed policy is the JSON object:

When present, `canonical` MUST be a JSON **string** holding those bytes directly, not base64, not hex, and not an array of byte values. Rule 6 above makes the canonical bytes ASCII-only, so they are always representable as a JSON string; without this sentence a producer whose language marshals a byte array as base64 by default and a verifier that expects a literal string will disagree on a byte-perfect, correctly-signed envelope, and the mismatch rule below obliges the verifier to reject it and report a tamper failure that did not occur.

An envelope MAY additionally carry `canonical`, the exact byte string the producer serialized and signed. When present a verifier MUST compare it against the bytes it recomputes and reject a mismatch, which is how step 1 below detects a payload that arrived in a non-canonical form. When absent a verifier recomputes the canonical bytes and verifies against those, so a payload whose serialization was not canonical still verifies: the signature covers the canonicalized document either way, and what is lost is the ability to notice. A producer that wants its counterparty to detect a non-canonical serialization MUST emit `canonical`.

```json
{ "payload": <canonical payload>, "sig": {
 "alg": "ECDSA-SHA256-secp256k1",
 "issuer": "<33-byte compressed pubkey, hex>",
 "signature": "<DER-encoded ECDSA signature, hex>" } }
```

* `alg` MUST be `"ECDSA-SHA256-secp256k1"`.
* `sig.issuer` MUST byte-equal `payload.issuer` (the issuer is bound inside the signed payload as well, defeating key-substitution ambiguity).
* `signature` is a DER-encoded ECDSA `(r, s)` over the payload digest of Section 3.3, produced with the private key corresponding to `issuer`. Signatures SHOULD be low-S normalized. Deterministic (RFC 6979) nonces are RECOMMENDED.

**Verification (an engine MUST, before accepting a policy):**

1. Parse the envelope; recompute the canonical payload bytes `C` from `payload` per Section 3.3 and confirm they reproduce the received serialization (reject if the received bytes were not canonical).
2. Confirm `sig.issuer == payload.issuer` and that this key equals the wallet's **pinned issuer key**, a build-/config-pinned identity key the operator controls. An engine MUST NOT accept an issuer key supplied only in the record or by the agent; the pin is authoritative. A policy signed by any other key MUST be rejected.
3. Verify the DER signature over `SHA256(C)` against `issuer`. Reject on failure. A verifier MUST **parse** the signature as DER rather than index into it: the SEQUENCE tag, the outer length, both INTEGER tags and both integer lengths MUST be checked, the outer length MUST cover exactly the remaining bytes, neither integer may carry a negative value or an unnecessary leading zero, and nothing may follow the second integer. An implementation that reads `r` and `s` from fixed offsets will accept a signature whose tags are wrong, whose declared length is wrong, or which has bytes appended, so one signature gains unboundedly many accepted encodings. Anything that treats the signature bytes as an identity, a de-duplication key or a replay marker can then be walked past using a re-encoding of a signature it has already seen.
4. Enforce structural validity per Section 3.1.

Only a policy that passes all four steps may be loaded into an allowance ledger. This ties every autonomous-spend authority to an attended, operator-signed artifact.

#### 3.5 Operator-side fields, and the line they MUST NOT cross

Section 3.1 fixes the signed payload exactly. An engine will nonetheless hold state about an agent that no issuer signed: a wallet-side deny-set, per-category sub-budgets, a note about who approved what. Nothing in this standard forbids that, and forbidding it would be unenforceable.

**The line is authority.** An unsigned field MAY narrow what the signed policy permits. An unsigned field MUST NOT grant anything the signed policy does not already permit, and MUST NOT enlarge any bound the signed policy sets. This is the same invariant Section 5.4 places on interposed checks, applied to state instead of to code, and for the same reason: the envelope is the record of what the issuer authorized, and an engine that adds authority beside it makes the signature describe less than the wallet will do.

Delegation is where this bites, and an implementation should read it carefully. If an engine lets a policy-governed agent create child agents with budgets of their own, then the authority to delegate, its depth, and the ceiling on what a child may hold are all authority. **They are therefore signed fields**, `can_create_children`, `delegation_depth` and `child_budget_ceiling` in Section 3.1, and an engine MUST read them from the verified envelope and from nowhere else. An engine that took them from operator-side configuration would have an agent minting spenders under a grant whose signed text says nothing about delegation, and an auditor reading the envelope could not see the tree it produced.

Two bounds that a child *creation* may carry, namely how many children and which child types, are deliberately **not** signed. Both can only ever narrow what the three signed fields already permit: a lower child count and a narrower type list each admit a subset of the trees the signature authorizes, so they fall under the general rule above rather than the exception. An engine MUST NOT read any unsigned field as raising `delegation_depth`, enabling creation when `can_create_children` is `false`, or lifting `child_budget_ceiling`.

A child's own record MUST be attenuated against this envelope and re-checked against it before the child may act: no child may carry a `total_budget` above `child_budget_ceiling`, a child's own signed record MUST carry a `delegation_depth` greater than its parent's, and a child MUST NOT itself delegate unless its own signed record says so and its depth is not already the deepest the engine supports. An implementation that supports delegation and cannot express these fields in a signed payload MUST refuse to delegate rather than delegate unsigned.

### 4. Authentication and origin binding (bearer token)

Autonomous requests MUST NOT be authorized on the basis of a client-supplied `Originator`/`Origin` header: such a header is forgeable, and a missing one collapses distinct agents into one shared bucket, merging budgets. Instead:

* Each agent presents a **bearer token** of the form `agt_` + a URL-safe base64 encoding of ≥ 32 bytes of CSPRNG entropy (the reference uses `secrets.token_urlsafe(32)`, yielding 43 chars → 47 with prefix). The token is a high-entropy secret. It MUST NOT be derived from the request and MUST NOT be logged.
* The token is presented in the substrate's request metadata. Over BRC-5 (HTTP) that is the `X-Agent-Token` header; substrates without headers (BRC-6, BRC-7) carry it in their own message envelope. This standard does not require any particular carriage. A valid token resolves to a binding `(agent_id, account_id, allowance)`.
* Token→binding resolution MUST be constant-time across all known tokens (e.g. `hmac.compare_digest` against each) so a caller cannot time-probe valid token values.
* At mint time the engine MUST enforce `binding.agent_id == allowance.policy.origin_token` (defense in depth: an allowance only ever authorizes its own bound agent).
* The `origin` value the enforcement engine evaluates (Section 5, check 3) is the resolved `agent_id`. The literal string `"local-unspecified"` MUST always be rejected on the autonomous path.
* Tokens are session-scoped and held in memory only for the enabled session. `revoke(token)` and `revoke_all()` (Section 9) are instant.

A `createAction` that does not resolve to a valid agent-token binding MUST fall through to the wallet's normal attended path; it MUST NOT be autonomously spent.

### 5. Enforcement semantics, per-`createAction` checks

For every `createAction` routed to the autonomous path, the engine MUST perform the following, in this order, under the atomic ledger discipline of Section 6. The engine returns a verdict ∈ {`ALLOW_AUTO`, `ESCALATE`, `REJECT`}. `REJECT` denies; `ESCALATE` diverts to the attended confirm+password path; `ALLOW_AUTO` proceeds to reserve/sign/broadcast.

#### 5.1 Request parsing

Parse the request's `outputs` into `(total, dests)`:

* `total` = Σ `satoshis` over all outputs. An output with a non-integer or negative `satoshis` MUST cause the whole request to be rejected as malformed (REJECT).
* `dests` = the canonical destination (Section 3.2) of every non-OP\_RETURN output. OP\_RETURN data outputs contribute to `total` (normally 0) but never to `dests`.

`total` is the **payout** used by the cap checks below. (The miner fee is added later, at debit time; see Section 6.)

#### 5.2 Check pipeline (ordered; first failure wins)

1. **Kill switch (frozen), FIRST.** If the allowance is frozen, REJECT (`"frozen: <reason>"`). A freeze denies everything, in-flight included.
2. **Expiry.** If `expiry_epoch != 0` and `now >= expiry_epoch`, REJECT (`"policy expired"`).
3. **Origin binding.** If `origin == "local-unspecified"` or `origin != policy.origin_token`, REJECT (`"origin not bound to this agent policy"`).
4. **Per-tx cap.** If `total > per_tx_cap`, → ESCALATE if `escalate_over_per_tx` else REJECT (`"over per-tx cap"`).
5. **Destination allowlist.** Let `off` = destinations in `dests` that are neither in `dest_allowlist` nor keys of `dest_caps`. If `off` is non-empty, → ESCALATE if `escalate_off_allowlist` else REJECT (`"off-allowlist destination(s)"`).
6. **Per-destination cap (tighten-only).** Let `dcaps` = the caps of those `dests` that carry a `dest_caps` entry. If `dcaps` is non-empty and `total > min(dcaps)`, REJECT (`"over per-destination cap"`). (Because check 4 already applied the global cap, a `dest_caps` entry can only tighten, never loosen.)
7. **Rolling period cap.** With `period_spent` = Σ debits in the rolling `period_window_s` window, if `period_spent + total > period_cap`, REJECT (`"over period cap"`).
8. **Rate limit.** If the count of actions in the rolling `rate_window_s` window `>= rate_limit`, REJECT (`"rate limit exceeded"`).
9. **Total budget (hard stop).** If `spent_total + total > total_budget`, REJECT (`"total budget exhausted"`).
10. **Circuit breaker.** If `cb_burst_per_s != 0` and `(burst_count + 1) > cb_burst_per_s`, set frozen and REJECT (`"circuit breaker: burst"`). If `cb_max_new_dests != 0` and `(distinct_seen_dests + new_distinct_dests_here) > cb_max_new_dests`, set frozen and REJECT (`"circuit breaker: too many new destinations"`). A tripped breaker MUST latch the freeze (subsequent requests fail at check 1) until an attended operator unfreeze.

**What ESCALATE means, and what it does not.** The pipeline is first-failure-wins, so an ESCALATE at check 4 or check 5 ends it: checks 5 to 10 do not run, and that includes the allowlist, the per-destination caps, the period cap, the rate limit, the `total_budget` this standard calls a hard stop, and both circuit breakers. An untrusted requester chooses the amount, so on a policy with `escalate_over_per_tx` set the requester chooses when to leave the policy engine. Three rules bound that:

* ESCALATE is **not a verdict on the request**, it is a referral. It MUST NOT cause a spend. The engine MUST hold the request and MUST NOT sign or broadcast anything until an attended operator approves it.
* An operator approval MUST re-enter the pipeline at check 1 with the approval recorded, and every remaining check MUST run against the state current at approval time. A request approved at check 4 that would fail check 7, 9 or 10 MUST still be rejected; approval lifts exactly the one bound that referred it, never the rest.
* On approval the debit follows the ordinary ALLOW\_AUTO path of Section 6. An engine that commits no debit for an approved escalation lets a policy be drained one approval at a time, and one that re-refers the same request forever never completes it.

An engine that cannot present an attended path MUST treat `escalate_over_per_tx` and `escalate_off_allowlist` as `false` regardless of what the policy says, and reject rather than refer. Referring to nobody is a silent allow-by-timeout in every implementation that later grows a timeout.

**Reason strings.** The parenthesised strings above are the normative reason for each rejection, and an engine MUST return them verbatim. They are observable to the agent and they identify which check fired, so two engines that word them differently are two engines a caller cannot write one handler against; and because the pipeline is ordered and first-failure-wins, the reason is also the only evidence available that the checks ran in the order this section specifies. An engine MAY append implementation detail after the normative prefix, separated by `": "`, and MUST NOT alter the prefix itself.

If all checks pass, the engine proceeds to the ALLOW\_AUTO reservation (Section 6). Note that checks 4, 6, 7 and 9 test the **payout** `total`, because the miner fee does not exist until the transaction is built. Where the builder reports a fee, that fee is folded into the debit and Section 5.3 re-runs checks 7 and 9 against payout plus fee before any counter moves, so on that path there is no budget slack: a built transaction whose fee would carry a counter past its cap is rejected, not absorbed. Where the builder reports no fee at all, the engine has nothing to fold in and the caps bound the payout only; that case, and its consequences, are set out in Section 5.3 under *Builders that report no fee*.

#### 5.3 Post-build fee re-check (normative)

Checks 4, 6, 7 and 9 run before the transaction exists, so they can only test the payout. The miner fee is chosen by the builder and is not knowable at that point, yet it is spent from the same account. An engine that stops here authorizes an action whose payout is `0`, passing every payout check trivially, while debiting `0 + fee`, with `fee` sized by whoever composed the request. Output count and data size are likewise unbounded before the build.

**What `build_tx` reports.** A builder either tracks miner fees or it does not, and the two cases are not the same. A builder that does not track fees at all reports no fee, and the engine treats the fee as `0`: the fee is structurally absent, not unobserved. A builder that DOES track fees reports either a definite satoshi count or an explicit UNKNOWN, and UNKNOWN is a value it MUST be able to express, because a builder that cannot read the fee of the transaction it just built has to be able to say so. An engine MUST NOT substitute `0` for UNKNOWN (check 1 below), and MUST NOT accept a reported fee that is not an integer number of satoshis: coercing one (truncating a fractional value, for instance) debits less than the transaction spends.

Therefore, after `build_tx` returns and **before** signing, still holding the Section 6 lock, the engine MUST apply these five checks, in this order:

1. REJECT (`"fee could not be determined"`) if the builder tracks fees and reported UNKNOWN, or reported a value that is not an integer number of satoshis. An unknown fee MUST NOT be charged as `0`: `fee > max_fee` is false for every `max_fee` when `fee` is `0`, so charging zero switches check 3 off entirely, and it does so precisely on the transactions nobody could measure. This is the fail-closed rule of Section 7 applied to the only pre-broadcast unknown that would otherwise fail open.
2. REJECT (`"negative fee rejected"`) if `fee < 0`. A negative fee is not a discount. The debit is `total + fee`, so a negative one CREDITS the ledger and re-opens budget that was already spent. No check below catches it: a negative fee is under every `max_fee`, and it makes `total + fee` smaller than the payout that checks 7 and 9 have already passed.
3. REJECT (`"fee cap exceeded"`) if `fee > max_fee`;
4. re-run check 7 against `period_spent + total + fee`, REJECT (`"period cap exceeded"`) on failure;
5. re-run check 9 against `spent_total + total + fee`, REJECT (`"total budget exhausted"`) on failure.

The order is normative because the reason strings are observable, and because checks 1 and 2 must precede check 3: an UNKNOWN or negative fee would otherwise be compared against `max_fee` as though it were a satoshi count.

A rejection here MUST release the coin reservation and MUST NOT debit, by the same full-undo path Section 7 specifies for pre-broadcast failure. Only after all five pass may the engine debit and sign.

**Builders that report no fee.** The five checks above bound a fee the builder reported. A builder that reports none gives the engine nothing to bound, and this is the one path on which a miner fee is absorbed rather than rejected: the wallet pays it from the account, no counter moves for it, and `max_fee`, the period cap and the total budget therefore do not apply to it. The gap is not a small residue on the last spend before a hard stop: it is every fee on every spend, and it is bounded only by the account's balance. It is permitted because a caller that genuinely does not track fees is a legitimate integration and forcing it to invent a number would be worse. Implementers MUST NOT read this as an alternative to reporting UNKNOWN: reporting no fee says *this builder never tracks fees*, a property of the integration, while UNKNOWN says *I track fees and could not read this one*, a property of one transaction, and only the second is denied. An engine SHOULD make the distinction visible to the operator, since a policy carrying a `max_fee` and a builder reporting no fee is almost always a wiring mistake rather than a choice.

The verdict-to-severity mapping (ESCALATE vs REJECT) is governed solely by `escalate_over_per_tx` and `escalate_off_allowlist`. The RECOMMENDED default for high-frequency agents is reject-fast (both `false`): anything off-policy is instantly rejected so the agent never stalls behind an attended prompt.

#### 5.4 Implementation-specific checks, and the one rule they MUST obey

An engine MAY interpose additional checks in the pipeline of Section 5.2, and a useful one will. The reference interposes several: an operator-only destination deny-list, per-category sub-budgets with their own rolling windows, and a separate pipeline in front of the ledger for delegated child accounts.

**The invariant is that an added check MAY refuse a request the signed policy would have allowed, and MUST NOT admit one the signed policy would have refused.** A policy is a grant with an upper bound. An operator who tightens their own grant has narrowed a thing they own; an engine that widens it has spent authority nobody signed for, and the signed envelope stops describing what the wallet will do. Every interposed check must therefore be expressible as an additional reason to say no.

Two consequences an implementer should read carefully, because both are reachable:

* **A second admission path for destinations is a widening unless all four conditions below hold.** The hazard is concrete: if an engine admits a destination because some wallet-side set vouches for it, then with an empty `dest_allowlist` it will allow a payment the policy admits nowhere, and the allowlist stops being the boundary the issuer signed. A wallet-side set of trusted destinations is a legitimate thing to keep, and one case is narrow enough to admit: an identity already named in `dest_allowlist` has rotated to a new address and has proved control of it to the wallet. An engine MAY admit such a destination if and only if **all four** of these hold, and MUST NOT otherwise: the set is computed wallet-side and no part of the request can add to it or influence it; every member is an address the wallet has itself verified as controlled by an identity that is already in `dest_allowlist`; the set admits nothing while `dest_allowlist` is empty; and each admitted destination is bounded by the same per-destination cap that would apply to the allowlist entry it was derived from. Those four are what keep this a resolution of an allowlisted identity rather than a second allowlist. An engine that admits on any looser basis has widened the grant and is not conformant. Otherwise a wallet-side set belongs on the operator's side of the signature, curating what goes INTO `dest_allowlist` before signing, which is what Section 8 describes.
* **Enforcing a cap other than the signed one is a widening whenever it is looser, and a silent change of meaning even when it is tighter.** An engine that scales the signed caps by a trust factor during a warmup period is enforcing numbers the issuer did not sign. If the factor is at most one it never widens, so it satisfies the invariant, but a reader of the envelope can no longer predict the wallet's behaviour from the envelope. An engine that does this MUST report the effective cap alongside the signed one wherever it reports a refusal, so the difference is visible rather than inferred from a rejection that does not match the arithmetic.

#### 5.5 Every rejection is a verdict, never an exception

An engine parses an untrusted request before it can check anything, and parsing fails in ways the checks never see: a locking script that is not hex, an output with no locking script at all, a `satoshis` member that is a JSON boolean rather than a number. Section 7 already says a malformed request is rejected, and this is what that requires of an implementation.

**An engine MUST convert every parse failure into the same REJECT verdict the checks produce, and MUST NOT allow an exception to escape the authorization call.** A caller receives a verdict or it receives nothing, and nothing is indistinguishable from a crash. In a server the difference is visible: an escaping exception drops the connection without a reply, so an agent cannot tell a refusal from a fault and a retry loop cannot tell whether retrying is safe.

Three cases are named because each has a plausible wrong answer:

* **A JSON boolean is not an amount.** In several languages a boolean is a subtype of integer, so `true` passes a naive integer test and is counted as one satoshi against every cap. Reject it.
* **A missing amount is not zero.** An output with no `satoshis` member contributes nothing to the total while its destination still faces the allowlist and breaker checks, so a request can pass a budget it never consumed. Reject the request rather than defaulting the member.
* **A malformed header is not an absent header.** A token that cannot be decoded MUST take the same path as a token that is wrong, which Section 4 already fixes as rejection. It MUST NOT take the path of a request that carried no token at all, and MUST NOT fault.

#### 5.6 A control that needs a timer MUST name what starts it

Two of this standard's fail-closed rules describe a state that is entered on one event and left on another: a reservation is held when a broadcast outcome is uncertain and released when the outcome is reconciled, and a freeze latches until an operator clears it. The first half of each is driven by the request that caused it. The second half is driven by nothing unless an implementation arranges it.

**An implementation that holds state pending reconciliation MUST name the mechanism that performs the reconciliation, and that mechanism MUST run without an operator remembering to run it.** A held reservation with no driver is not a conservative default. It is a leak: coins stay reserved, the next legitimate request finds nothing spendable, and the failure surfaces as an apparently empty wallet rather than as the stuck reservation it is. An operator debugging that message looks in exactly the wrong place.

Where an implementation cannot guarantee the driver runs, it MUST make the held state visible: a reservation older than the reconciliation window MUST be reported by whatever the operator inspects for health, so a control that has stopped running is distinguishable from one with nothing to do.

#### 5.7 Audit isolation covers every path, including the ones that fail early

Section 7 requires that a failing audit sink never change a spend decision. An implementation that routes its ordinary verdicts through an isolating wrapper will still let a sink exception escape from the paths that return before reaching it, and a malformed request is exactly such a path: it is rejected early, so it never reaches the wrapper the normal path relies on.

**Audit isolation MUST hold on every path that can emit an audit record, and a path that fails before the checks run is one of them.** The rule of Section 5.5 applies here too: the caller receives a verdict, never an exception, and least of all an exception raised by logging.

#### 5.8 A caller may tighten a ceiling for one request

An engine MAY accept a per-request ceiling from its caller and apply the tighter of that and the signed one. This narrows a single request and never widens it, so Section 5.4 permits it, but the number a refusal is measured against is then not the number in the envelope. An engine that does this MUST report which ceiling refused the request, because an operator reconciling a refusal against the signed policy will otherwise find arithmetic that does not add up.

### 6. Atomic allowance ledger and concurrency safety

The engine MUST maintain, per `(agent_account, origin)`, a thread-safe allowance ledger guarded by **one lock**. The critical section that lock protects is the *entire* check→coin-select→reserve→debit sequence. A language runtime's global lock (e.g. the Python GIL) does NOT make a multi-step check-then-debit atomic; without one explicit lock, two concurrent requests can both pass a cap check and then both debit (overspending) or both select the same coin (double-select). Implementations MUST hold the lock across:

1. the full Section 5 check pipeline,
2. coin selection + reservation (the `build_tx` callback, which excludes already-reserved coins and reserves the chosen ones), and
3. the counter debit.

Signing and broadcasting (network-blocking) MUST happen **outside** the lock, so concurrent high-frequency spends are not re-serialized on the network path.

**Debit.** On ALLOW\_AUTO the engine computes `debit = total + fee`, where `fee` is the miner fee the builder reported for the built transaction, so a reported fee cannot escape the caps. Where the builder reports no fee, `fee` is `0` and the caps bound the payout alone (Section 5.3, *Builders that report no fee*). It then, atomically under the lock: increments `spent_total` by `debit`; appends `(seq, now, debit)` to the rolling-period window; appends `(seq, now)` to the rate window; appends `now` to the burst window; and records each destination as seen. `seq` is a per-ledger monotonic id. The engine returns a **Reservation** carrying `(debit, tx, reserved_coins, seq)`.

**Ledger counters.**

| Counter           | Content                                          | Pruned when                                                                            |
| ----------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `spent_total`     | lifetime debited satoshis (seeded per Section 8) | never (only rolled back)                                                               |
| period window     | deque of `(seq, ts, debit)`                      | `now - ts > period_window_s`                                                           |
| rate window       | deque of `(seq, ts)`                             | `now - ts > rate_window_s`                                                             |
| burst window      | deque of `ts`                                    | `now - ts > 1.0s`                                                                      |
| seen destinations | deque of `(ts, canonical_dest)` + a set          | `now - ts > period_window_s` (set entry dropped only when no recent duplicate remains) |

**Durability.** Every counter in the table above, and the freeze flag of Section 9, MUST survive a restart of the enforcing process. Only `spent_total` can be rebuilt from outside (Section 8 re-seeds it from chain-observed history); the period, rate and burst windows, the seen-destination set and a latched freeze exist nowhere but in the engine, so an engine that holds them only in memory enforces `period_cap` and `rate_limit` per uptime rather than per window. Worse, it treats a process restart as the attended operator unfreeze that check 10 requires. An engine that cannot persist them MUST refuse to open a ledger rather than open one whose limits a restart resets. This is the same requirement, for the same reason, that Section 9 places on supersession state: the attack is a restart.

**Clock.** All window arithmetic is in seconds with a resolution of at least one millisecond; the burst window's `1.0s` width is not a hint that integer seconds suffice. Window comparisons MUST use a monotonic clock, not wall-clock time, so that an NTP step or a VM restore cannot empty a window or un-expire a policy. `expiry_epoch` and `issued_at` are wall-clock epoch values by definition and are exempt.

**Commit / rollback (identity-based).** After signing+broadcast, the caller settles the Reservation exactly once:

* `commit(txid)` marks it settled; counters stay debited.
* `rollback(reason)` re-credits `spent_total` by `debit` and removes **this reservation's own** period-, rate-, burst- and seen-destination entries **by `seq`**, never by popping the last-appended entry. Under concurrency the last entry belongs to a different in-flight spend; popping it would leak this spend's period/rate slot and let the caps be bypassed. Every window entry an engine appends MUST therefore carry the reservation's `seq`, including the burst window and the seen-destination set, which are otherwise not addressable and would leave a never-broadcast spend counting permanently toward both circuit breakers.

A settled reservation MUST ignore a second commit/rollback (idempotent).

### 7. Fail-closed rules

An engine MUST implement all of the following:

* **`noSend` denied.** A `createAction` with `noSend` (sign-and-store without broadcast) MUST be rejected on the autonomous path. A stored-but-unbroadcast spend leaves coins reserved with nothing on-chain to reconcile against, an unbounded reservation and double-select hazard. Agents have no reason to pre-sign.
* **`sendWith` denied.** A `createAction` carrying BRC-100's `options.sendWith` MUST be rejected on the autonomous path (`"sendWith on the autonomous path"`). `sendWith` names transactions created earlier as `noSend` actions and broadcasts them alongside the new one. Denying `noSend` closes only their creation; without this rule an agent submits an action whose own outputs are trivial, passes every check in Section 5.2 on those trivial outputs, and causes arbitrary pre-signed value to be broadcast beside it at **zero policy cost**, because Section 5.1 derives `total` and `dests` from `outputs` alone and never sees the batch.
* **Delayed broadcast denied.** The engine MUST set BRC-100's `options.acceptDelayedBroadcast` to `false` for every request on the autonomous path, and MUST reject a request that asks for `true` (`"delayed broadcast on the autonomous path"`). Its BRC-100 default is `true`, under which the transaction is handed to a background process; there is then no synchronous broadcast at which the freeze re-check below can run, and no return value whose absence the broadcast-uncertainty rule can read. Both of those rules are stated as MUSTs in this section and neither is satisfiable under a delayed broadcast, so an engine that leaves the default in place is not conformant however carefully it implements the rest.
* **Completeness assertion.** Before broadcast the engine MUST assert the signed transaction `is_complete()`. An incomplete signature is a pre-broadcast failure.
* **Pre-broadcast failure ⇒ full undo.** Any failure before broadcast (policy denial, coin-selection failure, sign error, incomplete tx) MUST roll the counters back AND release the reserved coins. Nothing left the wallet, so a full undo is safe.
* **Freeze re-check before broadcast.** After signing and immediately before broadcast (the last point at which the spend is still safely reversible) the engine MUST re-check the kill switch. A freeze that landed mid-sign MUST stop the spend (rollback + release).
* **Broadcast uncertainty ⇒ fail CLOSED.** A broadcast that raises OR returns no txid is ambiguous: the transaction may already be propagating. The engine MUST treat it as possibly-on-chain: keep `spent_total` debited, keep the coins reserved (never re-credited/released), commit the reservation, and surface a distinct `ERR_BROADCAST_UNCERTAIN`. Re-crediting here would let a concurrent spend re-select a coin that is actually confirming, a double-spend. Reconciliation against chain state resolves the true outcome before any retry.
* **Reservation held until reconciled.** On success the coin reservation is held until the spent coin is reconciled out of the account's live UTXO set; only then is the reservation dropped. A concurrent build MUST NOT be able to re-select a just-spent coin.
* **Audit isolation.** Audit/observability is best-effort. A throwing audit sink MUST NOT corrupt the ledger, unwind a fund-state mutation, or turn a successful spend into an exception (which a caller could read as failure and retry, double-spending). Audit failures MUST be isolated and surfaced loudly (e.g. to stderr), never swallowed silently and never fatal to the spend.
* **Malformed request ⇒ REJECT.** Any parse/validation failure in Section 5.1 is a REJECT, never a best-effort spend.

### 8. Budget seeding and graduated-trust destinations

**Chain-authoritative seed.** At startup the ledger's `spent_total` MUST be seeded from chain-observed history, not solely from a local counter, so that a wiped or edited local counter cannot re-open the budget. The seed is the account's net outflow (sum of negative history value-deltas). This is deliberately conservative (it counts fees and full outflow and ignores returned change) so it can only under-credit remaining budget, never re-open it.

**Graduated trust → graduated caps.** A curation layer produces `dest_allowlist` and `dest_caps` from an operator-selected subset of contacts, enforcing three properties:

* **Selective disclosure**: only the selected destinations are exposed to the agent; the rest of the address book stays private.
* **Verified → global cap; unverified/raw → own tighter cap.** A *verified* contact goes on `dest_allowlist` at the global `per_tx_cap`. An *unverified* contact or a *raw* address is admitted ONLY via `dest_caps` under its own, strictly positive, tighter per-tx ceiling, bounding a lower-trust destination's blast radius below everything else.
* **Explicit opt-in (fail-loud).** Admitting a lower-trust destination MUST require both an explicit risk-acknowledgement flag and a positive per-tx cap, or the operation fails loudly. A lower-trust destination can never be admitted by accident.
* **Resolve-then-pin.** A paymail/handle MUST be resolved to an address once, at selection time, and the resulting address pinned; the agent MUST NOT re-resolve per spend (a hijack surface).

### 9. Revocation and supersession

**One envelope authorizes one ledger.** An engine MUST NOT open a second allowance ledger from an envelope it has already accepted. Caps bound spending only while one counter stands behind them: two ledgers built from one envelope are two counters, each permitting the full signed total, and every per-ledger check passes because each ledger really is within the signed caps. Nothing in the caps can detect the doubling, so the rule has to live at acceptance. An issuer who wants a second ledger signs a second envelope with a fresh `nonce`, which is a deliberate act that leaves a record.

**Supersession needs durable state, and an engine without it cannot claim this clause.** The ordering rule is only enforceable if a candidate policy can be compared against something that survives a restart, because the attack IS a restart. An engine holding the current `issued_at` only in memory accepts an older envelope on the next start, restoring the destinations and the looser caps a newer policy had narrowed, with a signature that verifies perfectly and no cap check that can object. Reconciling spend from chain history does not close this: that restores what an agent has SPENT, never the policy's TERMS.

An implementation SHOULD take the durable part from state it already keeps rather than adding a store for it. A log that a compartment already replays to seed its spent totals can carry the policy revision on the same records and return it through the same seeding path, which introduces no new way for a legitimate restart to fail: a log readable enough to seed a budget is readable enough to seed a revision. An implementation with no such state MUST say so rather than present the ordering rule as enforced.

An implementation will usually have more ways to stop an agent than this section lists: a token that expires on its own, a token rotated out from under a binding, a dead-man timer, an account retirement. Any of these is a legitimate additional way to say no, and Section 5.4's rule covers them all. What matters for interoperability is the converse, so it is stated once: **every mechanism that stops authority MUST be observable to the operator, and none of them may be the only thing standing between an agent and a spend.** An engine whose sole protection against a stale grant is a token TTL has a policy that outlives its own enforcement.

The engine MUST support all of:

* **Freeze / unfreeze.** `freeze(reason)` immediately denies everything (check 1); in-flight actions fail closed. `unfreeze` is an attended operator action only.
* **Token revocation.** `revoke(token)` instantly deauthorizes one agent; `revoke_all()` drops every token (session teardown / global kill switch). A revoked token no longer resolves to a binding, so its requests fall off the autonomous path.
* **Total revocation.** The composite operator action drops the token(s) AND freezes the allowance.
* **Expiry.** A non-zero `expiry_epoch` auto-revokes at that time (check 2).
* **Supersession.** Policies for the same `(account_id, origin_token)` are totally ordered by the pair `(issued_at, nonce)`, comparing `issued_at` as an integer first and, when those are equal, the `nonce` strings by byte-wise lexicographic comparison of their lowercase hex. A newly loaded, validly-signed policy replaces the prior one **only if it is strictly greater under that order**, and an engine MUST NOT load one that is less than or equal to the policy currently loaded (replay defense). Equality is included deliberately: a rule written only against a *lower* `issued_at` leaves two policies issued in the same second freely interchangeable, including the looser of the two, and one-second granularity makes that the ordinary case for any scripted re-issue rather than an edge case. An issuer MUST NOT reuse a `nonce` for the same `(account_id, origin_token)`; two records identical in both fields are the same record, and an engine that receives a second one differing in any other field MUST reject it rather than choose between them. Because policies are session-loaded and issuer-pinned, and the budget seed is chain-authoritative, a revoked or superseded policy cannot re-open spent budget.

### 10. Security requirements (summary of MUSTs)

* The wallet MUST enforce the policy; no field of the agent's request is trusted except as input validated against the policy.
* Autonomous authority MUST be scoped to an isolated, capped account, never the main balance or seed.
* Destination matching MUST be over canonical destinations (P2PKH hash160 + script-hash), closing the raw-script bypass.
* The check→reserve→debit critical section MUST be atomic under one per-account lock; signing/broadcast MUST be outside it.
* The post-build fee re-check (Section 5.3) MUST run after `build_tx` and before signing, and it is **five** checks in this order, not three: a fee the builder could not determine (`"fee could not be determined"`), a negative fee (`"negative fee rejected"`), `fee > max_fee` (`"fee cap exceeded"`), and a period cap or total budget that payout plus fee would carry past its ceiling (`"period cap exceeded"`, `"total budget exhausted"`). Each MUST REJECT, release the coin reservation and debit nothing. Without the re-check at all, the cap checks bound the payout only, and a zero-payout action clears every one of them while still spending a fee. Without the first two, an unknown fee charged as `0` clears `max_fee` for every ceiling, and a negative fee credits the ledger and re-opens spent budget.
* Rollback MUST be identity-based (`seq`), never pop-last, and MUST cover every window the request touched, the burst window and the seen-destination set included.
* An OP\_RETURN-prefixed output MUST carry zero satoshis, and a request in which one carries value MUST be REJECTED. It contributes no destination, so it is invisible to the allowlist, to `dest_caps` and to the new-destination breaker, and on a bare `0x6a` output the value is spendable by anyone rather than burned.
* `sendWith` and `acceptDelayedBroadcast` MUST both be refused on the autonomous path. The first broadcasts value the check pipeline never sees; the second removes the synchronous broadcast that the freeze re-check and the broadcast-uncertainty rule both require in order to run at all.
* Every ledger counter and the freeze flag MUST survive a restart. Only `spent_total` can be rebuilt from chain state, so an engine holding the rest in memory enforces the period and rate caps per uptime and treats a restart as the attended unfreeze a tripped breaker requires.
* ESCALATE MUST NOT cause a spend. It ends the pipeline, so an approval MUST re-enter at check 1 and run every remaining check against current state; an engine with no attended path MUST reject rather than refer.
* An engine MUST refuse to open a ledger for an envelope against any account other than its signed `account_id`, and MUST refuse to debit any other account under it. This is what makes the isolation requirement above enforceable rather than advisory.
* Policies MUST be totally ordered by `(issued_at, nonce)` and an engine MUST NOT load one less than or equal to the policy currently loaded. A rule written only against a lower `issued_at` leaves two policies issued in the same second interchangeable, including the looser one.
* The three delegation authorities MUST be read from the verified envelope and from nowhere else. No unsigned field may raise `delegation_depth`, enable creation when `can_create_children` is false, or lift `child_budget_ceiling`.
* Broadcast uncertainty MUST fail closed; the kill switch MUST be re-checked at the last safe point pre-broadcast.
* A policy MUST verify under the wallet's pinned issuer key before it may authorize any spend.
* Bearer-token resolution MUST be constant-time **across the whole set an attacker can probe**. Comparing each candidate in constant time is not sufficient on its own: if the tokens are spread over several registries and a request reaches them in sequence, the number of comparisons performed before a match still varies with the token presented, and that count is observable. An implementation that partitions its tokens MUST either search every partition on every request or ensure the partition is not selected by attacker-controlled input. Tokens MUST NOT be logged, which includes writing one to standard output in a deployment whose standard output is collected by a log.

## Test Vectors

### Vector A, signed PolicyRecord (metered endpoint, stable payee address)

Issuer test key: secp256k1 private scalar `424242` (a throwaway test key; NOT an operator key), whose compressed public key is `03e9a2463c5ecaaaac49dc3ac382cae02cec513d342ee9a6c18e842c344f7b2bfb`.

**On the `type` domain separator.** These payloads are signed over `brc-181/agent-policy/1`, so an implementer can hash and verify them exactly as printed. The number is inside the signed payload rather than beside it, so it is not cosmetic: were this standard reassigned a different number, every digest and signature in this section would change with it. They are re-derived by `tools/brc_agent_policy_vectors.py --number <N> --write`, never edited by hand.

Canonical payload bytes `C` (exactly, no trailing newline):

```
{"account_id":7,"can_create_children":false,"cb_burst_per_s":0,"cb_max_new_dests":0,"child_budget_ceiling":0,"delegation_depth":0,"dest_allowlist":["p2pkh:1111111111111111111111111111111111111111"],"dest_caps":[],"escalate_off_allowlist":false,"escalate_over_per_tx":false,"expiry_epoch":0,"issued_at":1752710400,"issuer":"03e9a2463c5ecaaaac49dc3ac382cae02cec513d342ee9a6c18e842c344f7b2bfb","max_fee":100,"nonce":"a1b2c3d4e5f60718","origin_token":"agt-marketplace-bidder-01","per_tx_cap":100,"period_cap":1000000000,"period_window_s":3600,"purpose":"per-query metering, payee at a stable address","rate_limit":1000000,"rate_window_s":60,"total_budget":1000,"type":"brc-181/agent-policy/1"}
```

Expected outputs:

* Payload digest `SHA256(C)` = `60ce6f775987fcf1d7291a67f20c46fbb6ff09147cdbb30660617badf48e8882`
* DER signature (hex) = `3044022025837bf6d69dec70d1bcfc715f939fbe4150c26b403455ac824f334cd28e956e02206cfd2b1f088eaf03321eb2a6f1964cbd2958e80dcc9700ae2ed89377009c0320`
* Verifying the DER signature over `SHA256(C)` against the issuer public key MUST succeed. A signer using RFC 6979 deterministic nonces will reproduce this exact signature; any other valid signature is also acceptable, but the digest above MUST match.

### Vector B, signed PolicyRecord with per-destination cap + circuit breaker

Same issuer key as Vector A. This policy allowlists one verified P2PKH destination at the global cap and admits one lower-trust script destination under a tighter `dest_caps` ceiling of 5000, with both circuit breakers armed.

Canonical payload bytes `C`:

```
{"account_id":7,"can_create_children":true,"cb_burst_per_s":3,"cb_max_new_dests":2,"child_budget_ceiling":250000,"delegation_depth":1,"dest_allowlist":["p2pkh:1111111111111111111111111111111111111111"],"dest_caps":[["script:76649d1560a19192282962c83a535b543b544dcf4da3d7ef89d428083435479f",5000]],"escalate_off_allowlist":false,"escalate_over_per_tx":false,"expiry_epoch":0,"issued_at":1752710400,"issuer":"03e9a2463c5ecaaaac49dc3ac382cae02cec513d342ee9a6c18e842c344f7b2bfb","max_fee":5000,"nonce":"0f0e0d0c0b0a0908","origin_token":"agt-marketplace-bidder-01","per_tx_cap":100000,"period_cap":1000000000,"period_window_s":3600,"purpose":"pay curated contacts, bounded","rate_limit":1000000,"rate_window_s":60,"total_budget":1000000000,"type":"brc-181/agent-policy/1"}
```

Expected outputs:

* Payload digest `SHA256(C)` = `eab3434ad70de51918c2e23fa92972960508bdb1bdb064d77330bc23494cd93f`
* DER signature (hex) = `30440220362cbecaa6645a65f5ea433c82c9c188ab0ee2bd42501c6870ece9be775cb89c02203cb1d02b7878611d05330d4c39353326605b633e82cef0411dc4d8dd8a2dce5f`
* Signature verification against the issuer key MUST succeed.

The `script:` destination above is derived per Section 3.2 from the non-P2PKH script `52210300ff210300ee52ae`: `SHA256(52210300ff210300ee52ae)` = `76649d1560a19192282962c83a535b543b544dcf4da3d7ef89d428083435479f`.

### Vector C, canonical destination computation

| Input locking script (hex)                              | Expected canonical destination                                            |
| ------------------------------------------------------- | ------------------------------------------------------------------------- |
| `76a914` + `11`×20 + `88ac` (P2PKH)                     | `p2pkh:1111111111111111111111111111111111111111`                          |
| `52210300ff210300ee52ae` (bare multisig-ish, non-P2PKH) | `script:76649d1560a19192282962c83a535b543b544dcf4da3d7ef89d428083435479f` |
| `52210300aa88ac` (non-P2PKH, wrong length)              | `script:a0d411a4e1302e346978e9d905c9819548530cdba29850942e827aab8538303b` |
| `6a04deadbeef` (OP\_RETURN data)                        | none, contributes no destination                                          |

The P2PKH input MUST also produce the same canonical value when presented as raw bytes or as an array of byte integers (script normalization, Section 3.2).

### Vector D, enforcement walk (runtime)

Policy: `origin_token="agent1"`, `per_tx_cap=100`, `period_cap=1_000_000_000`, `period_window_s=3600`, `total_budget=1000`, `dest_allowlist={p2pkh:1111…1111}`, `rate_limit=1_000_000`, `rate_window_s=60`, `max_fee=100`, all breakers/escalations off. Coin selection returns a fresh distinct coin each call.

This walk is stated at **zero miner fee**, so the arithmetic below isolates the payout path. Fees are not ignored by the model: per Section 6 the debit is payout + fee, so at a non-zero fee the same policy admits fewer than ten actions before `total_budget` is reached. An implementation MAY run the walk at a non-zero fee. The result then has two regimes, divided by this policy's `max_fee` of 100:

* **`fee <= max_fee`.** Row 6 yields exactly `floor(1000 / (100 + fee))` approvals, and `spent_total` settles at exactly `approvals * (100 + fee)`. At `fee = 0` that is 10 approvals and `spent_total = 1000`; at `fee = 100` it is 5 approvals and `spent_total = 1000`.
* **`fee > max_fee`.** Row 6 yields **0** approvals and `spent_total` stays 0. At `fee = 150` the formula above would give 4; the conforming answer is 0.

The formula therefore describes the budget regime only, and an engine that returns 4 at `fee = 150` is not applying `max_fee`. Both regimes state the counts and totals only. They deliberately name no mechanism: which check rejects a given request depends on the fee, and an implementation conforms by producing these numbers, not by producing them in a particular way.

| # | Request                                                                                | Expected verdict                           | Reason                                                                                                                                                                                                                                                                       |
| - | -------------------------------------------------------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | pay 100 → `76a914`+`11`×20+`88ac`                                                      | `ALLOW_AUTO`                               | within policy; `spent_total` → 100                                                                                                                                                                                                                                           |
| 2 | pay 101 → allowlisted P2PKH                                                            | `REJECT`                                   | over per-tx cap (100)                                                                                                                                                                                                                                                        |
| 3 | pay 50 → `76a914`+`22`×20+`88ac`                                                       | `REJECT`                                   | off-allowlist destination                                                                                                                                                                                                                                                    |
| 4 | pay 50 → `52210300ff210300ee52ae`                                                      | `REJECT`                                   | non-P2PKH raw script not allowlisted (bypass closed)                                                                                                                                                                                                                         |
| 5 | origin `local-unspecified`, pay 1 → allowlisted                                        | `REJECT`                                   | origin not bound                                                                                                                                                                                                                                                             |
| 6 | 300 requests each pay 100 → allowlisted P2PKH, run sequentially and again concurrently | exactly **10** `ALLOW_AUTO`, rest `REJECT` | budget 1000 / per-tx 100 = 10; `spent_total` settles to exactly 1000 under either ordering; the no-coin-double-selected clause is the half only a concurrent run can exercise, and an implementation whose selector yields inside the critical section MUST still satisfy it |

Graduated-trust sub-case (per Section 8): with `per_tx_cap=100000` and a `dest_caps` entry `(scriptDest, 5000)` for an unverified destination, a request of 5000 to that destination is `ALLOW_AUTO`; 5001 is `REJECT` ("over per-destination cap"); and if the `dest_caps` entry were 999999 while the global `per_tx_cap` were 10000, a request of 20000 is `REJECT` ("over per-tx cap") because the global cap is checked first (a per-destination cap can only tighten).

Rollback sub-case (identity-based, Section 6): two in-flight reservations A (amount 100, `seq` 0) and B (amount 30, `seq` 1); `period_spent` = 130. Rolling back A MUST leave `period_spent` = 30 and `spent_total` = 30 (A's own window entry removed by `seq`, not the last-appended B entry), and B MUST still commit cleanly.

### Vector E, negative (tamper detection)

A vector set that only demonstrates success cannot distinguish a correct verifier from one that returns `true` unconditionally. This vector fails, and an implementation that accepts it is broken in the way that matters most.

Take Vector A and raise `per_tx_cap` from `100` to `101`. That is a one-byte edit, and the single most attractive change an attacker could make to a policy. Every other byte, including Vector A's signature, is unchanged.

Mutated canonical payload bytes `C'`:

```
{"account_id":7,"can_create_children":false,"cb_burst_per_s":0,"cb_max_new_dests":0,"child_budget_ceiling":0,"delegation_depth":0,"dest_allowlist":["p2pkh:1111111111111111111111111111111111111111"],"dest_caps":[],"escalate_off_allowlist":false,"escalate_over_per_tx":false,"expiry_epoch":0,"issued_at":1752710400,"issuer":"03e9a2463c5ecaaaac49dc3ac382cae02cec513d342ee9a6c18e842c344f7b2bfb","max_fee":100,"nonce":"a1b2c3d4e5f60718","origin_token":"agt-marketplace-bidder-01","per_tx_cap":101,"period_cap":1000000000,"period_window_s":3600,"purpose":"per-query metering, payee at a stable address","rate_limit":1000000,"rate_window_s":60,"total_budget":1000,"type":"brc-181/agent-policy/1"}
```

Expected outputs:

* Mutated payload digest `SHA256(C')` = `133f839214bff451e3ea39eed0ae8c95c74dbe965fb3bf39f981e8358f498efe`, which differs from Vector A's digest.
* Verifying **Vector A's DER signature** over `SHA256(C')` against the issuer public key MUST fail. An engine that returns any verdict other than a rejection here MUST be treated as non-conforming.
* The engine MUST reject at signature verification and MUST NOT reach any of the Section 5 checks. Rejecting later, for instance by noticing the amount at the cap check, is not equivalent: the signature is what binds the whole payload, and a verifier that admits the record and relies on downstream checks will accept every field the checks do not read.

Two further payloads MUST be rejected without reaching signature verification at all, per Section 3.1's exact-fields rule: Vector A with any field removed, and Vector A with an unrecognized field added. Both are malformed, not merely unauthorized.

## Implementations

A reference implementation exists (not a public path):

* **Policy engine + ledger**: `PolicyRecord`, `canonical_dest`, `amount_and_dests`, and the thread-safe `AgentAllowance` (single per-account lock; the ordered check pipeline of Section 5; the period/rate/burst/seen-dest counters; identity-based `Reservation` commit/rollback; chain-seeded `spent_total`; freeze + circuit breaker). A 300-thread test asserts that the allowance settles to exactly its budget and that no coin is double-selected. Measured honestly, that test does not by itself prove the locking: with a microsecond critical section the threads do not overlap, and the assertions still hold if the per-account lock is removed. Under an artificially contended selector the lock is observably serializing (peak simultaneous occupancy of the critical section falls from tens to one). Implementers should treat the lock as a requirement of Section 6 rather than as something the reference test demonstrates.
* **Headless spender**: the GUI-agnostic orchestration that runs policy-gated build→sign→broadcast, implementing the fail-closed rules of Section 7 (noSend denial, completeness assertion, pre-broadcast full-undo, freeze re-check, broadcast-uncertainty fail-closed, held reservations, audit isolation) via injected wallet operations, so it is unit-testable without funds and wallet-version-agnostic.
* **Bearer-token registry**: per-agent unforgeable tokens, constant-time resolution, instant revocation (Section 4/9).
* **Graduated-trust contact curation**: verified/unverified/raw tiers producing `dest_allowlist` + `dest_caps` with explicit opt-in and resolve-then-pin (Section 8).
* **Server routing**: a BRC-100 `createAction` carrying a valid `X-Agent-Token` is resolved to a binding and routed to the policy-gated headless spender before the ordinary per-origin path.

The enforcement core is covered by a unit suite (including real parallel-thread concurrency tests) and has passed a testnet validation of the full sign/broadcast seam. The signed-envelope serialization (Sections 3.3-3.4) is the interoperable wire format specified here; an implementation MAY hold the policy in memory sealed at rest by the wallet's account registry, provided any policy it accepts over the wire verifies per Section 3.4.

## Mathematical and cryptographic basis

The authority to spend reduces to one signature verification. A PolicyRecord is accepted only if its canonical payload C verifies under the wallet's pinned issuer key, over the digest SHA-256(C). Canonicalization (Section 3.3) is injective on the field set: sorted keys, tight separators, integer normalization, and sorted de-duplicated destination arrays send each distinct policy to one preimage string. To make the engine accept a policy the operator never signed, an attacker must produce a valid ECDSA signature over SHA-256(C') for a chosen C' under the pinned public key. That is an existential forgery against ECDSA on secp256k1, whose hardness rests on the elliptic-curve discrete-logarithm problem, composed with the second-preimage resistance of SHA-256. Both are infeasible, so a forged policy is infeasible.

Two design choices tighten the reduction. The issuer key is build-pinned rather than read from the record, so key substitution (signing C' under an attacker key and presenting that key alongside it) is rejected before any signature check runs. Supersession is ordered by issued\_at with a nonce tie-break, a strict total order that forbids loading an earlier policy over a later one, so a captured old policy cannot be replayed to reopen spent budget. The spend limits themselves are not cryptographic. They are integer-arithmetic invariants (Section 6) maintained inside one lock, so their guarantee is atomic accounting under concurrency, distinct from and composed with the signature guarantee above.

## Security Considerations

* **The agent is untrusted.** The entire design assumes the agent will, at some point, be prompt-injected into requesting an attacker-controlled spend. Safety derives from wallet-side enforcement plus account isolation, never from agent behaviour. A reviewer should verify that no code path trusts an agent-supplied field except as validated input.
* **Isolation is most of the safety.** The agent account holds only its allowance; even a total compromise of the held signing secret unlocks only that capped account. Operators SHOULD fund it conservatively and top it up rather than pre-loading a large balance.
* **TOCTOU / overspend.** The single per-account lock over check→reserve→debit is load-bearing. A runtime-level lock (GIL) is insufficient. Dropping the lock, or debiting outside it, reintroduces overspend and coin double-selection. The identity-based (`seq`) rollback is equally load-bearing: a pop-last rollback silently leaks period/rate slots under concurrency.
* **Broadcast is not atomically observable.** Treating an ambiguous broadcast as failed (re-crediting/releasing) risks re-selecting a coin that is actually confirming, a double-spend. This BRC mandates fail-closed with a distinct error and out-of-band reconciliation. Retrying an `ERR_BROADCAST_UNCERTAIN` without reconciliation is unsafe.
* **Fee escape.** The Section 5.2 cap checks are on the payout, because the miner fee does not exist until the transaction is built. A payout of `0` therefore clears every one of them while the action still spends `fee` from the account, and `per_tx_cap = 0` does not close that: it denies payouts, not fee-bearing actions. Section 5.3 is what closes it. After `build_tx` and before signing, the engine bounds the fee by `max_fee` and re-runs the period cap and the total budget against payout plus fee, releasing the coin reservation and debiting nothing on failure. An implementation that omits the post-build re-check has a fee escape bounded only by whoever composes the request.
* **Fee escape, the residue.** Section 5.3 closes the escape only for fees the builder reports. Where the builder reports no fee (Section 5.3, *Builders that report no fee*), every fee it pays is outside the ledger and outside `max_fee`, the period cap and the total budget, bounded only by the account's balance. That is a deliberate accommodation for callers that do not track fees, and it is also the configuration most likely to be reached by accident: a policy that sets a `max_fee` while its builder reports nothing looks enforced and is not. Reviewers SHOULD check which of the two a deployment is actually on, because the difference is invisible from the policy record alone.
* **Two seams, one rule for what a fee is.** An engine that accepts a fee from more than one place (a builder's return value, a wallet hook, a plugin) MUST apply the same test for "this is a satoshi count" at each. Where one seam coerces and another validates, the same reported value is allowed through one and denied at the other, and the coercing seam is the dangerous one: a truncated fractional fee debits less than the transaction spends, silently and on every action.
* **Raw-script bypass.** Allowlisting by address alone is bypassable with a semantically-equivalent or unrelated raw script; canonicalizing to `p2pkh:` hash / `script:` SHA-256 closes this. Implementers MUST NOT allowlist by address string.
* **Budget re-open.** A local counter is not authoritative; the chain-seeded, conservative `spent_total` prevents a wiped/edited counter from re-opening the budget. The seed only ever under-credits remaining budget.
* **Issuer-key pinning.** A policy is only as trustworthy as the pin. The engine MUST verify against a build-/config-pinned issuer key it controls, not a key carried in the record or supplied by the agent. Otherwise an attacker who can inject a policy grants themselves authority. Pin rotation is an attended operator action.
* **Token handling.** Bearer tokens are secrets: constant-time resolution defeats timing oracles; tokens MUST NOT be logged or derived from the request; and revocation MUST be instant. Because the token is the grant, its leakage is equivalent to granting the agent's authority, but still bounded by the policy and the account balance.
* **Circuit breaker as a backstop, not a boundary.** Burst / new-destination breakers latch a freeze on anomalies pending operator review, rather than draining silently. They are a safety net; the hard boundaries remain the caps and the allowlist.
* **Audit fail-loud.** Audit must never corrupt fund state or turn success into a retry-inducing exception. Fail-loud but non-fatal isolation of the audit sink is required.
* **Attended fallback vs reject-fast.** `ESCALATE` reintroduces a human and is appropriate for non-HFT agents; the reject-fast default avoids an attended stall on the hot path. Operators choosing `ESCALATE` should ensure the attended path cannot be starved or bypassed.

## References

* [BRC-1: Transaction Creation](/wallet/0001). Bitcoin SV BRC repository.
* [BRC-2: Data Encryption and Decryption](/wallet/0002). Bitcoin SV BRC repository.
* [BRC-3: Digital Signature Creation and Verification](/wallet/0003). Bitcoin SV BRC repository. (Used for the policy signature envelope, Section 3.4.)
* [BRC-5: HTTP Wallet Communications Substrate](/wallet/0005). Bitcoin SV BRC repository. (One substrate over which the agent token of Section 4 may be carried; this standard is substrate-neutral.)
* [BRC-42: BSV Key Derivation Scheme (BKDS)](/key-derivation/0042). Bitcoin SV BRC repository. (Agent-account derivation.)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043). Bitcoin SV BRC repository.
* [BRC-52: Identity Certificates](/peer-to-peer/0052) / [BRC-53: Certificate Creation and Revelation](/wallet/0053). Bitcoin SV BRC repository. (Cited for a future cert-gated policy credential.)
* [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100). Bitcoin SV BRC repository. (The `createAction` substrate this BRC gates.)
* [BRC-151: BRC-100 Risk Assessment and Best Integration Practices](/opinions/0151). Bitcoin SV BRC repository. (Opinion paper; its Section 9 states the AI-agent spending risk this standard is designed to bound.)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116). Bitcoin SV BRC repository. (Declares application permissions, including a manifest `spendingAuthorization` ceiling; complementary to the per-action envelope specified here.)
* [BRC-219: Wallet Permission Prompt Liveness Contract](/wallet/0219). Bitcoin SV BRC repository. (The attended-prompt counterpart this policy composes with; see Relationship to related work.)
* [BRC-98: P Protocols: Allowing Future Wallet Protocol Permission Schemes](/wallet/0098). Bitcoin SV BRC repository. (Reserves the `p` protocol-identifier namespace; discussed under *Why this is not a reserved-namespace module*.)
* [BRC-111: P Labels: Allowing Future Wallet Action Label Permission Schemes](/wallet/0111). Bitcoin SV BRC repository. (Reserves the `p` label namespace; a wallet MAY register a module label for policy-governed actions, and this standard does not depend on it.)
* [BRC-172: Giving an AI Agent Control of a Delivery Vehicle Without Giving It a Wallet](/state-machines/0172). Bitcoin SV BRC repository. (Bounds an agent by removing custody; complementary, and compared in *Relationship to related work*.)
* [BRC-177: Wallet-Enforced Expiry for `noSend` Actions](/wallet/0177). Bitcoin SV BRC repository. (Precedent for module-defined wallet behaviour under a reserved label namespace.)
* [BRC-166: P2PKH Payments for HTTP 402](/payments/0166). Bitcoin SV BRC repository. (Mandates a fresh, payer-unverifiable receiving address per invoice, which a signed destination allowlist cannot enumerate; the boundary is stated in *Relationship to related work*.)
* [BRC-179: Signed Capability Manifest for Wallet and Tool-Server Method Exposure](/wallet/0179). Bitcoin SV BRC repository. (Governs whether a spend-bearing method is exposed at all; evaluated before this standard runs, and compared in *Relationship to related work*.)
* RFC 2119: Key words for use in RFCs to Indicate Requirement Levels.
* RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format.
* RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and ECDSA.
* SEC 1 / SEC 2: Elliptic Curve Cryptography; the secp256k1 curve parameters.
* FIPS 180-4: Secure Hash Standard (SHA-256).


# Optional Metadata Registries and Their Stewardship

Ty Everett (<ty@projectbabbage.com>)

## Abstract

ProtoMap, BasketMap and CertMap associate wallet protocol identifiers, output baskets and certificate types with understandable names, descriptions, documentation and images. This specification explains those registries and defines a limited role for their stewards: improving public information, without deciding who may implement a protocol, request a wallet operation or use an application.

Registry metadata is optional. A wallet adopting this specification MUST NOT make registry inclusion, omission, opinion or availability a condition of using an otherwise supported protocol. Protocol validation, application permissions, spending approval and certificate verification remain separate. Builders are encouraged to document their protocols through the BRC process and then ask their chosen registry operators for inclusion. The initial process is deliberately lightweight and expected to evolve through public review.

## Status and scope

This is a explanatory and stewardship framework with a wallet consumption profile. The uppercase terms MUST, MUST NOT, SHOULD and MAY express requirements of **this specification for implementations claiming to follow it**, as described by RFC 2119 and RFC 8174. They are not a claim that every existing wallet already behaves this way, that the proposal has ecosystem consensus, or that a formal appeals institution already exists.

The proposal does not change BRC-100, key derivation, transaction validation, overlay wire protocols or existing identifier reservations. The implementation map below describes deployed conventions; it is not a replacement wire-format specification. This specification does not create a global namespace allocator, installable permission scheme, certificate issuer accreditation process, application store or security reputation feed.

## Motivation: coordination without control

A permission prompt containing only `[2, "auth message signature"]` is hard to understand. A useful description can explain that an application wants to sign authentication messages, while retaining the exact tuple and the actual counterparty. Similar problems arise when showing unfamiliar baskets and certificate fields. Shared, maintained descriptions reduce duplicated research and help builders explain interoperable identifiers consistently.

IANA's service-name and transport-port registry is a useful analogy for this **coordination function**: publish a stable reference, make assignments understandable, reduce collisions and steward information impartially. IANA itself distinguishes registration from endorsement and warns against treating a registered port as proof of trustworthy traffic. Registry stewardship does not provide a technical mechanism to stop someone using a port.

The analogy has limits. IANA has its own defined assignment procedures and authority; this specification neither imports them nor claims affiliation. These metadata registries record publisher-attributed descriptions of identifiers, rather than allocating exclusive rights to use them. A developer does not need a registry operator's permission to use a valid protocol identifier. No registry operator receives authority over BRC adoption or the wallet's permission decisions from this specification.

## 1. Participants and boundaries

| Participant                      | Role                                                                                    | Authority it does not acquire from metadata                                                |
| -------------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Protocol or application author   | Defines and documents intended behavior and actual identifiers                          | Ownership of an identifier merely by requesting a listing                                  |
| BRC contributors and maintainers | Discuss and publish specifications under the BRC contribution process                   | Control of a separate operator's publication key                                           |
| Registry publisher or steward    | Signs and maintains descriptions under its own identity                                 | Power to authorize a user's wallet operation, revoke a protocol or grant certificate trust |
| Overlay host                     | Indexes and serves registry transactions according to its topic rules                   | Authorship of a record merely by serving it                                                |
| Wallet implementer               | Chooses metadata sources, validates and renders results, implements supported protocols | A metadata-based veto over otherwise supported use under this profile                      |
| Wallet user                      | Grants or denies permissions and chooses available trust preferences                    | An obligation to use a particular registry publisher                                       |

The entity hosting an overlay and the entity signing a record can be different. Trusting a host for availability is not the same as accepting a publisher's statement. Metanet Trust Services is one publishing identity used by some wallet defaults, not a fourth registry type, a universal authority or a requirement imposed by BRC-100.

## 2. What a registry record describes

| Registry  | Exact lookup identity                                      | Human-readable information                                       | Existing topic / lookup service |
| --------- | ---------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------- |
| ProtoMap  | BRC-43 security level **and** protocol string              | Name, description, icon and documentation                        | `tm_protomap` / `ls_protomap`   |
| BasketMap | Basket identifier                                          | Name, description, icon and documentation                        | `tm_basketmap` / `ls_basketmap` |
| CertMap   | Certificate type bytes, conventionally expressed as base64 | Name, description, icon, documentation and per-field descriptors | `tm_certmap` / `ls_certmap`     |

A consumer's record identity also includes the network and publisher public key. Identical identifiers described by two publishers are two attributable statements, not an instruction to overwrite one with the other. Protocol security levels must not be collapsed; a BRC number is not itself a BRC-43 protocol tuple. Use the identifier accepted by the applicable wallet standard and implementation, with its required normalization. Do not derive identity from a friendly name or registry search result.

A certificate **type** is not a certifier identity. Describing a type or its fields does not mean trusting every issuer that uses it. Field spelling and case identify the signed certificate data; changing a display label must not rename those fields or alter the certificate. A basket description does not grant access to its outputs or permission to spend them. There is no additional spending-registry type in this model.

Parameterized identifiers need protocol documentation of their family and parameters. A literal record is not a wildcard matching rule. Avoid publishing user-specific counterparties, linkage data or dynamically generated identifiers as a public catalogue.

### 2.1 Existing publication lifecycle (informative)

Current RegistryClient implementations publish one-satoshi signed PushDrop outputs. The registry publisher signs the metadata and controls the output. Creating a definition creates a token; updating spends the previous output and creates a replacement; removing spends it without a replacement. Implementations use the respective `protomap`, `basketmap` or `certmap` wallet basket and signing protocol `[1, registryName]`, key ID `1`, with the `anyone` counterparty. The corresponding overlay topic indexes accepted outputs and its lookup service returns matching transaction outputs.

Removal withdraws that publisher's current description. It does not erase blockchain history, revoke a certificate, spend application assets, change wallet grants or prohibit the underlying identifier. An old database row or cached response is not by itself proof of a current unspent record. Consumers must distinguish authenticated content from freshness, availability and completeness.

The on-chain payload contains descriptive fields and publisher attribution. Review decisions, disputes, source revisions and operator policy changes can be maintained in a linked public review log; this specification does not pretend they are additional fields already present in the deployed token format.

## 3. Wallet consumption profile

### 3.1 Metadata must not become permission

A conforming wallet MUST keep the registry result outside its decision about whether an otherwise valid and supported protocol operation is available. It MUST NOT block, hide, disable or impose an additional confirmation solely because a record is missing, withdrawn, disputed, comes from an unselected publisher, or cannot be fetched. It MUST NOT require a builder to obtain a BRC number, registry approval, payment or operator relationship before an application can use the underlying protocol.

A record's presence, friendly wording, icon or publisher trust score MUST NOT grant, enlarge or renew permissions, approve spending, select a counterparty or establish trust in a certificate issuer. Registry strings MUST NOT be executed as policy or converted into an allowlist or denylist.

This does not remove normal wallet checks: user denial, protocol validity, reserved administrative identifiers, unsupported executable permission modules, transaction validity, fee limits, originator isolation and independently authorized application policies still apply. The reason for such a refusal must be the actual rule, not registry listing status. In particular, BRC-98/99/111 module support and the BRC-123 scheme registry are distinct from descriptive ProtoMap/BasketMap/CertMap records.

### 3.2 Fallback and presentation

Wallets MUST retain a usable normal authorization path showing the exact requested identifier and relevant request details when optional metadata fails. Start from that fallback; enrich it when a bounded lookup succeeds. Do not indefinitely delay a permission prompt, require a lookup retry, or reset a user's pending choice because the registry is unavailable. A neutral “description unavailable” indication is appropriate; presenting “unregistered” as evidence of danger or an extra barrier is not.

Show the exact protocol tuple, basket or certificate type alongside, or readily inspectable from, the friendly description. Preserve the actual originator, counterparty, requested operation, amount and certificate fields as applicable. Distinguish the application's explanation from the registry publisher's description. If metadata changes during a prompt, it must not silently change the request or the scope of consent.

For conflicting descriptions, apply a disclosed local source-selection policy or show separately attributed alternatives. Never blend fields from different publishers into a falsely unified endorsement. A neutral raw-identifier fallback remains available. A wallet may reject malformed or unauthenticated **metadata** without rejecting the protocol request.

### 3.3 Source choice

A wallet MAY ship with default publishers and hosts. Defaults are implementation choices, not permanent ecosystem appointments. Implementations adopting this profile SHOULD provide a documented way to replace or disable metadata sources without disabling the underlying protocols or forcing a user to migrate funds. Different wallets and communities may select different publishers or operate their own.

Registry-publisher trust and certificate-issuer trust have different meanings. Implementations that currently share a settings structure for them SHOULD disclose that coupling and its effects. Consumers MUST NOT infer that a publisher is an acceptable certificate issuer merely because its registry metadata is selected.

### 3.4 Acceptance scenarios

| Scenario                                                             | Expected result                                                                              |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Identifier has no record                                             | Normal request and permission flow with exact identifier                                     |
| Hosts are offline, slow or return an error                           | Bounded lookup; usable fallback without a registry-dependent veto                            |
| Record is withdrawn or under dispute                                 | Description may change or disappear; underlying protocol remains available                   |
| Record claims “automatically approved”                               | Treat it as untrusted prose; enforce the same wallet permissions                             |
| Record has an invalid signature or unsafe image URL                  | Discard the metadata or resource; preserve the request flow                                  |
| Two chosen publishers disagree                                       | Attribute alternatives or apply the disclosed selection policy; no automatic protocol denial |
| User changes publisher                                               | Change descriptions, not existing grants, assets or supported protocol semantics             |
| Request names an unsupported `p` module or reserved admin identifier | Apply the relevant wallet rule independently of metadata                                     |

These scenarios are an implementation review checklist, not a claim of completed conformance testing for existing products.

## 4. Builder inclusion workflow

1. **Document the protocol through the BRC process.** Open a BRC proposal, or improve an existing relevant BRC, using the repository's contribution and numbering rules. Explain exact tuples, basket identifiers and certificate types; intended semantics; counterparty and key-ID rules; requested capabilities; security and privacy implications; versioning and compatibility; and implementation evidence. Do not invent a new BRC for an already documented standard merely to obtain a listing.
2. **Contact the registry operators you want to publish the description.** A BRC pull request is not automatically forwarded to every operator. Supply the BRC or PR link and status, implementation/specification revisions, names and descriptions, public documentation, usable icons and exact certificate-field descriptors where relevant. Identify which publisher and network the request concerns.
3. **Collaborate on accuracy.** Operators check the exact identifiers, existing use, collisions, documentation and neutral wording. They may ask for clarification, publish an accurately labeled experimental description, or defer a request with reasons. A merged BRC does not automatically create a registry entry, and an entry does not mean an unmerged BRC has been adopted.
4. **Keep the record maintained.** Notify publishers of changed semantics, documentation, images, maintainers or deprecation. Request a correction against the exact identity and current record, with source evidence. Retain historical compatibility information where deployed clients still need it.

For the initial Metanet Trust Services process, use the [BRC submission guide](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md): open or link a BRC issue/discussion or proposal, tag @ty-everett, and reserve <ty@projectbabbage.com> for genuinely urgent or long-time unresolved matters. These are this operator's contact arrangements, not mandatory channels for every publisher.

This is a lightweight initial process, not a claim of a fully formal registration bureaucracy, guaranteed response time or mandatory universal approval. Operators SHOULD publish their monitored contact route, criteria, queue/status information and current process version. They should make submission and maintenance practical for independent builders. None of these editorial steps is a prerequisite to implementing or using a valid protocol.

## 5. Impartial stewardship and disputes

Operators adopting this framework commit to impartial treatment under published, consistently applied editorial criteria. Listing decisions should concern identifiable technical meaning, evidence, public documentation, accurate attribution and useful presentation. They MUST NOT be conditioned on using the operator's commercial services, endorsing its views, abandoning a competitor, or granting it additional authority over an application. Any applicable fees, capacity constraints or service arrangements SHOULD be disclosed; payment must not buy misleading descriptions or preferential truth claims.

Operators SHOULD publish dated reasons for inclusion, correction, deferral and removal, with relevant source references and record outpoints where available. Avoid silently repurposing an established identifier's description for an incompatible meaning. A first listing or earlier request is evidence of chronology, not a grant of intellectual-property rights or technical exclusivity. When an identifier has conflicting real uses, document the ambiguity and encourage authors to resolve it or choose distinct identifiers.

### 5.1 Initial dispute handling

Until a more formal process is agreed, the minimum useful practice is:

1. Accept a correction or dispute through the operator's published contact route, identifying the publisher, network, exact identifier, disputed statement, evidence and requested remedy.
2. Acknowledge the issue and give affected parties a reasonable opportunity to provide evidence. Publish a status and reasoned outcome without unnecessary personal information.
3. Disclose relevant conflicts of interest and seek an uninvolved reviewer where practicable. If none is available, state that limitation rather than claiming independent adjudication.
4. Allow reconsideration when new evidence appears and retain a visible correction history. Distinguish unresolved technical ambiguity from a finding against a party.
5. Respect the dispute and review procedures the operator publicly adopts as the process develops. Publish material process changes, their effective dates and a route for community feedback; do not invent or retroactively imply an appeals body that does not exist.

A narrow, urgent removal of misleading or unsafe **display material** may precede ordinary review, with a prompt explanation and opportunity to challenge the decision. That action cannot become a protocol ban through wallet behavior. Sensitive incident reports belong in an appropriate private channel, not an immutable public record.

This framework does not adjudicate legal rights, establish a universal arbitration authority or require competing publishers to reach identical conclusions. Disagreement remains visible and users retain the option to select other sources.

## 6. Replication, continuity and exit

Published registry transactions are public on-chain data. Independent parties can retrieve and verify available records, maintain archives and serve compatible overlay indexes without the original publisher operating the only server. Replication preserves the original publisher's signature and attribution; it does not grant possession of its private key or authority to modify its signed outputs. A new publisher can publish its own attributed descriptions, with provenance, and users can choose that publisher.

Public blockchain history is not a promise that every host retains or serves every transaction, that an index is complete or fresh, or that linked images and documentation remain online. Replicators need transaction availability, validation and current-state reconstruction, appropriate synchronization, and a policy for stale or conflicting observations. External resources require their own preservation and licensing arrangements. Operators SHOULD document export/recovery procedures and avoid making a proprietary API or nonportable enrichment the only way to obtain essential metadata.

If a publisher disappears, behaves unfairly or loses its key, its defaults are replaceable. Wallet users and implementers can change source selection; other hosts can serve authentic available records; other publishers can issue alternative descriptions. A replacement publisher must not impersonate the old identity. There is no new master authority or mandatory recovery key introduced by this specification.

## 7. Security and privacy considerations

The intended authority of a registry publisher is limited to attributable descriptive statements. Nevertheless, attractive false descriptions can mislead people and lookup infrastructure can observe what identifiers a wallet requests. Optional does not mean harmless.

Consumers SHOULD authenticate records, scope caches by network/publisher/type/identifier, bound lookup and parsing work, and treat text and URLs as untrusted input. Render text without executing markup; constrain images and redirects; avoid using metadata fetches to expose local resources or credentials. Prefer privacy-preserving caching and avoid sending user certificates, secret fields, transaction histories or unnecessary counterparties to metadata services. Validate data independently even when served by a familiar host.

Operators should secure publication keys, separate them from hosting credentials, retain review and change history, and limit what information is placed permanently on-chain. If a key is compromised, disclose the affected identity and scope, preserve evidence and support replacement source selection. Neither a signature nor a familiar publisher proves a description is true, safe, current or endorsed by the protocol author. Registry content must not automatically install software, grant wallet permissions or turn an incident into a remotely administered protocol kill switch.

## Implementations and adoption

The [TS Stack SDK RegistryClient](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/sdk/src/registry), the [overlay topic implementations](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/overlays/topics/src), and [Registrant](https://github.com/bsv-blockchain/registrant) provide existing publication and lookup tooling. Wallet settings and presentation layers determine which publishers are consulted and how results are displayed. Hosting, publisher selection and actual permission enforcement must be reviewed separately.

The companion guides can explain current APIs and operator contacts without hardcoding them into a permanent ecosystem authority. Adopting this specification requires checking the wallet scenarios above and publishing the operator's actual stewardship policy. Documentation of the desired boundary alone is not evidence that every installed wallet implements it.

## References

* [BRC contribution process](/#contributing)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-44: Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044)
* [BRC-46: Wallet Transaction Output Tracking](/wallet/0046)
* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-100: Wallet-to-Application Interface](/wallet/0100)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)
* [BRC-123: Basket Permission Scheme Registry and Governance](/wallet/0123)
* [BRC-22: Overlay Network Data Synchronization](/overlays/0022)
* [BRC-24: Overlay Network Lookup Services](/overlays/0024)
* [BRC-76: GASP](/transactions/0076)
* [IANA Service Name and Transport Protocol Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/)
* [RFC 6335: Service Name and Transport Protocol Port Number Registry Procedures](https://www.rfc-editor.org/rfc/rfc6335)
* [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174): requirement terminology


# Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections

* Ty Everett (<ty@projectbabbage.com>)
* [Deggen](https://x.com/deggen)

## Abstract

This proposal specifies how applications discover multiple wallets, select one wallet per connection, and carry the complete BRC-100 core interface over local, embedded, extension, and encrypted mobile substrates. Separately specified additive `getVersion` metadata is permitted under section 1 without negotiating away any core method or guarantee. It defines ascending localhost port allocation, a neutral chooser contract, origin-scoped selection memory, safe switching, a vendor-neutral pairing URI, and a mandatory encrypted mobile relay profile. Discovery, connection management, and user interfaces are composable components outside the core SDK. Applications can use automatic selection or supply their own user experience, including commercial onboarding wrappers.

**Status: proposed specification; draft for review.** The wire profiles and requirements below are proposed interoperability contracts, not claims that existing wallets implement them. No implementation or security-audit result is asserted.

## Motivation

An application should be able to use one wallet while another application uses another wallet on the same computer. A user should also be able to use a phone wallet from a desktop browser or move from a phone browser into a wallet and back. A single occupied localhost port, a last-writer-wins injected global, or a race between transports must not determine the user's wallet.

The September 23–24, 2026 discussion between Deggen, Ty Everett, and wallet users identified these problems and proposed ascending ports, selection among substrates, embedded-wallet defaults, and mobile QR pairing. The relevant public discussion and implementation inputs are linked in the references. This proposal turns those inputs into one connection contract while preserving the existing wallet interface.

## Specification

### 1. Scope, terminology, and conformance

The capitalized requirements use [BCP 14](https://www.rfc-editor.org/rfc/rfc8174). Sections 1–12 are normative. Subsequent sections are non-normative.

**The complete BRC-100 core contract is mandatory; core-method negotiation is prohibited.** This proposal MUST NOT add BRC-100 methods, parameters or result fields, or permit negotiation of a subset of its methods. `getVersion({})` retains its `{ version: string }` result shape. A legacy adapter MAY use the call as a noninteractive readiness probe; a successful probe or metadata advertisement is not proof of conformance, security or authorization.

Additive attributes within that string MAY use [BRC-205](/wallet/0205), which extends the complete string to at most 2048 ASCII bytes while retaining a 7–30-byte base. Wallets MAY emit that format without a separate opt-in, coordinated configuration, or support-discovery exchange. Consumers adopting BRC-205 MUST accept its bounds and parsing rules, including base-only results and ignoring unknown attributes. This is a narrow exception to this document's unchanged-result wording: only the syntax and length of `getVersion`'s string contents are extended, not its result shape, call code, transport framing, or the core behavior of any other method. Older strict validators may reject extended strings and need updates; BRC-185 support alone does not prove that an implementation's validator has been updated. No metadata-profile negotiation mechanism is introduced here.

Future BRC-defined attributes MAY advertise support for additive permission schemes at the existing reserved dispatch points in [BRC-98](/wallet/0098) (protocol IDs), [BRC-99](/wallet/0099) (basket IDs), and [BRC-111](/wallet/0111) (action labels). These use `p` followed by a space-free scheme/module ID and the scheme-defined remainder; the governing BRC's syntax and constraints still apply. Advertisements MUST distinguish the namespace and exact scheme/module ID and identify the defining specification. They MUST NOT replace mandatory methods, change ordinary non-scheme operation semantics, or themselves grant permission or bypass security checks. The wallet still decides whether it supports the explicitly requested scheme and applies that scheme's defined permission and operation rules; unsupported reserved identifiers remain rejected. A missing advertisement is not proof of non-support, and a present advertisement is not proof that an operation will be authorized or succeed. This BRC allocates no scheme-advertisement key or list format; future specifications define those details and their freshness/privacy behavior.

Every wallet claiming conformance MUST fully implement **all 28 BRC-100 methods**, and every conforming connection MUST carry all of them, with their complete arguments, results, errors, and originator semantics. An application is entitled to assume the full interface. An omitted implementation, method whitelist in a relay, or unsupported-method stub is nonconforming. A genuine permission denial, lock state, invalid argument, insufficient balance, or documented resource failure is not a missing method. Blanket denial used to conceal an absent implementation is nonconforming. [BRC-116](/wallet/0116) permission decisions continue to apply; neither discovery nor selection grants permission. A restricted tool-server facade is not a substitute for a conforming wallet connection.

| Term                | Definition                                                                                                                              |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Provider            | One independently selectable wallet instance. A product name, account, transport, and provider are different things.                    |
| Substrate / adapter | A communications mechanism / its implementation behind the discovery and connection interfaces.                                         |
| Candidate           | One provider and its reachable transport aliases, with provenance and availability state.                                               |
| Connection          | An immutable binding to one provider, one application security context, and one connection epoch.                                       |
| Epoch               | A fresh, random UUIDv4 created when a binding is established. Wallet-specific references belong to that binding.                        |
| Controller          | Framework-neutral discovery, selection, memory, and connection orchestration outside the core SDK.                                      |
| Chooser             | A replaceable presentation adapter; graphical, terminal, native, or application-defined.                                                |
| Wrapper             | Application-selected functionality around a bound `WalletInterface`, such as onboarding, funding, or monetization.                      |
| Application origin  | For a web application, the serialized URL origin: scheme, ASCII host, and nondefault port. It is not merely a domain.                   |
| Originator          | The BRC-100 application domain supplied to wallet methods. For web callers this is the origin's ASCII hostname, without scheme or port. |

A controller MUST distinguish the full application origin for connection/memory isolation from BRC-100's domain-shaped originator. It MUST NOT change existing BRC-100 permission semantics by treating its full origin as a new method argument. Non-web hosts MUST supply an explicit, stable application context and originator; a headless caller cannot acquire browser-origin authenticity by supplying a string.

Web URL parsing and origin serialization use the [WHATWG URL Standard](https://url.spec.whatwg.org/). A supplied origin MUST equal its parsed URL's serialized origin, with no credentials, path, query, or fragment; reject opaque `null` origins. For this profile loopback means `localhost`, a canonical dotted-decimal IPv4 address in 127.0.0.0/8, or `[::1]`. HTTP descriptor addresses MUST be serialized origins with one of those hosts and their actual port; no arbitrary DNS hostname, user information, path, query, redirect, or fragment is a loopback-discovery address. Display the complete ASCII origin when asking for approval; a friendly application name does not replace it.

Conformance is role-specific: a wallet implements the full interface and its advertised substrates; an adapter implements its advertised substrate; a controller and chooser implement their respective requirements; a mobile implementation implements sections 8–10. A wallet need not implement every physical substrate. Controllers MUST accept additional adapters through the same interfaces and MUST NOT exclude an otherwise usable provider because of vendor, commercial affiliation, or substrate family.

“Runtime support” means facts such as DOM availability, permitted loopback access, native bridge presence, or an interactive terminal. It MUST NOT mean a subset of BRC-100 methods. Wire version `1` below versions this connection protocol, not BRC-100.

### 2. Component boundaries and automatic WalletClient integration

The dependency direction MUST be:

```
application / commercial wrapper
             |
       WalletInterface
             |
   bound connection / WalletClient
             |
 external connection controller ---- external chooser / onboarding hooks
             |
 interchangeable discovery and transport adapters
             |
           wallet
```

Discovery algorithms, scanners, provider registries, pairing, relay clients, storage, chooser state, and framework-specific UI MUST live outside the core SDK source and runtime dependencies. The SDK MAY define a small interface and delegate to an externally supplied controller. A controller MUST return a complete `WalletInterface`; it MUST NOT require applications to know which substrate supplied it.

The following behavioral interface is normative; equivalent language bindings are permitted:

```ts
interface ConnectionRequest {
  applicationContext: string
  originator: string
  mode: 'automatic' | 'ask' | 'explicit'
  explicitProvider?: string
  signal?: AbortSignal
}
interface ConnectionProvider {
  resolve(request: ConnectionRequest): Promise<BoundConnection>
}
interface BoundConnection {
  wallet: WalletInterface
  providerId: string
  epoch: string
  close(): Promise<void>
}
```

`providerId` is controller-local and MUST NOT be a wallet account identity key. The controller MUST expose state changes, a current candidate snapshot, explicit refresh, choose/change-wallet, forget-preference, and disconnect operations to presentation adapters. Subscription MUST return an unsubscribe function. Snapshot entries MUST include provider ID, display name, provenance, availability, transport aliases, and whether continuity was verified. Discovery completion MUST include per-adapter `complete`, `partial`, `blocked`, or `unsupported` coverage; an empty partial snapshot is not “no wallet installed.”

For JavaScript auto integration, an external package MAY register a `ConnectionProvider` under `globalThis[Symbol.for('brc185.connection-provider.v1')]`. Registration MUST be explicit application initialization, MUST reject replacement of a different registered provider unless the application explicitly requests replacement, and MUST finish before the first automatic connection. An explicitly supplied provider takes precedence over the registration. The registered provider MUST implement `resolve`; malformed registrations fail with `CONFIGURATION_ERROR`. This is a same-realm integration hook, not an authenticated extension discovery channel.

An upgraded `WalletClient` in automatic mode MUST delegate to the supplied or registered provider on its first connection attempt. The controller automatically invokes the chooser only when section 5 requires it. Concurrent first calls MUST share one pending resolution; they MUST NOT produce multiple chooser windows or multiple bindings. After resolution, calls use that binding until explicitly closed. Resolution cancellation MUST reject every waiting call without sending it to any wallet.

An explicitly supplied `WalletInterface` or explicitly selected legacy substrate MUST retain its existing behavior and MUST NOT unexpectedly open a chooser. In the absence of a supplied/registered provider, the SDK MUST preserve the existing automatic connection path and errors; upgrading the SDK alone MUST NOT add a DOM dependency, scan additional ports, open a popup, or install a global controller. A host with no chooser can use an explicit provider or unambiguous automatic selection; otherwise it receives `SELECTION_REQUIRED` with the snapshot. It MUST NOT hang awaiting nonexistent UI.

SDK documentation MUST show separately distributed browser/React, plain HTML/JavaScript, Node/terminal, and headless integrations, plus custom adapter, UI, storage, and wrapper composition. Example package names and APIs MUST be labeled as examples until those packages exist. Importing the core SDK in Node, a worker, React Native, or server rendering MUST not access DOM globals.

### 3. Discovery records, continuity, and privacy

Discovery MUST be noninteractive at the wallet-permission layer. It MUST NOT sign transactions, retrieve balances or account identity keys, unlock a wallet, or request BRC-100 permissions. Browser or OS permission to access a substrate is a separate state that the controller MUST explain and respect.

New providers expose the following descriptor. JSON in this BRC is UTF-8, rejects duplicate property names, invalid Unicode, nonfinite numbers, and unknown fields unless an explicit extension field is defined. Cryptographic canonicalization uses [RFC 8785 JCS](https://www.rfc-editor.org/rfc/rfc8785). Base64url uses the unpadded RFC 4648 alphabet; decoders MUST reject noncanonical encodings.

| Field           | Type and requirement                                                                                                               |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `version`       | Integer, exactly `1`.                                                                                                              |
| `instanceId`    | Lowercase UUIDv4, stable for this provider and application context. Different independently selectable profiles use different IDs. |
| `name`          | Plain text, 1–80 UTF-8 bytes; product name, never executable markup.                                                               |
| `instanceLabel` | Plain text, 0–80 UTF-8 bytes; optional user-understandable distinction between instances, no secret/account identity.              |
| `icon`          | Optional image reference under section 3.1; the same decoded value used by `brc185-icon`.                                          |
| `vendor`        | Lowercase reverse-domain string, 1–253 ASCII bytes; self-asserted, not proof of domain ownership.                                  |
| `continuityKey` | Base64url of a 32-byte Ed25519 public key generated independently of wallet spending/identity keys.                                |
| `endpoints`     | 1–32 records `{id, type, address}`. IDs are unique 1–64-character ASCII tokens matching `[A-Za-z0-9._-]+`.                         |

`type` is `http-wire`, `http-json`, `https-json`, `window`, `xdm`, `react-native`, or an absolute HTTPS URI naming an extension's public specification. `address` is a URL for HTTP types and an opaque adapter-local handle for other types, at most 2048 UTF-8 bytes. An endpoint record never authorizes connecting to a different host. Adapter scope and application policy still apply.

The provider MUST maintain a distinct random continuity key and instance ID per application context; transports for the same instance/context share them. They MUST persist across ordinary restarts, MUST rotate on reset, and MUST NOT be derived by exposing an account identity key. Reset/rotation means a new provider for remembered-choice purposes. Private continuity material MUST be protected as a secret, using secure OS storage where available. It MUST never be returned to an application or relay.

A provider exposing only one active internal account at a time MAY retain its `(instanceId, continuityKey)` when that account changes. This proves provider continuity, not account continuity; section 6 still requires invalidation of the old account's binding. Independently selectable provider instances/profiles remain distinct.

`describe(origin, challenge)` returns `{descriptor, challenge, signature}`. `challenge` is a fresh 32-byte random base64url value supplied by the controller. The Ed25519 signature is over:

```
UTF8("BRC-185 descriptor v1\n") || JCS({origin, challenge, descriptor})
```

Use pure Ed25519 from RFC 8032, not Ed25519ph or a wallet signing key. The controller MUST verify the signature, exact challenge and context before using continuity or merging aliases. The response is bounded to 16,384 bytes. `origin` is supplied by the trusted adapter context; a web request body's origin claim MUST NOT override the browser-supplied origin.

Two candidates MUST be merged only when they prove the same `(instanceId, continuityKey)` in the same context, or a trusted application-supplied adapter explicitly attests that they are aliases of the same object. Name, vendor, icon, port, wallet version, response speed, and account public key are insufficient. A single product can legitimately occupy several rows. Conflicting metadata for a proven identity is shown as a conflict and requires explicit selection; it MUST NOT silently overwrite an existing row.

Legacy providers lacking this record MUST remain usable through adapters. They are marked `legacy/unverified`; a controller MUST NOT invent verified continuity or silently persist a transport address as wallet identity. On a later resolution, a remembered legacy choice requires confirmation. Within a live, unmodified explicit object binding it remains usable normally.

Names and textual metadata MUST be rendered as inert text. The descriptor has no analytics URL, balance, certificate, account key, or method list. Optional UI artwork may be application-supplied or use section 3.1's provider-supplied image reference. It is treated as untrusted and MUST NOT trigger network requests merely because a wallet announced itself. Discovery records and the candidate inventory MUST remain local to the application unless the user explicitly requests sharing them. Storage denial MUST degrade to in-memory preferences, not block connection.

Continuity proves possession of a previously seen transport key. It is not product certification, account authentication, code integrity, or protection against a compromised application, wallet, browser extension, or operating system.

#### 3.1 Optional wallet icon reference

This BRC defines the optional BRC-205 attribute `brc185-icon`: one image reference for the responding wallet product. Its value uses BRC-205's percent-encoded UTF-8, single-occurrence, generic bounds and unknown-key rules. The decoded reference MUST be 1–256 UTF-8 bytes and one of:

* An absolute HTTPS URL serialized under the WHATWG URL Standard, with no credentials or fragment.
* `uhrp://<identifier>`, where the case-sensitive identifier is Base58Check of prefix bytes `ce00` and a 32-byte SHA-256 hash, as described in [BRC-26](/overlays/0026).
* A canonical `chirp://<rootIdentifier>` URI under [BRC-167](/overlays/0167).

Scheme names MUST be lowercase; identifiers and HTTPS paths/queries retain their case-sensitive meaning. Consumers MUST validate the reference without trimming, case-folding its identifier, or applying another percent-decoding pass to the BRC-205 value. Inline data URIs and other schemes are outside this attribute. Omission means no icon is supplied; invalid or unsupported icon metadata is treated as absent and MUST NOT prevent wallet use or change selection.

A provider MAY include the same decoded reference in its optional descriptor `icon` field, covered by the existing descriptor signature, so a chooser can obtain artwork without invoking wallet methods. The attribute supplies artwork through the existing `getVersion({})` result on the selected connection or an otherwise permitted noninteractive legacy readiness probe. Neither path authorizes retrieving account labels from every discovered wallet or causing a permission/unlock prompt. An icon, its URL, or a signature over that URL is not vendor certification, provider identity, account continuity, or an authentication result.

Controllers/UIs MAY resolve supported references when presenting a chooser or a connected-wallet surface. Resolution MUST remain outside the core SDK dependencies, honor application fetch policy, and apply bounded retrieval and image-decoding limits. UHRP and CHIRP resolution MUST verify content under their respective protocols. Retrieved artwork MUST be displayed only in a non-executable image context, never inserted as document markup. Missing resolver support, blocked access, failed retrieval or an invalid image MUST fall back to generic artwork without delaying or rejecting connection. Wallets MUST supply public product artwork, not account-specific identifiers, secrets, tracking tokens or application histories in the reference.

Consumers MAY cache images by the exact reference using HTTPS cache semantics or the applicable content-addressed integrity rules. They SHOULD refresh the reference from a new descriptor snapshot or selected-wallet metadata result when presenting a new connection surface, and MUST discard the previous provider's displayed reference on provider switch. Icon changes MUST NOT invalidate an epoch, merge providers, or alter chooser ordering. Remote retrieval can reveal the user's IP address and connection timing; applications MAY disable it or use application-supplied artwork.

| Example                                                                                                 | Interpretation                                                                                            |
| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `example-1.2.3; brc185-icon=https%3A%2F%2Fwallet.example%2Ficon.png;`                                   | Valid BRC-205 attribute; decoded reference is `https://wallet.example/icon.png`.                          |
| Descriptor `icon: "https://wallet.example/icon.png"`                                                    | The same reference, without BRC-205 value encoding, inside the signed descriptor.                         |
| Decoded `uhrp://<identifier>` or `chirp://<rootIdentifier>`                                             | Valid only with the corresponding valid identifier; angle-bracket text is schematic, not a literal value. |
| Decoded `http://wallet.example/icon.png`, `data:image/png;base64,...`, or an invalid content identifier | Icon absent.                                                                                              |
| A decoded reference over 256 UTF-8 bytes, or duplicate `brc185-icon` keys                               | BRC-205 generic validation makes all attributes unavailable; wallet use remains unaffected.               |

### 4. Local and embedded substrate profiles

#### 4.1 Ascending localhost allocation

The starting ports are fixed:

| Substrate                   | First candidate          | Subsequent candidates |
| --------------------------- | ------------------------ | --------------------- |
| BRC-100 binary HTTP / BRC-5 | `http://localhost:3301`  | 3302, 3303, …, 65535  |
| BRC-100 JSON HTTP / BRC-5   | `http://localhost:3321`  | 3322, 3323, …, 65535  |
| BRC-100 JSON HTTPS / BRC-5  | `https://localhost:2121` | 2122, 2123, …, 65535  |

A wallet starting a listener MUST attempt an exclusive bind at the first port and increment by one after address-in-use failures until a bind succeeds or its configured allocation ceiling is reached. The bind itself is the allocation operation; a separate availability test is insufficient. Unrelated services also occupy ports. Other bind errors MUST be surfaced, not treated as evidence of an available wallet. No vendor owns a port. A wallet with several listeners allocates them independently and advertises all successfully bound endpoints; offsets need not match.

The sequences intentionally overlap. **Port number does not identify wire format.** For example, a binary listener may bind 3321 after earlier ports are occupied. New endpoints MUST serve `GET /.well-known/brc-wallet` for discovery independently of their RPC format. The controller reads the signed endpoint type before sending RPC. It MUST deduplicate identical scheme/host/port probes across scan sequences. A wallet MAY serve both formats on one listener if it dispatches by the BRC-5 content type and advertises both.

Listeners MUST bind only loopback interfaces, never wildcard/LAN interfaces for this profile, and MUST validate `Host` and web `Origin`. Implementations using both IPv4 and IPv6 MUST ensure both addresses route to the same instance for an advertised `localhost` endpoint; otherwise they MUST advertise the actual loopback literal as a distinct address and avoid ambiguous `localhost` binding. A failure to reserve one address family MUST NOT cause requests to be routed to a different wallet in that family.

The default discovery round examines offsets 0–63 inclusive from each base, in ascending offset order, with at most 16 simultaneous requests and a 1000 ms deadline per discovery request. It MUST NOT stop at the first gap or first successful wallet. `http://localhost` overlap is probed once; HTTP and HTTPS remain separate addresses. These are bounded default discovery windows, not reserved port ranges or allocation ceilings. A larger explicit window or explicit loopback endpoint MAY be configured. The snapshot MUST disclose the covered intervals and any blocked/timed-out probes. UI MUST offer refresh and a way to extend/configure the window when coverage is incomplete. It MUST NOT claim to have searched all ports or all installed wallets.

Discovery SHOULD begin only on an application wallet-use or connect action. A controller MUST NOT repeatedly rescan in the background, work around browser local-network permissions, disable certificate validation, or follow discovery redirects. A policy-blocked scan is `blocked`, not evidence of absence. A browser UI can ask for local-network access through normal browser controls or offer another substrate. The HTTPS profile requires a normally trusted certificate; the HTTP profile retains the local BRC-5 trust boundary and MUST NOT be presented as encrypted remote pairing.

The discovery request has query parameter `challenge` containing the 32-byte challenge. Web servers derive `origin` from the browser `Origin` header. Non-web adapters send an explicit `Originator` plus a configured application-context header `BRC-185-Context`; these are host assertions, not browser authentication. Responses are `200 application/json` with the section 3 proof, `Cache-Control: no-store`, and `Vary: Origin`. CORS MUST return the exact permitted origin, never reflect `null`, and MUST NOT enable credentials or authorize wallet operations through discovery. Preflight is noninteractive. Descriptors MUST advertise the responding endpoint with a matching scheme, host, port, and format.

To bind a new-profile HTTP connection, the controller sends `POST /.well-known/brc-wallet/connect` with JSON `{epoch, challenge, instanceId, continuityKey}` and the same origin rules as discovery. `epoch` is a new UUIDv4 and `challenge` is a new random 32-byte base64url value. A matching provider returns `{epoch, descriptor, challenge, signature}`, where signature covers `UTF8("BRC-185 connect v1\n") || JCS({origin, epoch, challenge, descriptor})`. A mismatch returns HTTP 412 without binding. The controller verifies this fresh proof and the selected identity before accepting the connection. The wallet binds epoch to this application context and its current account context; existing epochs cannot be rebound.

New-profile HTTP RPC uses `POST /brc185/v1/rpc/<BRC-100 methodName>` with headers `BRC-185-Epoch`, `BRC-185-Instance`, and `BRC-185-Key` carrying the bound epoch, instance ID, and continuity public key. Before dispatch, the wallet MUST validate all three against a live binding and the request origin. An unknown, retired, or mismatched binding returns HTTP 412 with a section 6 `PROVIDER_CHANGED` error and `not-sent` delivery. The distinct path is necessary: if the port is reused by a legacy wallet that ignores new headers, it must not accidentally execute a legacy method route. HTTP 404, missing binding validation support, and changed proofs MUST NOT trigger fallback/replay of that call onto legacy routes.

The new route carries the existing [BRC-5](/wallet/0005) binary or JSON method payload/result representation, content type, and origin semantics, subject only to section 1's separately adopted `getVersion` string-content profile; it adds no BRC-100 argument. Success uses HTTP 200 and the original method result/error representation. `DELETE /.well-known/brc-wallet/connect/<epoch>` with the three binding headers closes the binding and returns HTTP 204; the same origin checks apply. CORS preflight MUST support those named headers and DELETE for permitted origins. Responses MUST be noncacheable. An actual listener/account restart invalidates in-memory bindings even if its continuity key persists.

Existing `POST /<methodName>` routes remain unchanged for legacy clients. No `/v1` prefix is added to those existing routes. Adapters MAY probe legacy default endpoints using unchanged `getVersion({})` when the well-known path is absent. Only the known legacy default format is assumed in that fallback; nondefault ambiguous endpoints require an explicit format configuration. An arbitrary HTTP response is not a wallet. A legacy HTTP binding lacks this new protection against port reuse and MUST be identified accordingly; probing a version string cannot supply it.

#### 4.2 Browser extensions and `window.CWI`

Multiple new providers MUST announce independently; they MUST NOT compete to overwrite `window.CWI`. The JavaScript event names are `brc185:request-provider` and `brc185:announce-provider`. A controller first installs its announcement listener, then dispatches a `CustomEvent` on `window` with detail `{version:1, requestId}` where `requestId` is a UUIDv4. Each provider responds with detail `{version:1, requestId, handle}`. `handle` exposes `describe(origin, challenge)` and `connect(origin, epoch, onClose): Promise<WalletInterface>`, where `onClose(reason)` reports `account-changed`, `revoked`, or `lost`. The returned interface is bound to that epoch and cannot be retargeted. Providers MUST answer each fresh request once and MUST NOT trigger wallet prompts when announcing or describing. The initial event collection window is 200 ms; late announcements MUST update the snapshot and never replace an already bound connection.

The controller MUST call `describe` with the actual `window.location.origin` and a fresh challenge, with a 1000 ms noninteractive discovery deadline. A timeout is partial coverage. `connect` MUST bind that origin. The handle also exposes `disconnect(epoch): Promise<void>` to close its binding without modifying any BRC-100 method. Browser extensions MUST derive the web origin at their trusted extension boundary and MUST NOT accept an override from page-supplied arguments. A controller MUST snapshot and validate metadata, limit unsolicited announcements, and retain a dismissal option for abusive providers. Page events and provider labels themselves are not authenticated extension installation evidence.

An adapter MUST also support an existing `window.CWI` object as a legacy provider. If a trusted host identifies it as the same object as an announced provider, it is one candidate. Otherwise a potentially duplicate entry remains distinguishable by provenance; guessing a merge is prohibited. A legacy overwrite cannot reveal providers that were already overwritten; controllers MUST not claim that it can.

#### 4.3 XDM and React Native

A top-level browser document whose parent is itself has no parent-XDM provider; that adapter reports `unsupported` without sending a message. A missing native bridge similarly reports `unsupported`, rather than waiting on a nonexistent channel.

New XDM discovery sends `{type:"brc185:request-provider",version:1,requestId,challenge}` and returns `{type:"brc185:announce-provider",version:1,requestId,proof}` through JSON `postMessage` data. The request ID is a UUIDv4 and challenge is a fresh random 32-byte base64url value. The discovery response deadline is 1000 ms; a timeout is partial coverage. Initial discovery MAY send only those nonsensitive values to the direct parent with target `*` when its origin is not yet known. The receiver derives the application origin from `MessageEvent.origin`. The controller MUST accept an announcement only from that expected parent window, reject opaque/null origins, verify the proof, and pin `event.source` and `event.origin` for every subsequent message. It MUST NOT broadcast wallet calls to all windows. Application-registered wallet frames require an explicit expected source and origin.

XDM connection establishment sends `{type:"brc185:connect", version:1, instanceId, continuityKey, epoch, challenge}` to the pinned source/origin. The wallet validates identity and returns `{type:"brc185:connected", version:1, proof}` with the same signed connect proof as HTTP. The controller verifies it before invoking methods. A new epoch binds the current account context; mismatches return `{type:"brc185:connection-error", version:1, epoch, error}` carrying a section 6 error. Wallets MUST reject invocation before establishment.

New XDM invocation envelopes are `{type:"brc185:invoke", version:1, instanceId, epoch, id, method, params}`. `epoch` and `id` are UUIDv4 values. Responses are `{type:"brc185:result", version:1, instanceId, epoch, id, result}` or the same routing fields with `type:"brc185:error"` and `error:{code, message, data?}`. `code` and optional `data` preserve the underlying wallet error; stack traces SHOULD be withheld. The wallet derives originator from the pinned caller's hostname. All routing fields MUST match the active binding; unknown methods and mismatched fields are rejected before dispatch. Every BRC-100 method and its existing JSON representation is carried. Result and error are mutually exclusive. New envelopes coexist with, and MUST NOT reinterpret, legacy BRC-6 `CWI` envelopes. Either side closes with `{type:"brc185:closed", version:1, instanceId, epoch, reason}`, with reason `account-changed`, `revoked`, or `lost`; it MUST come from the pinned peer. Closed epochs are never reused.

React Native adapters use those JSON envelopes over the host's registered WebView bridge, with origin derived from the native host's verified current document URL. The host MUST invalidate a binding on navigation to another origin and MUST distinguish independent WebViews/providers. A page-provided origin string, `isTrusted` alone, or the presence of a `postMessage` function is not origin validation. Legacy XDM and React Native bridges remain eligible through their existing adapters. A trusted host-provided `WalletInterface` can be supplied directly with no discovery at all.

### 5. Selection policy and neutral presentation

Applications select `automatic`, `ask`, or `explicit` behavior when instantiating the controller/chooser integration. The default is `automatic`. An application MAY declare an embedded provider as its preferred/default provider; it MUST do so explicitly through configuration or a trusted host binding, never by recognizing a favored vendor name.

Apply the following order after adapter coverage is known:

| Condition                                                                             | Required behavior                                                                                                        |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Explicit object/provider supplied                                                     | Bind that provider or return its error; do not substitute another.                                                       |
| `ask`                                                                                 | Present the chooser even for one candidate; await selection.                                                             |
| Account-change invalidation satisfying section 6's same-provider rebinding conditions | MAY establish a fresh binding to that provider without a chooser, subject to application selection policy.               |
| Valid remembered choice, verified as the same provider                                | Reuse it without a chooser. Other newly discovered wallets do not disrupt it.                                            |
| Remembered choice missing, changed, conflicting, or unverifiable                      | Ask again; do not fall through to a different single candidate. Preserve the old preference until replaced or forgotten. |
| App-configured embedded default available                                             | Select it without a chooser; other adapters may be deferred until Change wallet.                                         |
| Exactly one candidate and discovery coverage complete within the configured scope     | Select it without a chooser.                                                                                             |
| Multiple candidates, or unresolved coverage that could change an automatic choice     | Present the chooser with the coverage state.                                                                             |
| No candidates, discovery complete                                                     | Offer phone pairing and application-provided onboarding/install paths.                                                   |
| No candidates and blocked/partial discovery                                           | Explain access/coverage, offer retry, phone pairing and onboarding; do not assert no wallet is installed.                |
| Choice required but no UI adapter                                                     | Return `SELECTION_REQUIRED`; include candidates and coverage.                                                            |

“Complete” is completion of the configured discovery scope, not proof that no other wallet exists anywhere. An explicitly configured embedded-only initial scope is valid and normally gives a single automatic default. An expanded chooser MUST include all usable candidates returned by all configured adapters, including extensions, localhost, window\.CWI, XDM, React Native, and established mobile connections. It MUST expose unavailable/blocked adapters as coverage information. Application/runtime restrictions must be disclosed, not disguised as vendor incompatibility.

Phone pairing and wallet installation are actions, not fictitious discovered wallets. Their availability alone MUST NOT force a chooser popup when an existing desktop or embedded wallet can be automatically selected. Applications MUST have an API to request the chooser/phone path explicitly. A graphical integration MUST provide a reachable Change wallet/Use phone control, even when initial selection is automatic; an app-defined settings or connection menu satisfies this requirement.

When automatic selection chooses the sole discovered provider and it is not the application's configured embedded default, the graphical integration MUST immediately identify the selected provider and show a Change wallet control in the connection surface. No additional confirmation or chooser is required merely to show this state.

Chooser rows MUST use equal typography, icon area, interaction affordances, and information density. No sponsorship, vendor relationship, port number, response speed, or adapter priority may influence wallet ordering, default selection, prominence, or omission. The default order is ascending Unicode code-point order of `name`, then `instanceLabel`, then `providerId`; UI MAY instead preserve a user-set order. A label identifying the user's current/remembered choice or the application's embedded default is permitted factual state, not an endorsement. Different transport aliases of one proven provider are one row with inspectable connection details.

Lock state is not absence. The controller MUST not silently move to another wallet because the selected wallet is locked, denied a permission, needs funding, or is temporarily offline. Wallet authentication and method permission remain wallet-controlled steps after selection. The chooser MUST support keyboard access, visible focus, accessible names, screen-reader status updates, cancel/back, and an equivalent non-QR path for users who cannot scan a code. Modal styling is not prescribed.

Selection memory is scoped to the full application context and provider proof. The default is to remember a successful selection for future visits when local storage is available; the UI MUST expose the remember setting and a forget action. Store only the provider identifier, continuity key, instance ID, selected mode, and necessary adapter-local locator. Do not store wallet keys, permissions, balances, account identity, pairing secrets, or a full discovered-wallet inventory. A changed account/identity notification invalidates the binding. A fresh explicit selection is required unless section 6's same-provider rebinding exception applies; remembered choice, an embedded default, or a lone candidate MUST NOT bypass that requirement. When a legacy substrate cannot signal account changes, the integration MUST disclose that limitation; it MUST never claim to verify account continuity through `getVersion`.

#### 5.1 User-visible connection states

Graphical integrations MUST expose understandable status and reachable actions for the applicable states below. These are presentation obligations, not new wire states or BRC-100 methods. An application MAY use a modal, panel, page, or native surface and its own wording and branding. Equivalent presentations MUST preserve section 5's equal treatment, accessibility, cancellation, and selection policy.

| State                                                         | Required presentation and actions                                                                                                                                                                                                                                                       |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Discovering                                                   | Identify that discovery is in progress and offer cancel. An empty pending snapshot MUST NOT be described as a completed search with no wallet installed.                                                                                                                                |
| Discovery blocked or partial                                  | Explain the access/coverage limitation, distinguish it from wallet absence, and offer retry, phone pairing and application-supplied onboarding/install paths. Expose the coverage controls required by section 4.                                                                       |
| Discovery complete, no candidates                             | Show an explicit empty state with phone pairing and application-supplied onboarding/install paths. Installation and pairing actions MUST remain distinguishable from discovered-provider rows.                                                                                          |
| Choice required                                               | Show eligible providers and coverage, with cancel/back and the remember/forget controls required by section 5. A selected/default label MUST NOT become preferential styling or commercial ordering.                                                                                    |
| Connected                                                     | Identify the bound provider and make Change wallet/Use phone and disconnect reachable. Connection MUST NOT be presented as account authentication or approval of a wallet operation.                                                                                                    |
| Locked/authentication required or waiting for wallet approval | Explain the wallet-controlled next step and distinguish it from discovery or installation. Preserve the pending request and section 6's cancellation/delivery rules; no fixed permission-response deadline is introduced.                                                               |
| Suspended, disconnected or closed                             | Distinguish recoverable transport suspension with retained session state from terminal closure requiring a new binding/pairing. Offer the applicable reconnect, fresh connection, or disconnect action without replaying wallet operations or promising that prior effects were undone. |

#### 5.2 Selected-account labels and bundled wallets

When adopting [BRC-205](/wallet/0205), an application SHOULD obtain `account-label` from `getVersion({})` on the selected connection when presenting account-continuation UI. It MUST NOT query all candidates for labels, use a label to select/rank a provider, replace descriptor `name` or `instanceLabel` with it, merge providers on it, or use it as a remembered-choice key. The label MUST remain distinguishable from provider provenance and application-owned action text. It MUST follow BRC-205's inert rendering, freshness, privacy and generic-fallback rules; missing/invalid metadata MUST NOT block wallet use. A label rename alone MUST NOT invalidate a binding or imply account replacement; an unchanged label MUST NOT conceal a real account change under section 6.

A wallet bundled with an application MAY be that application's explicitly configured embedded default. Other applications can discover it only if it exposes a supported, reachable substrate; an in-process embedded object alone is not an externally discoverable wallet service. The bundled application's default status MUST NOT transfer to unrelated applications. Those applications apply their own section 5 selection policy, show the provider's product name during discovery, and obtain any account label only in the selected context. Provider continuity and selection memories remain application-scoped; neither a shared display label nor installation by another application grants authentication or permission.

### 6. Binding, switching, and error semantics

An application MAY hold several explicitly created connections concurrently. Each has its own immutable provider and epoch. No ambient “active wallet” may retarget existing connections. Transport aliases MAY be selected before binding using app-configured runtime suitability; once requests have been dispatched, changing an alias requires a new epoch and the switching rules below. Selection does not migrate keys, funds, baskets, certificates, account sessions, or action references.

A new-profile wallet MUST bind an epoch to its selected internal account context without exposing an account identity key in discovery. Changing that context invalidates the epoch before another call can execute. In-flight work either completes against the original context or fails with its true delivery certainty; it MUST NOT move to the new context. Object/XDM/native adapters notify closure as specified above; HTTP invalidates the binding so the next request returns 412; mobile sends an encrypted `PROVIDER_CHANGED` session FAILURE with `cause: 'account-changed'` and closes. Legacy substrates without such notifications/bindings cannot claim this guarantee.

After a binding closes with reason `account-changed`, or an authenticated mobile session FAILURE reports `PROVIDER_CHANGED` with `cause: 'account-changed'`, the controller MAY establish a new binding to the same provider without a chooser. This exception requires that the provider is still present, re-proves the exact same `(instanceId, continuityKey)` with a fresh section 3 challenge for the same application context, and has no conflicting identity metadata. It remains subject to application selection policy: `ask` or an application requirement for explicit re-selection still requires selection. A product name, cached descriptor, remembered preference, unchanged continuity key alone, generic HTTP 412, or `PROVIDER_CHANGED` without a verified account-change cause is insufficient. If the provider is missing, changed, conflicting, or cannot re-prove continuity, the controller MUST require fresh explicit selection and MUST NOT substitute another provider or fall through to an automatic default.

On account-change invalidation, the controller MUST notify the application that the account changed and the old epoch closed, whether or not a replacement is established or a chooser is shown. This notification MUST precede admitting calls on any replacement binding. The replacement MUST have a fresh epoch and be exposed to the application as a new `BoundConnection`; existing connection objects MUST remain bound to their closed epoch. Old queued or in-flight calls MUST NOT be transferred, retried, or replayed onto the new binding, including calls known to be `not-sent`. New calls may begin only against the separately established binding, with action references and wrapper state isolated as specified below. Provider continuity does not establish account authentication or grant wallet permissions. For mobile, omitting the provider chooser does not omit pairing: sections 8–10 still require a fresh invitation, cryptographic session, continuity proof and explicit endpoint approvals; the closed session cannot be reopened.

On Change wallet, the controller MUST stop admitting new calls to the old connection and show pending work. The user/application can either wait for all calls to settle or explicitly abandon the connection. Waiting MUST NOT impose a permission deadline. Abandoning rejects unresolved application promises with delivery certainty as below, closes the old connection, and MUST NOT claim that wallet-side effects were undone. Where a BRC-100 action reference is known, an application MAY explicitly call `abortAction` on the old wallet; there is no invented universal cancellation method. New work begins only on a separately resolved binding.

`signAction`/`abortAction` references, in-progress funding flows, and wrapper state MUST stay with the wallet/epoch that produced them. A wrapper MUST be recreated or explicitly reset on switching; changing the `base` of a live wrapper with pending work is prohibited. An adapter MUST NOT retry a wallet method automatically after uncertain delivery, reconnect it to another wallet, or replay a transaction because the chooser reopened. Re-delivery of identical encrypted transport records within one live mobile session is not re-invocation; section 10 prevents duplicate dispatch.

Connection-layer errors are distinct from wallet errors and MUST retain the following fields:

```ts
type ConnectionError = {
  domain: 'brc185'
  code: string
  message: string
  delivery: 'not-sent' | 'unknown'
  epoch?: string
  cause?: string
}
```

Codes are `CONFIGURATION_ERROR`, `SELECTION_REQUIRED`, `USER_CANCELLED`, `DISCOVERY_BLOCKED`, `PROVIDER_CHANGED`, `PAIRING_EXPIRED`, `PAIRING_REJECTED`, `PROTOCOL_ERROR`, `RESOURCE_LIMIT`, `DISCONNECTED`, and `CONNECTION_CLOSED`. Before wallet dispatch, delivery is `not-sent`. If dispatch may have occurred and no authoritative wallet result exists, it is `unknown`. A network timeout cannot prove no transaction was created. An explicit BRC-100 error remains the original wallet error, not a connection error. Libraries MAY add a cause without replacing these fields. For a mobile account-context change, the wallet MUST include the exact string `cause: 'account-changed'` in its encrypted session FAILURE; the controller MUST NOT infer this cause from a relay event, error message, or permission/authentication failure. Other causes do not authorize the same-provider rebinding exception.

Applications MUST be able to distinguish discovery, choosing, pairing, connected, locked/authentication required, waiting for wallet approval, suspended, disconnected, and closed. `isAuthenticated:false` MUST NOT be converted into “install a wallet.” Permission requests follow [BRC-219](/wallet/0219): the wallet presents/maintains the prompt or returns an error; connected pending prompts have no fixed permission timeout. Cancellation of a chooser or unapproved pairing sends no queued BRC-100 call.

### 7. Wrappers, onboarding, and commercial composition

The controller's output MUST be wrappable as an ordinary `WalletInterface`. Applications MAY place onboarding, funding, branded presentation, developer/platform fees, or other commercial policies above it. This BRC neither mandates nor removes those policies. A transport/controller MUST NOT edit transaction outputs, suppress a wrapper's fee, insert its own fee, or reroute funding. Wallet permissions remain authoritative for any resulting action.

The application MUST be able to replace the default no-wallet/onboarding UI, supply install and same-device handoff routes, and receive typed connection errors. One component must own a given connection/onboarding interaction: a controller and wrapper MUST NOT each open a competing missing-wallet modal for the same resolution. Integration documentation MUST explain how to disable or delegate overlapping UI while retaining funding and commercial behavior. A user-cancelled chooser MUST not be transformed into an indefinite missing-wallet wait.

Discovery and chooser entry MUST NOT require a vendor account, paid directory listing, referral agreement, sponsored auction, or approval by a central catalog. A separate onboarding catalog MAY contain disclosed commercial offers. It MUST be distinguishable from the neutral list of discovered wallets and MUST NOT hide, rank, or preselect those wallets because of payment. A wrapper cannot claim a conforming chooser while using commercial ranking inside it.

For example, a connection can be resolved once and supplied to `new BabbageGo(connection.wallet, options)`. Funding retries after a definite insufficient-funds result remain wrapper/application decisions against that same binding. They MUST NOT become retries after ambiguous transport failure or against a replacement wallet. This preserves the composition boundary without standardizing a vendor's fee amounts, business model, or UI.

### 8. Mobile pairing and trust boundaries

#### 8.1 Endpoints and relay

Mobile connections MUST use authenticated end-to-end encryption. HTTPS/WSS to a relay alone does not satisfy this requirement. The mandatory profile is `Noise_NNpsk0_25519_ChaChaPoly_SHA256`, using [Noise revision 34](https://noiseprotocol.org/noise.html), X25519, ChaCha20-Poly1305, and SHA-256. Implementations MUST use maintained implementations of these primitives/protocol processing, cryptographically secure randomness, and reject an all-zero X25519 shared secret. Implementations MUST NOT invent alternate nonce, key derivation, or cipher constructions under version 1. A future cryptographic profile requires a separately specified version; unsupported versions fail closed, with no plaintext fallback.

The application is the Noise initiator; the wallet is the responder. The invitation carries a random 256-bit preshared key (PSK), never a seed, spending key, account identity key, or password. Both sides generate fresh ephemeral X25519 keys for every invitation. The PSK and ephemeral private keys MUST be erased after a successful handshake or abandonment; only the resulting in-memory session state remains. In managed runtimes implementations MUST remove references and overwrite mutable buffers where supported, without claiming guaranteed erasure of garbage-collected copies. The displayed QR/link MUST be removed at that point. Sessions MUST NOT resume after a process loses its cryptographic state.

Two deployment modes use the same wire protocol:

| `mode`    | Encryption endpoint and disclosure                                                                                                                                                                                                                                                                                           |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client`  | Default. The actual application client, including a browser, encrypts/decrypts. A separately deployed application backend and relay receive no BRC-100 plaintext or session keys.                                                                                                                                            |
| `backend` | Explicit configuration. The application's backend is the encryption endpoint and therefore can read the calls/results. Both connection UIs MUST say that the named application backend has this access before pairing is approved. Client-to-backend traffic MUST use authenticated HTTPS and application-session isolation. |

`backend` MUST NOT be selected automatically as a workaround for unavailable client cryptography. The mode is authenticated in the handshake. A relay is a replaceable ciphertext router and MUST NOT receive the PSK, ephemeral private keys, transport keys, decrypted payloads, or account keys in either mode. A backend that terminates the wallet encryption is an application endpoint, not a ciphertext-only relay. Self-hosted and vendor-operated relays use the same contract; no relay operator is privileged by this BRC.

This profile protects payloads from the relay and network intermediaries and provides forward secrecy through fresh ephemeral DH. It does not hide connection timing, approximate traffic volume, IP addresses, or the relay topic. It cannot protect plaintext from either authorized endpoint or compromised code running there. The QR/link is a short-lived bearer invitation: anyone who obtains it can attempt pairing, so explicit endpoint confirmation remains required.

#### 8.2 Invitation encoding

The vendor-neutral URI is exactly:

```
brc-wallet://pair#<base64url(JCS(offer))>
```

There is no query, username, port, trailing path, or alternate host in this URI. Fragment transport avoids placing the invitation in an HTTP request or Referer. An offer has exactly the following fields, all required:

| Field         | Value                                                                                                                         |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `version`     | Integer `1`.                                                                                                                  |
| `topic`       | 16 independently random bytes, base64url. Single invitation/session identifier.                                               |
| `relay`       | Absolute `wss:` URL of the relay WebSocket endpoint; no credentials, query, or fragment; at most 2048 UTF-8 bytes.            |
| `walletToken` | 32 random bytes, base64url; authorizes relay attachment as wallet, not wallet operations. Distinct from PSK and client token. |
| `psk`         | 32 independently random bytes, base64url, produced by the application encryption endpoint.                                    |
| `challenge`   | 32 independently random bytes, base64url, for the wallet descriptor proof.                                                    |
| `origin`      | Canonical HTTPS application origin, or a canonical HTTP loopback origin for explicit local development.                       |
| `originator`  | ASCII hostname of `origin`, 1–250 bytes.                                                                                      |
| `mode`        | Exactly `client` or `backend`.                                                                                                |
| `expires`     | Integer Unix seconds, no later than 300 seconds after invitation creation.                                                    |
| `returnUrl`   | `null`, or an absolute HTTPS URL with the same origin as `origin`, no credentials or fragment, at most 2048 UTF-8 bytes.      |
| `returnState` | 16 independently random bytes, base64url; for same-device return correlation, even when `returnUrl` is null.                  |

The decoded offer MUST be at most 4096 bytes. Encoders MUST ensure the complete encoded URI fits their selected QR capacity and MUST reject oversize offers rather than truncate fields or lower cryptographic entropy. Wallets MUST accept the full 4096-byte decoded limit through paste/link intake even when a particular camera/QR renderer cannot carry it. A QR renderer MUST expose the equivalent link/intake alternative.

Version 1 mobile origins are web origins. A native or terminal application can use an application-controlled HTTPS origin and MUST explain its non-browser context; this does not prove the native program owns the domain. It MUST NOT impersonate another application's origin. Non-web origin authentication mechanisms can be specified separately without changing BRC-100.

Parsers MUST reject offers whose expiry is at or before their current time and MUST reject offers more than 330 seconds in the future (the extra 30 seconds accommodates a slightly slow receiving clock). The originating endpoint's 300-second deadline remains authoritative; clock disagreement can fail pairing but cannot extend it. The application MUST destroy an unused invitation at its expiry and generate new topic, PSK, tokens, challenge, and state for Refresh. Expiry applies to establishing a new session, not to an approved session's pending permission requests. The application MUST permit only one completed wallet pairing per invitation and MUST invalidate the invitation after approval or cancellation.

Offers MUST be produced from the actual application context, not a URL supplied by an untrusted frame or the relay. QR data, tokens, and fragments MUST NOT enter logs, analytics, crash reports, public clipboard history services, or third-party URL shorteners. A pairing page MUST not load third-party code with access to the fragment and MUST remove the fragment from visible browser history as soon as it has safely handed the offer to the local pairing component. Only explicitly user-initiated copying is permitted.

#### 8.3 Origin and user confirmation

Before any wallet method can be sent, both endpoints MUST complete this sequence:

1. Validate the offer, establish the Noise handshake, and compute the confirmation code below. Handshake message payloads are empty. No BRC-100 request is a handshake payload.
2. The wallet sends encrypted `HELLO`, containing the section 3 proof for `offer.origin` and `offer.challenge`. The application verifies it and displays the wallet name, instance label, and provenance, including whether it matches a remembered continuity key.
3. Both surfaces display the application origin and the same confirmation code. In `backend` mode both also display the backend-access disclosure. The wallet MUST identify the origin as a pairing claim, not an independently verified browser attestation. The user MUST confirm that it is the intended application and compare the code across the actual application and wallet surfaces. For same-device use, the UI MUST support switching between those surfaces to compare it.
4. Each endpoint requires its user's explicit confirmation and sends encrypted `APPROVE`. Once both local and peer approvals exist, the connection becomes usable. Until then, a requester MUST NOT send and a wallet MUST NOT dispatch any BRC-100 method. Reject/back sends `REJECT` and destroys the session. A relay event or URL callback never substitutes for encrypted approval.

Let `h` be the final Noise handshake hash. Compute SHA-256 over `UTF8("BRC-185 confirmation v1\n") || h`; interpret its first four bytes as an unsigned big-endian integer; take its value modulo 100000000 and display eight zero-padded decimal digits, grouped `1234 5678`. A refresh/retry MUST generate a new invitation, not repeatedly guess confirmation codes within one invitation. The code is an authenticated-channel comparison aid, not a password or wallet identity.

An origin claim is bound against modification by the relay through the prologue, but a malicious QR issuer can claim a false origin. The code comparison and user's verification of the actual application surface are mandatory; branding or an unverified origin label MUST NOT be represented as cryptographic website ownership proof. In a browser the controller MUST source the claim from the real document origin. Permissions MUST bind to the confirmed originator and MUST never be silently reused for another origin.

#### 8.4 Same-device links and return

Wallets MUST accept the vendor-neutral offer through an in-wallet scan or paste/intake route. They SHOULD also expose OS-verified HTTPS app/universal links. After the user chooses a wallet, the application MAY wrap the same offer as:

```
https://<wallet-controlled-host>/<registered-path>#brc185=<base64url(JCS(offer))>
```

The wallet link endpoint is supplied by an application-configured adapter or an explicitly selected onboarding entry. The application MUST NOT follow a relay-supplied redirect to choose a wallet. The HTTPS route MUST use the platform's verified app-link association when opening a native wallet; a generic custom scheme MAY be offered as a disclosed fallback, never treated as authenticated app identity. Neither route changes the offer, encryption, or confirmation requirements. An OS's own app chooser is outside this BRC's ordering control; an application-owned list is subject to section 5.

After approval, rejection, or cancellation the wallet MAY open `returnUrl` after user action, appending `brc185_state=<returnState>` and `brc185_status=connected|rejected|cancelled` using normal URL query encoding. Existing occurrences of those two reserved parameters make an offer invalid. The wallet MUST NOT include tokens, keys, signatures, BRC-100 results, or permission grants in the return URL. The application MUST verify state against its locally outstanding invitation and take authoritative connection status only from the encrypted channel. It MUST consume the state once. A return link MUST NOT initiate/replay a wallet operation. A failed return MUST leave a visible manual Return to app path.

#### 8.5 Pairing presentation and recovery

Graphical pairing integrations MUST distinguish waiting for wallet intake, establishing the encrypted channel, awaiting endpoint confirmations, and connected status. They MUST make cancel/back reachable before approval and explain that both the application and wallet surfaces require confirmation. A relay peer-presence event, successful handoff, or one endpoint's approval MUST NOT be displayed as a completed connection.

While offering intake, the application MUST communicate the invitation's expiry and provide the equivalent non-QR path required by section 8.2. After the handshake, it removes the invitation artwork/link as required by section 8.1 and presents the origin/code comparison instead. Until both approvals establish the session, section 8.2's deadline still applies. On expiry it MUST disable the old attempt, explain the expiry, and offer explicit Refresh or cancel. Refresh MUST terminate the prior unapproved attempt and create fresh invitation material; codes and approvals MUST NOT carry into the replacement. Rejection, cancellation and process/state loss MUST have distinguishable recovery guidance and MUST NOT release queued methods onto another connection.

Same-device UI MUST explain how to switch between the actual application and wallet surfaces to compare the code, and provide the section 8.4 manual return path when automatic return fails. Ordinary surface switching with retained process state follows sections 9–10's suspension/reconnection rules, not automatic re-pairing. Once connected, the UI MUST distinguish wallet authentication and operation permission prompts from pairing confirmation, and MUST NOT apply the invitation expiry to those decisions. Active-connection and revocation controls remain required by section 10.

### 9. Relay wire profile

#### 9.1 Allocation and attachment

A version 1 relay exposes `POST /brc185/v1/sessions` over HTTPS. Request JSON is `{version:1, topic, expires}`. The relay MUST atomically reject an existing topic with HTTP 409 and expired/invalid requests with HTTP 400. On HTTP 201 it returns `{version:1, relay, clientToken, walletToken}`; tokens are distinct random 32-byte base64url values. The application validates fields and MUST refuse any relay URL that violates its configured relay-origin policy. The relay MUST NOT request the PSK or complete offer. Authentication, quotas, payment, and abuse controls for allocation MAY be deployment-specific; they cannot alter session semantics. No secret is transmitted in a URL query.

The WebSocket uses subprotocol `brc185.v1`. Its first message is UTF-8 JSON `{version:1, type:"attach", topic, role, token}`, with role `client` or `wallet`. The server responds `{version:1,type:"attached",peerPresent:boolean}`. It subsequently sends `{version:1,type:"peer",present:boolean}` on peer attachment changes. Invalid attachment closes with WebSocket code 1008. Expired, unapproved sessions cannot accept a first wallet attachment. At most one socket per role can be active; a valid replacement attachment closes that role's previous socket before accepting frames from the new socket. Client implementations MUST serialize their own reconnects.

The relay routes each binary message unchanged to the opposite role, in order, and never echoes it to the sender. It MUST NOT reinterpret the binary contents. When the other role is absent it MAY discard messages; endpoint replay, not relay storage, provides continuity. It MUST make peer presence changes visible. Text after attachment is restricted to the server control messages above and client `{version:1,type:"close"}`; unexpected text fails the session. Close erases tokens and ends the relay session. Either endpoint can close; this is transport closure, not proof of wallet-side rollback.

To preserve a successfully established session beyond invitation expiry, the client sends `{version:1,type:"established"}` after both encrypted approvals. This is the one additional permitted client text message. It MUST be sent before the invitation expires. The relay then accepts reattachments with the existing role tokens until explicit closure or actual resource/service failure; it MUST NOT impose a permission-response deadline. This relay notification is not authorization: endpoints still require encrypted approvals, and a malicious relay cannot grant them. Service policy MAY retire an idle session only when both peers have been absent for at least 24 hours, reporting it as disconnection on a subsequent attachment.

TLS certificate validation is mandatory. Redirects during allocation or attachment MUST NOT be followed automatically. A deployment MAY require a user-approved relay account or fee, but that is a relay service choice, not wallet-provider certification. Logs MUST omit tokens and payloads. The relay MUST enforce allocation/attachment resource limits and return a visible failure; none of these controls establishes trust in ciphertext contents.

#### 9.2 Handshake and transport records

The Noise prologue is:

```
UTF8("BRC-185 mobile v1\n") || JCS(publicOffer)
```

`publicOffer` is the complete offer with only `psk` omitted; the PSK is supplied separately to the Noise `psk0` input. Both sides MUST use identical canonical bytes. Neither the prologue nor its fields need to be sent to the relay. The handshake starts after both roles are attached. The first binary message is byte `0x00` followed by the initiator's Noise handshake message; the second is `0x01` followed by the responder's. With this pattern, 32-byte public keys, and empty payloads, each message is exactly 49 bytes including its leading type byte. Unexpected direction, extra handshake payloads, failed authentication, or malformed lengths abort pairing. Each side MUST cache its exact sent and received handshake bytes for the session. On reconnect before approval, it retransmits any already-generated handshake message verbatim; a byte-identical previously processed handshake message is a duplicate, not another Noise state-machine step. A responder receiving the duplicate first message retransmits its cached second message. A different message in an already processed handshake position is fatal. Retained handshake state can survive socket loss until invitation expiry; it MUST never be restarted with reused ephemeral keys. Loss of that state requires a fresh invitation.

After Noise `Split`, the initiator's first cipher state sends client-to-wallet and its second receives wallet-to-client; the responder uses the inverse directions. Each binary transport record is:

```
0x02 || sequence:uint64be || noiseCiphertext
```

Each direction starts sequence 0. Sequence MUST equal that direction's Noise cipher nonce. Additional authenticated data is `UTF8("BRC-185 record v1\n") || topicBytes || directionByte || sequenceBytes`, where direction is `0x00` client-to-wallet and `0x01` wallet-to-client. Use the Noise cipher state's normal `EncryptWithAd` / `DecryptWithAd` and monotonic nonce progression. Ciphertext is at most 65535 bytes, including its 16-byte authentication tag. Application plaintext is at most 65519 bytes. The nine-byte outer header is not part of that ciphertext limit. At 2^31 records in either direction, endpoints MUST stop admitting new requests and drain pending work before establishing a fresh approved session. They MUST never generate or accept a record with sequence 2^32 or greater; exhausting that remaining drain allowance is a resource failure with appropriate delivery uncertainty, never a nonce reset under an existing key.

Record plaintext is:

```
kind:uint8 || requestId:16bytes || total:uint32be || offset:uint32be || payload
```

The header is 25 bytes; each payload fragment is at most 65494 bytes. `total` counts the entire unfragmented payload; `offset` is its zero-based byte position. Fragments for a given `(kind,requestId)` MUST be contiguous, start at offset 0, preserve total, and end exactly at total. Different requests MAY interleave. Empty payloads use one fragment with total 0 and offset 0. Reassembly MUST finish and validation succeed before a wallet method is dispatched. Partial requests cannot execute.

| Kind            | Direction, ID, and payload                                                                                                                     |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x01 ACK`      | Either; zero ID; exactly 8 payload bytes containing highest contiguous authenticated sequence received, uint64be.                              |
| `0x02 HELLO`    | Wallet to client; zero ID; JCS descriptor proof, at most 16384 bytes.                                                                          |
| `0x03 APPROVE`  | Either; zero ID; empty.                                                                                                                        |
| `0x04 REJECT`   | Either; zero ID; empty.                                                                                                                        |
| `0x05 CLOSE`    | Either; zero ID; empty.                                                                                                                        |
| `0x10 REQUEST`  | Client to wallet; new random nonzero 16-byte ID; complete BRC-100 binary request including method call code, originator frame, and parameters. |
| `0x11 RESPONSE` | Wallet to client; matching ID; complete BRC-100 binary response including success/error code and existing response/error serialization.        |
| `0x12 FAILURE`  | Either; matching request ID, or zero for a session failure; JCS section 6 connection error, at most 4096 bytes.                                |

Controls other than HELLO MUST fit in one record with offset 0. Every request and response payload MUST be at most 67,108,864 bytes (64 MiB). Endpoints MUST support this limit for a single message, at least 16 outstanding dispatched calls, and at least 64 MiB of aggregate in-progress reassembly; greater aggregate use MAY return `RESOURCE_LIMIT`. This finite transport resource limit is not permission to omit methods. Oversize requests fail locally `not-sent`; oversize responses produce `RESOURCE_LIMIT` with `unknown` delivery because wallet effects may already exist. Implementations MUST bound length arithmetic and allocation before accepting fragments and never silently truncate a BEEF or other byte array.

The receiver MUST compare the BRC-100 originator frame with the approved offer's originator and reject mismatches before wallet dispatch. It MUST pass that originator to the wallet and MUST NOT substitute the relay hostname. Method codes, serialization, and results are exactly [BRC-100](/wallet/0100), with no method-name whitelist or rewritten fields. Unknown connection kinds or invalid control ordering terminate the connection with `PROTOCOL_ERROR`; an existing wallet error is returned as RESPONSE.

The endpoint state machine is fixed:

| State        | Accepted transition/control                                                                                                                                                                                                                                                                          |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `offered`    | Validate offer and attach; expiry/reject goes to `closed`. No wallet RPC.                                                                                                                                                                                                                            |
| `handshake`  | Exactly the two directional handshake messages; success goes to `confirming`; authentication/state failure goes to `closed`; socket loss with retained state goes to `suspended`.                                                                                                                    |
| `confirming` | Wallet sends exactly one HELLO before its APPROVE. Client accepts/verifies HELLO before its own APPROVE. Each side sends at most one APPROVE after local confirmation. ACK, REJECT, CLOSE, or fatal FAILURE are permitted; REQUEST/RESPONSE are prohibited.                                          |
| `connected`  | Both local and peer approvals exist. REQUEST/RESPONSE, ACK, FAILURE, and CLOSE are permitted. A newly encrypted HELLO, APPROVE, or REJECT is a protocol error.                                                                                                                                       |
| `suspended`  | Prior handshake/confirmation/connection state survives but a socket/peer is absent. Reattach and replay cached handshake bytes and/or encrypted records to return to that prior state; no newly admitted application requests until connected. The invitation deadline still applies until approval. |
| `closed`     | Terminal. No frame can reopen it; a new invitation is required.                                                                                                                                                                                                                                      |

A socket loss while the user switches between same-phone surfaces MUST NOT by itself discard a retained handshake or confirmation. Before approval, the invitation expiry remains in force; after approval, no permission deadline is added. Duplicate cached handshake messages and duplicate ciphertext are handled as specified above, not as new controls. A terminal state change completes pending application promises according to section 6.

### 10. Delivery, mobile lifecycle, and revocation

Within a live session, each endpoint retains its send/receive cipher states, next sequence, unacknowledged ciphertext records, request dispatch table, and unfinished responses. Keys and role tokens MUST remain in endpoint memory or equivalently protected live-process storage and MUST NOT enter ordinary browser persistent storage. Loss of any state required to avoid nonce reuse or duplicate dispatch terminates the session. A new connection requires new pairing material; it MUST NOT replay old requests.

The receiver processes only its expected next sequence. Lower sequences are duplicates and MUST be discarded without decryption, nonce advancement, or wallet dispatch. A higher sequence is a gap: the receiver suspends application delivery, closes that socket, and reconnects to request peer retransmission through the attachment/presence transition. It MUST NOT decrypt out of order or invent missing results. Authentication failure on the expected sequence is fatal. Relays can disrupt availability; endpoints MUST never trade authenticity for reconnection.

After every successfully received non-ACK record, the receiver MUST enqueue an encrypted ACK for the highest contiguous received sequence, coalescing adjacent acknowledgements when possible. ACK records do not trigger ACKs by themselves. An ACK can acknowledge earlier ACK records. Only an authenticated peer ACK can release stored outbound ciphertext; relay presence, write completion, and attach responses cannot. ACK values beyond the largest sequence sent are protocol errors; older ACK values never move the acknowledged position backward. An implementation MUST retain at least 8 MiB of unacknowledged ciphertext; at its configured bound it applies backpressure to new data and continues processing incoming records/ACKs. It MUST reserve an additional 64 KiB for ACK/control output so two full send buffers cannot deadlock acknowledgements. It MUST NOT drop unacknowledged records to make space. Exhausted control capacity is an explicit resource failure, not permission to reuse a nonce.

On either role reconnecting, both endpoints retransmit their retained, unacknowledged records in original sequence order before sending new records. These MUST be the exact original bytes: do not re-encrypt, reset counters, rebuild requests, or dispatch an ID twice. After that replay batch, each endpoint that has received at least one peer record MUST send a fresh ACK for its current contiguous receive position, even if the batch contained only duplicates. This recovers a lost ACK without an ACK-to-ACK response loop. Reconnect replay happens only after attachment/peer-presence transitions, not on every ordinary ACK.

A terminal RESPONSE/FAILURE ID cannot be reused; the wallet retains dispatched IDs for the session even after acknowledging the response. Once a REQUEST has completed reassembly, that ID in a new REQUEST is `PROTOCOL_ERROR`, not a retry; correctly ordered continuation fragments of its original reassembly are not new requests. A second terminal response for one ID is also a protocol error. Response ciphertext retransmission is handled by sequence deduplication. These rules provide at-most-once dispatch within the surviving session, not durable exactly-once transactions after process loss. Implementations MAY bound session dispatch history, but MUST stop admitting new requests and drain/close before discarding IDs; they cannot silently forget them while keeping the session usable.

A request-specific FAILURE ends only its matching request. If failure occurs during reassembly, the receiver MUST discard subsequent payload fragments for that ID while continuing authenticated sequence processing through the declared total; it MUST never dispatch the partial request. A sender receiving that failure MUST still send any already encrypted records in sequence and MAY omit only fragments not yet encrypted. The receiver marks such an abandoned reassembly terminal on receiving the failure's authenticated ACK from the sender or on session closure. A zero-ID FAILURE is fatal to the session. Any failure that prevents reliable sequence/state retention MUST terminate the whole session and report possibly dispatched calls as `unknown`.

An account change sends a zero-ID `PROVIDER_CHANGED` FAILURE with `cause: 'account-changed'` and permanently closes that session. Section 6 permits selecting the same freshly proven provider for a new epoch without a chooser; it does not permit socket reattachment, nonce/state reuse, or old-request replay from the closed session. Fresh pairing and both endpoint approvals remain required.

Ordinary backgrounding, screen lock, suspension, and foreground return MUST preserve the session and pending prompt while the process/state remain alive. A reconnecting UI shows `suspended`, retains the original request, and offers cancel/disconnect. It MUST NOT reissue the method or start a second permission prompt merely because the user returned through a link. There is no elapsed-time limit on a live permission decision. Implementations MUST honor BRC-219 prompt visibility and restoration. Actual process/state loss, unrecoverable relay failure, explicit disconnect, and service closure end the connection with `unknown` delivery for possibly dispatched calls. They are not permission timeouts.

Users MUST be able to see and revoke active mobile connections in the wallet and disconnect in the application. Revocation sends encrypted CLOSE when possible, closes relay attachment, erases keys/tokens and unresolved invitation material, and blocks future requests under that session. Receiving CLOSE has the same effect. Revoking transport access does not delete wallet data or retroactively revoke completed operations; wallet permission persistence is separately controlled by BRC-116. A future mobile connection requires a new invitation and explicit approval, even when a wallet preference is remembered.

### 11. Complete interface and conformance evidence

The method set is fixed by BRC-100. The following table is a coverage checklist, not a negotiable manifest:

| Codes | Required methods                                                                                                                       |
| ----- | -------------------------------------------------------------------------------------------------------------------------------------- |
| 1–7   | `createAction`, `signAction`, `abortAction`, `listActions`, `internalizeAction`, `listOutputs`, `relinquishOutput`                     |
| 8–10  | `getPublicKey`, `revealCounterpartyKeyLinkage`, `revealSpecificKeyLinkage`                                                             |
| 11–16 | `encrypt`, `decrypt`, `createHmac`, `verifyHmac`, `createSignature`, `verifySignature`                                                 |
| 17–22 | `acquireCertificate`, `listCertificates`, `proveCertificate`, `relinquishCertificate`, `discoverByIdentityKey`, `discoverByAttributes` |
| 23–28 | `isAuthenticated`, `waitForAuthentication`, `getHeight`, `getHeaderForHeight`, `getNetwork`, `getVersion`                              |

A conforming implementation MUST publish a versioned test report identifying wallet, controller, adapter, runtime, and test fixture versions. A declaration alone does not establish conformance. The suite MUST include:

| Area                        | Required observations                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Full interface              | All 28 methods over each advertised substrate; valid results and wallet errors; preservation of optional fields, byte arrays, originator, and large/fragmented payloads. Method stubs fail.                                                                                                                                                                                                                                                                                                         |
| Additive metadata           | BRC-205 consumers accept base-only and extended strings within its bounds without opt-in or support discovery; unknown attributes ignored and malformed attributes handled under BRC-205; scheme advertisements never reduce the core method set or grant permission; unsupported reserved schemes remain rejected.                                                                                                                                                                                 |
| Icon metadata               | Optional HTTPS/UHRP/CHIRP references in `brc185-icon` and signed descriptors; encoding and identifier validation; bounds/duplicate keys; safe image display; no fetch on announcement alone; generic fallback for absent/invalid/unavailable artwork; no effect on wallet selection, identity or epochs.                                                                                                                                                                                            |
| Independent implementations | Two independently implemented endpoints interoperate; same-library loopback alone is insufficient.                                                                                                                                                                                                                                                                                                                                                                                                  |
| Ports                       | Two wallets concurrently; occupied unrelated ports; gaps; bind races; HTTP-range overlap at 3321; IPv4/IPv6 consistency; ceiling exhaustion; default and extended discovery coverage.                                                                                                                                                                                                                                                                                                               |
| Every adapter family        | Extension announcements, legacy window\.CWI, embedded object, XDM, React Native, localhost, mobile, and an independently registered custom adapter can enter the chooser without vendor logic.                                                                                                                                                                                                                                                                                                      |
| Choice                      | No popup for single desktop or configured embedded default; sole automatically selected nondefault provider is immediately identified with Change wallet; explicit ask still works; multiple wallets require choice; remembered provider returns; missing/rotated/legacy provider asks again; no permission/funding-based substitution.                                                                                                                                                             |
| UX states and labels        | Pending empty discovery versus completed empty/blocked/partial states; reachable recovery/cancel controls; differently styled applications preserve neutral rows; labels queried only in the selected context; generic fallback and retired-response rejection; display rename does not replace a binding; bundled default does not become another application's default.                                                                                                                           |
| Neutrality                  | Swapping vendor names, arrival order, port order, and sponsorship changes no prominence except the documented neutral sort; all eligible candidates remain accessible.                                                                                                                                                                                                                                                                                                                              |
| Switching                   | Multiple explicit connections; pending prompt; action reference isolation; wrapper/funding state; abandon with uncertain delivery; no replay onto replacement wallet.                                                                                                                                                                                                                                                                                                                               |
| Account-change rebinding    | Same present provider freshly proves the exact tuple/context: optional chooser-free new epoch and application notification; old epoch/connection stays closed; queued and in-flight calls, including `not-sent` calls, never cross or replay; action references and wrapper state stay isolated. Missing/changed/conflicting/unverified provider, wrong context, stale proof, generic failure, or `ask` policy requires explicit selection; mobile still requires fresh pairing and both approvals. |
| Mobile pairing              | Desktop QR to Android and iOS; same-phone verified link and manual return; paste intake; expired/altered offer; rejected comparison; rejected backend disclosure; cancellation before dispatch.                                                                                                                                                                                                                                                                                                     |
| Pairing presentation        | Intake/handshake/confirmation/connected states; peer presence and one-sided approval never imply completion; expiry before both approvals; explicit refresh invalidates old invitation/session/code/approvals; non-QR route; comparison across same-phone surfaces; rejection/cancel/state-loss recovery sends no queued methods.                                                                                                                                                                   |
| Cryptography                | Official primitive/Noise known-answer vectors; independent interoperability; altered prologue/mode/origin; wrong PSK; all-zero DH; corrupted record; replay; gaps; duplicate ID; sequence limits; no plaintext in relay capture.                                                                                                                                                                                                                                                                    |
| Mobile continuity           | Background/foreground, screen lock, socket replacement, ciphertext retransmission, ACK loss, reassembly backpressure, request approval after a long wait, process loss, relay outage, and revocation.                                                                                                                                                                                                                                                                                               |
| Compatibility/DX            | Core SDK import in browser/SSR/worker/Node without DOM access; no registration retains old behavior; explicit objects remain untouched; graphical/terminal/headless adapters; Babbage Go-style wrapper retains configured funding and fees without duplicate onboarding UI.                                                                                                                                                                                                                         |
| Privacy/origin              | No account probes during discovery; separate full-origin memories and continuity keys; wrong source/frame/origin rejected; metadata inert; no secrets in logs/return links; blocked local access accurately reported.                                                                                                                                                                                                                                                                               |

Wallet operations in conformance tests MUST use controlled fixtures/test environments, not involuntary user payments. Method conformance requires implemented behavior, not forcing a user to grant every permission. The initial release of the encrypted profile MUST undergo an independent protocol/implementation security review covering the complete pairing, identity, nonce, replay, origin, and lifecycle design. Passing primitive tests alone is insufficient to claim the entire connection system has been audited.

### 12. Compatibility and rollout requirements

This proposal adds transport/control protocols alongside existing ones. It MUST preserve BRC-100's mandatory core methods, method wire codes, argument/result shapes, ordinary operation semantics and security/authorization guarantees, as well as legacy BRC-5 routes and BRC-6/BRC-7 message meanings. Section 1 permits BRC-205's additive `getVersion` string-content format and future advertisements of explicitly selected reserved schemes; it does not authorize a reduced core interface or make metadata a permission grant. Legacy wallets remain usable through compatibility adapters, with limitations honestly identified. They MUST NOT be labeled BRC-185 conforming merely because one readiness call succeeds. Older strict 30-byte or base-only validators may reject extended strings and need updates; this compatibility caveat does not require coordinated adoption or a support-discovery exchange.

New desktop listeners SHOULD retain the default port when it is available, making a lone new wallet reachable by legacy clients. When it is occupied, ascending allocation makes the additional wallet available to new discovery clients. A legacy client that probes only the default port cannot see every new listener; documentation MUST say so. A new client MUST NOT send a legacy JSON request to a binary listener based only on its port number.

Existing mobile-relay protocols and branded URI schemes MAY coexist through explicit legacy adapters. They MUST NOT be silently accepted as the encrypted version 1 profile, and version 1 MUST NOT downgrade to them after an authentication failure. A relay method subset cannot be made conforming by advertising optional capabilities. Migration requires implementing complete carriage, preserving request/error semantics, and the new pairing security contract.

Implementation work MUST preserve the layering in section 2. Public packages SHOULD release independent adapters/controller/UI components and document compatible version combinations. Reference UI/framework packages MUST NOT become transitive dependencies of the core SDK. Wallet-specific integration can supply an adapter; applications and wrappers MUST NOT need vendor-specific branches to call BRC-100.

## Design rationale and implementation guidance (non-normative)

### Illustrative user journeys

These journeys explain the normative requirements above; screen names and layouts are illustrative. Existing implementation screens do not define this contract.

| Stage   | Desktop browser and phone wallet                                                                                                                 | Browser and wallet on the same phone                                                                                                                  |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Start   | User chooses Use phone from the connection surface.                                                                                              | User chooses Use phone and a supported wallet handoff/intake route.                                                                                   |
| Intake  | App offers a temporary QR and equivalent non-QR route, with expiry and cancel. Wallet scans or accepts the offer.                                | App opens the explicitly selected wallet through a supported link, or the user uses manual intake. Handoff is not connection approval.                |
| Compare | After the handshake, the app identifies the wallet; both surfaces show the application origin, matching code, and any backend-access disclosure. | User switches between the actual app and wallet surfaces to compare the same information. Retained session state survives ordinary surface switching. |
| Approve | User explicitly approves on both surfaces before wallet requests can pass.                                                                       | Same approvals, then user-initiated return link or manual Return to app. A callback alone cannot establish connection or replay a request.            |
| Use     | App stays on the desktop; phone handles wallet authentication and operation permissions as needed, separately from pairing.                      | App continues in the browser; wallet handles the same separate authentication/permission steps.                                                       |
| Recover | Expiry offers fresh pairing; rejection/cancel ends the attempt; suspension preserves retained state; state loss requires fresh pairing.          | Same rules, including visible manual return when automatic handoff back fails.                                                                        |

For a bundled-wallet example, Pocket Photos can onboard a user through its explicitly configured embedded default without opening a provider chooser. If the bundled wallet also exposes a supported external substrate, another browser application can discover its declared product name, for example "Pocket Photos Wallet". That application applies its own selection policy: complete discovery of that sole provider may connect automatically with visible provider identification and Change wallet, while multiple candidates require the neutral chooser unless another section 5 rule applies. After selection, a valid BRC-205 label can produce "Continue with your Pocket Photos Account"; absent metadata produces generic account copy. This does not authenticate the person or carry Pocket Photos' permissions into the new application. A later label of "Metanet Account" changes presentation, not provider identity or the bound account by itself. Phone-hosted wallets still require explicit pairing; an installed phone application is not automatically a discovered desktop provider.

### Source findings and proposed changes

At the September 25, 2026 capture, the TS stack's `WalletClient` used fixed default endpoints and priority among successful readiness probes. It could accept a supplied `WalletInterface`, but it did not enumerate ascending ports or choose among all wallets. BSV Desktop listeners used fixed ports. The proposed controller and descriptor are new; they are not descriptions of shipped discovery behavior.

The existing `ts-wallet-relay` provides useful pairing, mobile prompt, and modal experience. Its application backend is an encryption endpoint, whereas its WebSocket relay routes encrypted traffic. Its typed proxied-method list was a subset of BRC-100 at the captured revision. The new client endpoint mode, full method carriage, URI, wire protocol, and security requirements therefore require implementation work; compatibility must be explicit. The BSV Browser pairing route delegates to the shared mobile wallet-toolbox pairing UI, providing another concrete integration point.

`@babbage/go` version 0.1.20, commit `448505904367b8990e5493f68c419bebf06b88c7`, accepts `WalletInterface` in its constructor, defaults to `WalletClient` when none is supplied, handles wallet-unavailable UI, and wraps `createAction` with funding and monetization behavior. Its platform contribution and developer fee are distinct settings. Its existing modal/error handling can deliberately leave a promise pending; integration with typed selection cancellation needs specific attention. Its existing `base` and pending payment state make one wrapper per immutable connection the appropriate composition model. These are source observations, not execution tests or recommendations to change its commercial terms.

EIP-6963 supplies useful precedent for independent browser provider announcements without redefining the wallet API. WalletConnect's pairing specifications supply precedent for explicit pairing versions and expiry. This BRC does not adopt Ethereum methods, a WalletConnect network dependency, or a central wallet directory. Chrome's local-network access controls and Android/iOS app-link behavior are platform constraints that adapters must respect; a BRC cannot grant a browser permission or native-app association.

The bounded discovery window makes routine connection work finite. Ascending allocation remains valid above that window; the coverage report and explicit extension avoid pretending that a bounded scan is exhaustive. Signed, per-origin descriptors provide continuity and alias merging without asking every discovered wallet to reveal a user identity. They do not certify vendors.

Noise is selected as a documented protocol construction, rather than composing ad hoc encryption primitives. Fresh ephemeral keys and a random invitation PSK establish encrypted sessions without using wallet spending keys as transport identities. The extra confirmation and honest origin labeling address the fact that a QR payload alone does not attest a browser origin. The mandatory security review remains a release gate, not evidence that this newly composed profile is already secure in deployed implementations.

### Initial implementation and acceptance plan

The specification precedes implementation. Historical source findings and referenced screens are integration inputs, not alternative definitions of conformance. Earlier method subsets, timeouts, backend assumptions, or branded flows do not create exceptions to the normative requirements above. Suggested work packages, in dependency order, are:

1. Build independent fixtures for descriptor signatures, origin normalization, allocation/discovery overlap, binary carriage, state transitions, and encrypted records. Produce deterministic cross-language test vectors for this BRC's envelopes in addition to upstream cryptographic vectors. Complete the protocol review before presenting the profile as production-ready.
2. Implement the external controller interfaces, storage, runtime checks, and neutral selection policy. Add the narrow optional WalletClient delegation hook with compatibility tests. Publish plain HTML, React, terminal, headless, and wrapper composition examples separately.
3. Add signed discovery and next-free-port allocation to desktop wallets; implement extension, window\.CWI, XDM, and React Native adapters and their provider events. Test simultaneous independent wallets and alias deduplication before mobile integration.
4. Implement the complete encrypted relay/phone profile and its lifecycle/replay tests, then QR, same-phone links, return, revocation, and backend disclosure UIs. Retain legacy mobile adapters only as explicitly identified paths.
5. Exercise Babbage Go around the bound interface, including missing-wallet UI ownership, insufficient-funds retry, both fee settings, action signing and pending state, cancellation, and switching. Verify behavior from source and execution rather than assuming constructor compatibility proves every flow.
6. Run the published conformance matrix with BSVA reference wallets first, then independent vendor wallets. Candidate follow-on integrations include Metanet Desktop, Metanet Explorer, Peacock Wallet, PaperTrade, and Metanet Docs. This order is an implementation plan, not chooser ranking, certification, or a claim of commitments by those projects.

No package names, reference applications, or proposed rollout order grant a vendor special treatment in normative discovery or selection. Onboarding opinions such as BRC-110/BRC-137 and the separate sponsored-onboarding proposal are background inputs; their catalogs or business flows are not prerequisites for this connection protocol.

## Implementations

No implementation of this complete proposal is claimed at submission. Existing code listed below is prior art and integration input. This section should gain pinned releases, cross-implementation conformance reports, and security-review links when they exist.

[Deterministic format fixtures](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/0185-vectors.json) accompany this draft for descriptor canonicalization/signing, confirmation-code formatting, BRC-100 `getVersion` request framing, record associated data, and size boundaries. Their intentionally public RFC 8032 test seed is for testing only. They do not include a complete Noise handshake/ciphertext interoperability vector and are not an implementation audit. For the fixture handshake-hash bytes `00` through `1f`, the displayed comparison code is `9420 8747`; this is a format example, not a reusable pairing credential.

## References

### Existing wallet contracts

* [BRC-100: unchanging wallet-to-application interface](/wallet/0100), [BRC-5: HTTP](/wallet/0005), [BRC-6: XDM](/wallet/0006), and [BRC-7: window substrate](/wallet/0007).
* [BRC-116: wallet permissions](/wallet/0116) and [BRC-219: prompt liveness](/wallet/0219).
* [BRC-98: protocol schemes](/wallet/0098), [BRC-99: basket schemes](/wallet/0099), [BRC-111: action-label schemes](/wallet/0111), and [BRC-205: version-string attributes](/wallet/0205).
* [BRC-38: wallet data](/outpoints/0038) and [BRC-39: encrypted wallet data](/outpoints/0039): connection switching is not a wallet-data migration format.
* [BRC-110](/opinions/0110), [BRC-137](/opinions/0137), and [draft sponsored onboarding / wallet choice PR #224](https://github.com/bsv-blockchain/BRCs/pull/224): non-normative onboarding context, not adopted catalog or sponsorship mandates.

### Public discussion, September 23–24, 2026

* [Jorge: simultaneous wallets and port conflict](https://x.com/BitcoinSVCOL/status/2102787768376336697); [per-application and cross-device UX](https://x.com/BitcoinSVCOL/status/2102887910798205434).
* [Deggen: chooser, next-free ports, embedded wallet and mobile QR](https://x.com/deggen/status/2102966055635181768).
* [Ty: substrates, mobile QR and interoperability BRC](https://x.com/ProjectBabbage/status/2102971112112218334); [collaboration follow-up](https://x.com/ProjectBabbage/status/2103119480520077543).
* [Joel: simultaneous-wallet/testing context](https://x.com/JoelDalais/status/2102883259579355333); [wallet picker expectations](https://x.com/Pentium_486/status/2103020048562708799).
* [Permission-module capability discussion](https://x.com/shruggr/status/2103014439184367761): distinct from BRC-100 method discovery, which this proposal excludes.

### Pinned implementation inputs

* [TS WalletClient](https://github.com/bsv-blockchain/ts-stack/blob/fc6ebef7132f77f93995fb7cd2869a5f8e7c509d/packages/sdk/src/wallet/WalletClient.ts), [wallet-relay source](https://github.com/bsv-blockchain/ts-stack/tree/fc6ebef7132f77f93995fb7cd2869a5f8e7c509d/packages/wallet/ts-wallet-relay).
* [BSV Desktop revision](https://github.com/bsv-blockchain/bsv-desktop/tree/bcafc74f918c04575a17d13812876d3fcae5f941), [BSV Browser pairing route](https://github.com/bsv-blockchain/bsv-browser/blob/52ec2a688f54530dacea14a74dd6bf466fe0bf0f/app/pair.tsx), [shared mobile toolbox revision](https://github.com/bsv-blockchain/bsv-wallet/tree/01c4c8b73d376f1ecf8db81d4fa9003c3992aef3/packages/expo-wallet-toolbox).
* [Babbage Go source](https://github.com/p2ppsr/babbage-go/blob/448505904367b8990e5493f68c419bebf06b88c7/src/index.ts), [README](https://github.com/p2ppsr/babbage-go/blob/448505904367b8990e5493f68c419bebf06b88c7/README.md), and [fee composition tests](https://github.com/p2ppsr/babbage-go/blob/448505904367b8990e5493f68c419bebf06b88c7/test/platform-fee.test.mjs).

### Protocol and platform references

* [Noise Protocol Framework, revision 34](https://noiseprotocol.org/noise.html), [RFC 7748 / X25519](https://www.rfc-editor.org/rfc/rfc7748), [RFC 8439 / ChaCha20-Poly1305](https://www.rfc-editor.org/rfc/rfc8439), and [RFC 8032 / Ed25519](https://www.rfc-editor.org/rfc/rfc8032).
* [RFC 8785 / JCS](https://www.rfc-editor.org/rfc/rfc8785), [RFC 4648 / base encodings](https://www.rfc-editor.org/rfc/rfc4648), [RFC 6455 / WebSocket](https://www.rfc-editor.org/rfc/rfc6455), and [BCP 14 requirement terms](https://www.rfc-editor.org/rfc/rfc8174).
* [EIP-6963 provider discovery](https://eips.ethereum.org/EIPS/eip-6963), [WalletConnect pairing URI](https://specs.walletconnect.com/2.0/specs/clients/core/pairing/pairing-uri).
* [Chrome 142 release notes: local-network access](https://developer.chrome.com/release-notes/142), [Android app links](https://developer.android.com/training/app-links), [Apple universal links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app).


# User Management Protocol (UMP)

Ty Everett (<ty@projectbabbage.com>)

## Abstract

The User Management Protocol (UMP) represents a recoverable wallet account with an on-chain, spendable account descriptor. The descriptor contains encrypted copies of the account's root keys, hashes used for discovery, encrypted recovery factors, and, optionally, encrypted profiles and password derivation metadata. Any two of a presentation key, a password, and a recovery key can recover the account. Changing those factors normally spends the current descriptor and creates its successor while preserving the account's underlying keys.

This document specifies the deployed CWI-style UMP formats, including mandatory compatibility with legacy PBKDF2 accounts, the version 3 KDF extension, account and profile operations, and the `tm_users` topic and `ls_users` lookup service. It explains how a Wallet Authentication Backend (WAB) can supply one factor without becoming an indispensable custodian, and how a user can recover without that backend. It also specifies first-accepted hash ownership and the spend continuity required to replace an account descriptor.

Section 3.4 supplies the BRC-184 ProtoMap registration request for UMP's two wallet-administrative derivation protocols, including their exact identifiers, requested metadata, permission boundaries, and maintenance arrangements.

## 1. Status, scope, and terminology

The uppercase terms MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY have the meanings in RFC 2119 and RFC 8174. They describe conformance to this specification, not a claim that every historical implementation already enforces every validation requirement here.

UMP is an optional account-management system beneath the [BRC-100](/wallet/0100) wallet interface. A BRC-100 wallet need not use UMP. UMP does not replace that interface, specify application permissions, standardize every WAB authentication method, or provide a complete wallet transaction-storage backup. The account descriptor is called a **token** because it is a tracked UTXO, not because it represents money or a transferable username.

This document distinguishes three things:

* **Account format and operations:** the interoperable key scheme and on-chain descriptor, including historical formats that readers must understand.
* **Overlay admission and discovery:** the stateful `tm_users` and `ls_users` behavior, including its ordering and replication limits.
* **Implementation compatibility:** the local token/snapshot encodings and particular deployment differences documented in Sections 12 and 14. These local encodings are not an alternative on-chain envelope.

An **outpoint** is a transaction ID and a zero-based output index. In UMP JSON and local token records its canonical text is `txid.index`: 64 lowercase hexadecimal characters, a literal full stop, and a decimal integer from 0 through 4294967295 with no redundant leading zero. The transaction ID uses conventional display byte order. An outpoint is not a version number.

A **rendition** is one descriptor output in an account's history. A **successor** is created by a transaction spending its predecessor. A **lineage** is the resulting spend-connected history. Two outputs with an equal discovery hash are not, by that equality alone, in the same lineage.

## 2. Background and motivation

Ty Everett's *Computing with Integrity* (2020), especially its key definitions and initial bootstrap process, described a password, presentation key, backup key, primary key, and privileged access key, with pairwise XOR and authenticated encryption supporting recovery. The paper's “backup key” corresponds to the recovery key here. It is a conceptual source, not the wire specification for today's UMP: its publication envelope and some key arrangements predate the spendable descriptors, derivation protocols, and KDF parameters specified below. [Original paper](https://github.com/open-cash-standards/papers/blob/master/computing-with-integrity/paper.pdf).

A person ordinarily wants to open the same account on another device, change a password without changing their identity, and recover when a device, password, or service becomes unavailable. Keeping only a password with a vendor makes the vendor indispensable; keeping only one irreplaceable secret makes its loss catastrophic. UMP distributes recovery across three factors while keeping the account's cryptographic roots stable.

A WAB typically associates an externally verified authentication method, such as a phone number and a one-time code, with a randomly generated presentation key. After verification it returns that key to the wallet over a protected connection. The wallet hashes it to discover the UMP token, obtains the password locally, and recovers the account. The telephone number, code, WAB database ID, and WAB URL do not appear in the UMP descriptor and are not inputs to its password KDF.

The recovery key is independently retained by the user. If the password is forgotten, the presentation key and recovery key suffice. If the WAB disappears, refuses service, changes ownership, or otherwise becomes unsuitable, the password and recovery key suffice. This second path requires neither the old WAB's permission nor knowledge of its current authentication policies. A replacement client can implement it directly from this specification.

“Two of three” describes access to encrypted root-key material. UMP is neither a Bitcoin `OP_CHECKMULTISIG` construction nor Shamir secret sharing. Its on-chain token has a single derived signing key. The distinction matters: password prompts and privileged operations are wallet policy, whereas spending the token is enforced by its locking script.

## 3. Keys and cryptographic primitives

### 3.1 Values and notation

All keys and salts in the canonical account format are 32-byte byte strings. Preserve leading zero bytes. `||` means byte concatenation; `X XOR Y` means position-by-position XOR of equal-length byte strings. No hashing, string encoding, or integer serialization is implicit in XOR.

| Symbol | Meaning                     | Source and retention                                                                 |
| ------ | --------------------------- | ------------------------------------------------------------------------------------ |
| `S`    | Public password salt        | Fresh cryptographically random 32 bytes at creation and each password change         |
| `W`    | Password key                | Derived from the exact password bytes and `S` using Section 4                        |
| `P`    | Presentation key            | Random factor supplied or recovered through a WAB or another user-selected mechanism |
| `R`    | Recovery key                | Random factor generated by the client and saved independently by the user            |
| `A`    | Root primary key            | Random root used to build the ordinary/default wallet                                |
| `V`    | Root privileged key         | Independent random root used for privileged operations and factor wrapping           |
| `H_P`  | Presentation discovery hash | `SHA256(P)`                                                                          |
| `H_R`  | Recovery discovery hash     | `SHA256(R)`                                                                          |

`P` and `R` are secret bytes, not their public keys. Hash the raw 32 bytes once with SHA-256; do not hash their hexadecimal or Base64 representations, a phone number, or a secp256k1 public key. The salt and both hashes are public.

Interpret `A` and `V` as unsigned, big-endian secp256k1 private scalars when performing wallet operations. New roots and derived profile roots MUST be nonzero and less than the curve order `n`. Generate new roots or pads if necessary. Never remove leading zero bytes before XOR or serialize a canonical root in fewer than 32 bytes. A presentation key used as a private key by an ancillary funding protocol must also satisfy that protocol's scalar requirements.

### 3.2 Authenticated encryption envelope

Define `E(K, M)` as AES-256-GCM encryption with key `K`, no additional authenticated data, a fresh random **32-byte IV**, and a 16-byte authentication tag. Its serialized result is:

```
IV[32] || ciphertext[len(M)] || authenticationTag[16]
```

`D(K, C)` parses that exact layout, authenticates, and returns the plaintext, or fails. This is the existing SDK symmetric encryption layout; replacing the IV with the common 12-byte GCM IV changes the format. Do not add a salt header, padding, version prefix, or a separate tag-length byte. GCM supports the specified IV length directly; do not truncate or hash it before passing it to GCM.

An encrypted canonical 32-byte key occupies 80 bytes. Every encryption operation MUST use a fresh IV, including operations encrypting different fields with the same key. Authentication failure MUST NOT return partial plaintext or trigger creation of a replacement account. Fixed IVs in Section 13 are test fixtures only.

### 3.3 Root-derived keys

UMP uses the [BRC-42](/key-derivation/0042) and [BRC-43](/key-derivation/0043) derivation scheme, with the following exact, lowercase protocol names and string key IDs:

| Purpose                                 | Root | Protocol ID                          | Key ID | Counterparty |
| --------------------------------------- | ---- | ------------------------------------ | ------ | ------------ |
| Token locking key and field signature   | `A`  | `[2, "admin user management token"]` | `"1"`  | `"self"`     |
| Symmetric wrapping of `P`, `W`, and `R` | `V`  | `[2, "admin key wrapping"]`          | `"1"`  | `"self"`     |

These are wallet administrative protocols; applications do not gain access merely by requesting the names. The root/default profile, not the currently selected non-default profile, is used for token operations.

For completeness, the self-derivation needed here can be implemented as follows. Let `G` be the secp256k1 generator, `r` a root scalar, `SEC(Q)` the 33-byte compressed encoding of a point, and `BE32(x)` its unsigned 32-byte big-endian encoding. For an invoice string `I`, compute:

```
shared = SEC((r * r mod n) * G)
tweak  = integer_big_endian(HMAC-SHA256(key=shared, message=UTF8(I)))
d      = (r + tweak) mod n
```

The UMP token private scalar is `d` with root `A` and invoice `2-admin user management token-1`; its locking public key is `SEC(d * G)`. Reject an invalid derived scalar rather than using an invalid point.

For factor wrapping, use root `V` and invoice `2-admin key wrapping-1` to derive `d_V`. The wrapping key is:

```
K_wrap = BE32(x-coordinate((d_V * d_V mod n) * G))
```

This is self ECDH between the BRC-42 child keys. It is **not** `V`, `SHA256(V)`, the compressed shared point, or SHA-256 of its x-coordinate. `E(K_wrap, factor)` is the result of a BRC-100 `encrypt` operation under the privileged root with the wrapping parameters above.

### 3.4 BRC-184 protocol registration

This specification also serves as a registration request under [BRC-184](/wallet/0184) and the [registry submission guide](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md). It requests **two ProtoMap descriptions for existing protocols**, with the following exact identities and requested display metadata:

| Protocol ID                          | Requested name               | Requested description                                                                                                                                                                                          |
| ------------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[2, "admin user management token"]` | UMP Account Token            | Derives the wallet-administrative key that signs UMP account descriptors and authorizes spending their outputs. It controls account updates, not general application spending or proof of a person's identity. |
| `[2, "admin key wrapping"]`          | UMP Recovery Factor Wrapping | Encrypts and decrypts UMP presentation, password-derived, and recovery keys under the root privileged key. Decryption exposes recovery factors; this protocol is reserved for wallet administration.           |

Security level `2` is part of each registry identity. The protocol strings are the exact lowercase strings in Section 3.3, including spaces; neither `UMP` nor `188` is a substitute identifier. Both protocols use key ID `"1"` and counterparty `"self"` for the operations specified here. These derivation parameters describe UMP use; they are not extra ProtoMap identity fields, and `"self"` is not an account-specific public key to publish in a registry.

**Capabilities and access boundaries.** The account-token protocol uses root `A` for public-key derivation, descriptor field signing and verification, and the input signature that spends the previous token. The wrapping protocol uses root `V` for authenticated encryption and decryption of `P`, `W`, and `R`; it wraps the derived password key, not the plaintext password. The exact cryptography and root/default-profile requirements remain those of Sections 3.2 and 3.3. Both identifiers are administrative under [BRC-44](/key-derivation/0044). A wallet MUST preserve that administrative restriction independently of registry metadata: a listing does not grant an external application access, and a friendly description is not permission to export roots, decrypt recovery factors, or sign account updates. These are descriptions of existing internal capabilities, not application permission schemes or installable permission modules.

The common submission information for both entries is:

| Item                             | Registration information                                                                                                                                                                                                                                                                                                                                                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Registry and requested publisher | ProtoMap; Metanet Trust Services. Other publishers may independently describe the same identifiers.                                                                                                                                                                                                                                                                                                                            |
| Requested networks               | Mainnet and testnet, as separate network-scoped publications under the publisher's identified public key for each network.                                                                                                                                                                                                                                                                                                     |
| Requested action                 | Create descriptions for the two exact tuples. If that publisher already has a current record for either tuple on a requested network, identify its public key, outpoint, and current content in the review thread and handle the corresponding change as an update or correction.                                                                                                                                              |
| Proposal and review record       | [BRCs PR #278](https://github.com/bsv-blockchain/BRCs/pull/278). This is a specification of implemented protocols; the request does not assert that a registry record has been published.                                                                                                                                                                                                                                      |
| Documentation                    | This BRC, especially Sections 3–7, 14, and 15. The repository location is `wallet/0188.md`; a publication should use an absolute public URL to the reviewed revision, retaining the specification commit in the review record.                                                                                                                                                                                                 |
| Icon                             | [UMP account descriptor icon](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/media/0188-ump.svg), shared by both entries. This newly contributed, static SVG is dedicated to the public domain under [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/). It carries no vendor endorsement and may be copied or mirrored. Use an absolute retrievable URL to the reviewed asset revision when publishing. |
| Implementation evidence          | The pinned Wallet Toolbox `CWIStyleWalletManager` in Section 14 uses the token tuple in `buildAndSend` and token validation, and the wrapping tuple in factor encryption and `getFactor`. The pinned Metanet Client Desktop and Metanet Explorer integrations provide the application context. This is evidence of implemented use, not a claim that every deployed version conforms to this specification.                    |
| Maintenance contact              | Ty Everett, [@ty-everett](https://github.com/ty-everett), through the linked BRC PR or a linked registry correction/dispute request. Preserve the public review history and source revisions.                                                                                                                                                                                                                                  |

**Compatibility and maintenance.** The eleven-field legacy layout, the legacy layout with profiles, and the v3 layouts all retain these two exact tuples. PBKDF2 and Argon2id select password derivation within UMP; they do not create new ProtoMap identities. A metadata correction MUST NOT silently repurpose either established tuple for incompatible semantics. Maintainers should notify publishers of changed meaning, documentation, icons, contacts, deprecation, or migration requirements, preserving the legacy account compatibility described in Sections 4 and 14. Neither tuple is deprecated by this specification.

`tm_users` and `ls_users` are the overlay topic and lookup-service names specified in Sections 9 and 10, not BRC-43 protocol tuples. This request creates no ProtoMap entry for those names, no wildcard entry for profiles or user-specific identifiers, and no BasketMap or CertMap registration. The KDF algorithm names, discovery hashes, factor values, roots, passwords, WAB authentication data, and user-specific counterparties are not registration payloads.

**Editorial handling.** The existing BRC PR is the initial inclusion and maintenance thread; request Metanet Trust Services review there by tagging @ty-everett rather than creating a duplicate request. Ty Everett is both this specification's author and the named registry contact, so the request is not independent review. Seek an uninvolved reviewer where practicable and record any limitation. The publisher should record its decision, reviewed specification and icon revisions, network, publication public key, and resulting record outpoints in that thread. Do not mark the request published merely because the BRC is merged; confirm the resulting records through lookup after publication. No publisher key or existing record outpoint is asserted by this request.

Under BRC-184, these records are optional, publisher-attributed descriptions. Their presence, absence, withdrawal, or availability MUST NOT determine whether an otherwise supported UMP account can be discovered, recovered, or updated. Registry publication confers neither identifier exclusivity nor authority over `tm_users` admission, `ls_users` results, WAB selection, account ownership, or the user's wallet permissions. Consumers retain network and publisher attribution and the exact tuples when interpreting metadata.

## 4. Password derivation and legacy compatibility

### 4.1 Password bytes

Encode the entered Unicode password as UTF-8. Do not trim, case-fold, normalize Unicode, prepend an identity, or append a terminator. Canonically equivalent Unicode strings can produce different keys. UIs SHOULD preserve the user's input exactly and avoid silently rewriting it. The public salt is used as raw bytes, not as the text of its encoding.

### 4.2 Legacy accounts: mandatory PBKDF2 support

An account with no on-chain KDF metadata uses:

```
W = PBKDF2-HMAC-SHA512(UTF8(password), S, iterations=7777, dkLen=32)
```

Every UMP recovery implementation MUST support this derivation. It is not PBKDF2-HMAC-SHA256, 7,777 bytes of output, or a truncation of a differently parameterized PBKDF2 call. The rule applies both to the eleven-field legacy format and to legacy tokens with encrypted profiles.

The absence of KDF metadata is a positive selection of this historical derivation. Readers MUST NOT apply the current new-account default to those tokens. A failed PBKDF2 decryption is not permission to silently try a new KDF and rewrite the account.

### 4.3 Version 3 metadata

Version 3 adds three fields after the core fields and any profile field:

| Field          | Byte representation                               |
| -------------- | ------------------------------------------------- |
| `umpVersion`   | One byte `03`                                     |
| `kdfAlgorithm` | UTF-8 `argon2id` or `pbkdf2-sha512`, exactly      |
| `kdfParams`    | UTF-8 JSON object containing the parameters below |

For `argon2id`, all four members are required. The deployed new-account default is:

```json
{"iterations":7,"memoryKiB":131072,"parallelism":1,"hashLength":32}
```

Use Argon2id version 1.3 (`0x13`), `t=iterations`, `m=memoryKiB` in **KiB**, `p=parallelism`, and the given output length. There is no Argon2 secret input or associated data. `131072` denotes 128 MiB. UMP's interoperable factor length is 32 bytes, so producers MUST set `hashLength` to 32. Readers MUST NOT truncate, extend, or otherwise reinterpret an Argon2id result of a different length to make it fit the XOR operations.

For `pbkdf2-sha512`, `iterations` is required and sets the PBKDF2 iteration count. The derived key is always **32 bytes**. Existing writers can also include `memoryKiB`, `parallelism`, and `hashLength`, because they serialize a common KDF configuration object. `memoryKiB` and `parallelism` do not affect PBKDF2. For compatibility, the PBKDF2 implementation's fixed 32-byte result takes precedence over any `hashLength` member; new producers SHOULD use 32 or omit that member. For example:

```json
{"iterations":7777}
```

JSON member order and insignificant whitespace do not change KDF meaning, but they do change the signed field bytes and therefore the script and transaction ID. Writers SHOULD emit the compact order `iterations`, `memoryKiB`, `parallelism`, `hashLength`, omitting unused members. Readers MUST authenticate the original bytes and MUST NOT reserialize JSON before checking the field signature. Recognized numeric parameters must be JSON numbers representing positive integers, not strings or booleans. A parser SHOULD reject duplicate parameter names and MUST NOT interpret unrecognized members as executable instructions or alternate algorithms.

The current wallet resource envelope bounds Argon2id iterations at 20, memory at 262144 KiB, and parallelism at 16; PBKDF2 iterations at 10000000; and a supplied `hashLength` at 64. It limits the KDF JSON field to 1024 bytes. Argon2 also requires its own valid parameter relationships, including sufficient memory for the selected parallelism. These bounds do not make every accepted configuration a usable UMP factor: Argon2id still needs a 32-byte result. Readers MUST validate and bound work before running a KDF. A device unable to process a supported account MUST report that limitation and allow recovery on a suitable device, not substitute weaker parameters or classify the account as absent.

Unknown algorithms, unsupported version markers, and malformed metadata MUST produce an unsupported/malformed-account result. They MUST NOT fall back to legacy derivation. Implementations claiming complete current account compatibility MUST support both algorithms and the default Argon2id parameters.

### 4.4 Changing the derivation

An existing `W` is not enough to change algorithms. Migration requires the plaintext password or a newly chosen password, a fresh salt, and a newly derived `W`. The new encrypted fields and the new metadata MUST be published together in one successor token.

A factor or profile update that does not change the password MUST preserve `S`, `W`, and the existing KDF interpretation. In particular, it MUST NOT label a legacy PBKDF2-derived `W` as Argon2id. A password change may either retain legacy PBKDF2 encoding or publish a correctly marked v3 successor. Deriving with Argon2id while omitting the metadata is not a valid migration.

## 5. Account descriptor fields and locking script

### 5.1 Core fields

Field indexes start at zero. Every format starts with these eleven fields in this order:

| Index | Name                             | Exact content   |
| ----- | -------------------------------- | --------------- |
| 0     | `passwordSalt`                   | `S`             |
| 1     | `passwordPresentationPrimary`    | `E(W XOR P, A)` |
| 2     | `passwordRecoveryPrimary`        | `E(W XOR R, A)` |
| 3     | `presentationRecoveryPrimary`    | `E(P XOR R, A)` |
| 4     | `passwordPrimaryPrivileged`      | `E(W XOR A, V)` |
| 5     | `presentationRecoveryPrivileged` | `E(P XOR R, V)` |
| 6     | `presentationHash`               | `SHA256(P)`     |
| 7     | `recoveryHash`                   | `SHA256(R)`     |
| 8     | `presentationKeyEncrypted`       | `E(K_wrap, P)`  |
| 9     | `passwordKeyEncrypted`           | `E(K_wrap, W)`  |
| 10    | `recoveryKeyEncrypted`           | `E(K_wrap, R)`  |

Fields 0, 6, and 7 are 32 bytes. The other core fields are 80 bytes for canonical 32-byte plaintexts. A compatibility reader MAY accept a historically serialized privileged scalar of 1 through 32 bytes in decrypted fields 4 or 5, interpreting it as unsigned big-endian and left-padding it to 32 bytes for subsequent key operations. This accommodates integer serializers that dropped leading zeros; it does not authorize variable-length presentation, recovery, or password factors, and new writers MUST emit fixed-width roots.

There is no `UMP` magic string, plaintext account name, root public key, rendition counter, WAB identifier, or timestamp in the core fields. The locking public key is outside this array. Adding such a field at the beginning would shift the entire format and is not compatible.

### 5.2 Four interoperable layouts

The optional profile field is specified in Section 8. The final field signature is **not** a protocol data field in this table.

| Layout                  | Protocol data fields                                                 | Count before signature | Count including signature |
| ----------------------- | -------------------------------------------------------------------- | ---------------------- | ------------------------- |
| Legacy without profiles | `F0` through `F10`                                                   | 11                     | 12                        |
| Legacy with profiles    | `F0` through `F10`, `profilesEncrypted`                              | 12                     | 13                        |
| v3 without profiles     | `F0` through `F10`, `03`, algorithm, parameters                      | 14                     | 15                        |
| v3 with profiles        | `F0` through `F10`, `profilesEncrypted`, `03`, algorithm, parameters | 15                     | 16                        |

The legacy on-chain formats have no explicit version byte. Calling the profile extension “v2” is useful shorthand and matches a local serialization version; it does not mean a byte `02` exists in its locking script. For v3 the marker is at protocol field 11 without profiles and field 12 with profiles.

Readers MUST recognize the layout by the field count and marker position after validating/removing the field signature. The marker is the binary byte `03`, not UTF-8 `"3"` (`33`) or a four-byte integer. Profiles are either omitted or contain a nonempty authenticated encryption envelope. Producers MUST NOT insert an empty placeholder to move the version marker. Unknown trailing extensions require a separately specified format; readers MUST NOT guess their meanings.

### 5.3 PushDrop envelope and field signature

UMP uses the lock-before-data variant of the [BRC-48](/scripts/0048) PushDrop family. Its complete locking script is:

```
PUSH33(lockingPublicKey) OP_CHECKSIG
PUSH(F0) ... PUSH(F_last) PUSH(fieldSignature)
OP_2DROP ... OP_2DROP [OP_DROP]
```

There is one `OP_2DROP` for every pair of pushed data/signature fields and a final `OP_DROP` when their total is odd. The `OP_CHECKSIG` result remains on the stack. Do not substitute the lock-after-data example in BRC-48: current UMP readers expect the public-key lock first.

The `fieldSignature` is a secp256k1 ECDSA signature by the token locking key over:

```
SHA256(F0 || F1 || ... || F_last)
```

It is DER-encoded, with no transaction sighash byte. No field lengths, Script push opcodes, public key, satoshi value, or transaction data enter this preimage. Producers SHOULD use low-S signatures. The signature field is excluded from its own preimage. Consumers MUST verify it under the public key from this same script, and MUST NOT remove a field merely because it resembles DER. It authenticates the descriptor under its token key; it does not independently prove ownership of a phone number, knowledge of the recovery factors, first-registration priority, or an account's history.

Use minimal Script pushes: direct pushes through 75 bytes, `OP_PUSHDATA1` for 76 through 255 bytes, `OP_PUSHDATA2` with a little-endian 16-bit length for 256 through 65535 bytes, and `OP_PUSHDATA4` with a little-endian 32-bit length above that. Single-byte values 1 through 16 use `OP_1` through `OP_16`; thus the v3 marker is encoded as `OP_3` (`53`). Empty data uses `OP_0`, and the Script-number negative-one byte `81` uses `OP_1NEGATE`. Those last cases are not valid replacements for required nonempty UMP fields. Readers MUST recognize the small-integer opcode as its logical field value, including `[3]` for `OP_3`.

Conforming descriptor consumers MUST check the entire script, its compressed public key, allowed field count, minimal pushes, exact drop sequence, absence of extra opcodes, and valid field signature. They MUST bound individual and aggregate payload sizes before processing expensive content. The current wallet ceiling is 16777216 bytes for both a field and the aggregate decoded fields, including the signature; operational transaction size limits may be lower.

### 5.4 Spending and output value

New UMP writers MUST create a one-satoshi descriptor output. Historical topic admission does not enforce the exact amount, and account discovery readers SHOULD NOT discard otherwise usable historical account data solely because an older output carries a different positive amount. Spending always uses the actual source amount.

The unlocking script is a single minimally pushed transaction signature under the same token private key. The standard wallet update uses `SIGHASH_ALL | SIGHASH_FORKID` (`41`), without `ANYONECANPAY`, binding all inputs and outputs. Unlike the field signature, this signature has a sighash byte after its DER encoding. Calculate its preimage using the source outpoint, full source locking script, source amount, and the finalized transaction according to Bitcoin's fork-ID signature rules.

The account scheme does not turn the script into a covenant. A holder of the root primary key can derive the token signing key. Requiring privileged access before an ordinary client rewraps recovery factors is a client policy, not an extra consensus signature requirement.

## 6. Creation, authentication, and recovery

### 6.1 Creating an account

1. Obtain or establish `P`, and look up `H_P` as specified in Sections 10 and 11. Resolve existing-account evidence before offering account creation. A failed or ambiguous lookup is not an empty account.
2. Generate `R` and require the user to save it independently. Generate fresh `S`, `A`, and `V`, and derive `W` from the selected password and KDF. New accounts SHOULD use the v3 Argon2id default.
3. Compute every core field and the field signature. Initially there need not be a profile field.
4. Fund a transaction containing the one-satoshi token and necessary fees, using the default wallet identity rooted at `A`. A WAB faucet is an optional funding mechanism, not part of the account format.
5. Submit the transaction and required transaction evidence to `tm_users` using BRC-22/SHIP. Retain the exact transaction, resulting outpoint, and recoverable submission state. Verify that the returned transaction ID is the one actually submitted.
6. Commit local account state only when publication has a known successful result. If a WAB has a pending registration, finalize that association after publishing the token. On a timeout, reconcile the transaction and registration before retrying; do not generate a second account as a blind retry.

The account password and recovery key MUST NOT be sent to the WAB as part of this flow. A recovery-key saver MUST not report success before the user or their selected backup mechanism has actually accepted the key.

### 6.2 The three factor pairs

| Factors available | Discovery                            | Root primary recovery | Root privileged recovery |
| ----------------- | ------------------------------------ | --------------------- | ------------------------ |
| `P` and password  | `H_P`                                | `A = D(W XOR P, F1)`  | `V = D(W XOR A, F4)`     |
| `R` and password  | `H_R`                                | `A = D(W XOR R, F2)`  | `V = D(W XOR A, F4)`     |
| `P` and `R`       | `H_P` or `H_R`, with matching hashes | `A = D(P XOR R, F3)`  | `V = D(P XOR R, F5)`     |

For the password paths, obtain `S` and KDF metadata from the authenticated token before deriving `W`. For each supplied non-password factor, verify its discovery hash against the token. Authenticate every envelope used, validate the resulting root scalars, and reconstruct the token public key from `A`; it MUST agree with the descriptor's locking key before using that descriptor for account updates. Never accept a plausible-looking decrypted integer without GCM authentication.

Ordinary login may keep `A` in a protected session while deferring reconstruction of `V` until a privileged operation requires a password prompt. Recovery can reconstruct `V` immediately and use it temporarily to replace a missing factor. Once `V` is available, compute `K_wrap` and decrypt fields 8, 9, and 10 to recover `P`, `W`, and `R`. This recovers the password **key**, not the original password text.

Possession of `A` plus the password also recovers `V` from field 4. Consequently, a saved session containing `A` is security-sensitive even if it does not contain `V`.

### 6.3 Vendor-independent recovery

A client implementing recovery without a WAB performs the following operation:

1. Decode the user's saved `R`, compute `H_R`, and query independently selected `ls_users` providers. It MAY also use a saved authenticated descriptor and outpoint as a starting point for recovering its spend history.
2. Validate the returned descriptor, transaction evidence, and lineage. Resolve stale renditions and ambiguity under Section 11. Do not require an old WAB pin when unambiguous independent evidence suffices.
3. Ask for the password locally, derive `W` using that account's exact KDF, and recover `A` and `V` by the second row of the table.
4. Recover the wrapped factors and profiles. Rebuild the same default and profile identities and reconnect to compatible wallet storage using those identities.
5. If desired, establish a presentation key with a new backend and publish a successor token replacing `P`, consuming the old descriptor. Preserve the roots and profile pads. The previous WAB does not sign or approve this transition.

A conforming recovery tool MUST expose the `R` plus password path without first requiring successful authentication to the old WAB. A particular wallet UI may expose fewer recovery paths; that is a product limitation, not a cryptographic requirement of UMP.

Recovery still needs the descriptor and transaction evidence, suitable storage or a backup for the wallet's other state, and funds for an update transaction. UMP does not make a vanished storage service's private records reappear. Save recovery material, known outpoints, and appropriate wallet backups independently of a single backend or overlay host.

### 6.4 Recovery-key text

UMP itself uses raw `R`. Metanet clients commonly export it as standard, padded RFC 4648 Base64: a 32-byte key encodes to 44 characters ending in `=`. The surrounding text file's title and date are presentation, not protocol fields. A receiving client MUST decode the selected key text to exactly 32 bytes. It MUST NOT hash or use the Base64 characters as the factor. This format is not a mnemonic, WIF, or a Shamir share.

## 7. Updating an account and handling interruption

### 7.1 Spend the predecessor

Every normal account change MUST consume the exact current UMP outpoint and publish one successor for that account in the **same transaction**. Funding inputs and change outputs may also appear. The token need not be output zero; identify its actual index by the finalized output script and value, and reject an ambiguous match.

Before signing, obtain the predecessor's transaction evidence, check its outpoint and authenticated hashes, and verify that its locking key is the root/default wallet's expected token key. Bind the requested input, source amount, successor script, and final transaction before accepting a wallet action result. If the predecessor cannot be obtained, refuse the update. Do not silently turn it into an unrelated initial mint.

Retain the old local state and all new recovery material until the successor's publication outcome is known. A lost acknowledgment may follow a successful transaction; retrying the identical transaction or discovering its successor is appropriate, whereas generating another independent token is not. Concurrent devices may attempt to spend the same predecessor. Reconcile to the accepted spend and its lineage, then rebuild any still-desired change from the new current token. There is no last-write-wins timestamp field.

### 7.2 Factor changes

| Operation                    | Required changes                                                                         | Preserved values                                      |
| ---------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| Password change/reset        | Fresh `S`; new `W`; matching KDF metadata; re-encrypt affected root and factor envelopes | `A`, `V`, `P`, `R`, profile IDs and pads              |
| Recovery-key replacement     | Fresh saved `R`; new `H_R`; re-encrypt envelopes involving or wrapping `R`               | `A`, `V`, `P`, `S`, `W`, KDF interpretation, profiles |
| Presentation-key replacement | New `P`; new `H_P`; re-encrypt envelopes involving or wrapping `P`                       | `A`, `V`, `R`, `S`, `W`, KDF interpretation, profiles |
| Profile-list change          | New encrypted profile list, or omission when empty                                       | Roots, factors, salt, and KDF interpretation          |

An implementation MAY re-encrypt all envelopes on any update, as the reference wallet does, with fresh IVs. It MUST use one internally consistent set of factors throughout the successor. Preserving `A` and `V` preserves default identity, derived protocol keys, and profile identities.

When changing a WAB association, coordinate the off-chain and on-chain steps so an interrupted operation remains recoverable. The deployed phone-change flow stages the new association and key, spends the UMP token to publish the rotation, then finalizes the WAB change. The WAB retains current and pending keys during that interval. An idempotent reconciliation path is necessary because a database commit and a Bitcoin transaction are not one atomic operation. A new backend may use different authentication APIs while producing the same UMP transition.

### 7.3 Deletion and revocation limits

Spending a token without an admitted successor removes that live descriptor from lookup when the overlay observes the spend. Historical bytes remain on-chain. Deleting a WAB row, deleting a local wallet, relinquishing local output tracking, and spending a UMP descriptor are different operations; none should be represented as all the others.

Factor rotation does not erase old encrypted envelopes. Anyone who has already recovered `A` or `V`, or who later obtains enough factors to decrypt an earlier rendition, may still recover the unchanged roots. UMP factor rotation is therefore not root-key revocation. Remediation of compromised roots requires migrating the affected assets, identities, permissions, and data under an appropriate separate procedure. A client MUST NOT promise that a password change alone revokes knowledge of historical roots.

## 8. Profiles

Profiles provide multiple wallet identities beneath one recoverable UMP account. The default profile has the 16-byte all-zero ID, uses `A` and `V` directly, and is not included as an ordinary entry in the encrypted list.

For each additional profile, generate a distinct random nonzero 16-byte ID and independent 32-byte `primaryPad` and `privilegedPad`. Its roots are:

```
A_profile = A XOR primaryPad
V_profile = V XOR privilegedPad
```

Interpret them as fixed-width secp256k1 scalars and validate them as in Section 3.1. The profile ID and name do not themselves derive either key. Losing a pad is losing the corresponding derivation information.

The plaintext is a UTF-8 JSON array of objects:

```json
[
  {
    "name": "work",
    "id": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    "primaryPad": [192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223],
    "privilegedPad": [224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255],
    "createdAt": 1700000000
  }
]
```

The byte arrays are arrays of JSON integers from 0 through 255, not hex or Base64 strings. `createdAt` is seconds since the Unix epoch; writers use a nonnegative integer. Names are nonempty strings of at most 250 UTF-16 code units, matching the current JavaScript client bound. New lists MUST have unique IDs and SHOULD have case-insensitively unique names, reserving `default`. Readers MUST bound the list; the deployed maximum is 1000 entries. Invalid or unauthenticated profile data MUST produce an explicit error rather than silently discard profiles and save an empty list.

The on-chain profile field is:

```
profilesEncrypted = E(A, UTF8(JSON(profileArray)))
```

It is encrypted directly under the **root primary key**, not `V`, `K_wrap`, or a profile root. Historical source comments have described this differently; the primary-key encryption is the implemented format. The root wallet can therefore discover its profiles during ordinary login. Property order and JSON whitespace may vary; authenticate and decrypt the actual bytes.

If no additional profiles exist, omit the field. Adding or deleting a profile publishes a successor UMP token. Switching the active profile is a local choice and requires no transaction. UMP publication always uses the default profile's token key. Removing a profile entry does not spend its assets or make old on-chain encrypted profile lists disappear; clients SHOULD ensure that users have handled the profile's assets and backups before removing its discovery metadata.

## 9. The `tm_users` overlay

### 9.1 A fairly strict overlay

UMP is a **fairly strict overlay**, using [BRC-183](/overlays/0183) as the framework for explaining that qualification: most participating nodes should carry most account records most of the time, and discovery should ordinarily identify the same account lineage across providers. It is not intended as a collection of unrelated vendor-curated account catalogues. “Fairly strict” is a description of UMP's operational model, not a third formal category introduced into BRC-183.

The qualification is deliberate. This is not a guarantee that every node has identical state or that local first-admission races have a globally deterministic winner. BRC-183's fully Strict Overlay definition is stronger: it expects common state at a common chain checkpoint under prescribed rules. The deployed UMP profile described here has local first-accepted reservations, incomplete historical coverage, and possible legacy ambiguity. Calling it fairly strict does not add a consensus or block-order tie-break that its implementations do not have.

Hosts SHOULD replicate and reconcile account records and their relevant spend histories, and clients SHOULD use independent providers. A temporary difference in coverage is not an excuse to discard existing ownership checks or admit an unrelated replacement. GASP and BRC-64 history may help distribute evidence; merely using those transports does not settle independent initial-claim races.

### 9.2 Admission input and validation

The topic name is exactly `tm_users`. Submission follows [BRC-22](/overlays/0022), using BEEF transaction evidence and the overlay engine's normal Bitcoin validation. Only inputs already recognized as relevant UMP coins are eligible predecessors in `previousCoins`; input indexes are not output indexes. A claimed predecessor in JSON or an arbitrary funding input is not a token spend.

The baseline HTTP submission is:

```http
POST /submit HTTP/1.1
Content-Type: application/octet-stream
X-Topics: ["tm_users"]
```

The body is the serialized BEEF containing the submitted transaction and its required evidence, not a JSON serialization of the eleven fields or bare transaction hex. UMP defines no off-chain field payload. The per-topic JSON acknowledgment identifies actual output indexes and retained input indexes. For example, an update consuming relevant input 0 and admitting descriptor output 0 can return:

```json
{"tm_users":{"outputsToAdmit":[0],"coinsToRetain":[0]}}
```

Initial creation has no UMP predecessor to retain. A successful HTTP response with an empty `outputsToAdmit` array is not acknowledgment that the requested descriptor joined the topic. Submission acknowledgment and Bitcoin confirmation remain separate observations.

A conforming host MUST identify UMP descriptor candidates, validate their publicly checkable format, and apply the ownership rules below before final admission. It cannot decrypt the envelopes and therefore cannot certify that the publisher chose mutually consistent plaintext factors. It MUST NOT claim that admission proves a password or recovery key will work.

The Topic Manager at the pinned source revision uses a minimum shape check weaker than a wallet's authenticated descriptor parser: it decodes PushDrop, checks at least eleven fields and 32-byte hashes, and, when a single-byte v3 marker is found at field 11 or 12, checks marker `03`, one of the two algorithm names, and JSON with positive `iterations`. That acceptance alone is not sufficient for descriptor conformance. Hosts claiming the complete validation profile in this specification MUST bring publicly checkable checks into alignment with Section 5, while preserving recovery access to existing account data. Consumers MUST perform their own descriptor checks regardless of a host's software version.

Outputs are considered in ascending transaction output index. For each candidate, form its exact outpoint and read `H_P` and `H_R`. The following rules govern identity reservations even when an older host's shape validation is more permissive.

### 9.3 Separate hash namespaces and first-accepted ownership

Maintain an ownership map with two distinct namespaces:

```
(presentation, H_P) -> current owner outpoint
(recovery,     H_R) -> current owner outpoint
```

An equal byte string in different namespaces is not a collision. Both claims must succeed for a candidate output to be admitted. Checking only presentation hashes, or only recovery hashes, is insufficient.

For each hash claim:

1. If no live owner or unexpired provisional reservation exists, the first successful reservation acquires the hash provisionally.
2. Reprocessing the same outpoint is idempotent and does not create a competitor.
3. If a different admitted owner exists, a candidate may take over that claim **only if the transaction creating it actually consumes that exact owner outpoint as a recognized UMP input**.
4. Otherwise reject the conflicting candidate. Matching a hash, using the same locking public key, mentioning an ancestor, or spending an unrelated token is not sufficient.

These checks apply independently to both hashes. If the hashes have different current owners, the transaction must consume both owners to satisfy both claims; consuming only one is insufficient. A normal single-account update consumes its one predecessor and either retains its two hashes or replaces one with an unclaimed hash. Two sibling successor outputs cannot both own the same hash; reservation processing within one transaction must serialize that conflict.

“First” means first successfully reserved and admitted under that host's serialized ownership state, including shared state across replicas of that host. It does **not** mean the first lookup result, lowest transaction ID, largest fee, latest timestamp, lowest block height, or first message received by an arbitrary peer. Implementations MUST serialize competing claims using durable shared state or an equivalent atomic mechanism; a check followed by an independent insert is not a concurrency-safe ownership decision.

A new host must synchronize relevant ownership and history before claiming equivalent coverage. The deployed protocol does not define a universal reconciliation winner when disconnected hosts have already admitted different, unrelated initial claims. Such a conflict requires explicit evidence-based reconciliation and remains ambiguity to a client until resolved. A host MUST NOT silently replace a live owner merely because a different peer supplied another same-hash output later.

### 9.4 Reservation lifecycle

Reservation is not final admission, and final overlay admission is not mining confirmation. The implementation's reservation `confirm` operation means the output has been admitted/indexed, including when its Bitcoin transaction is still unconfirmed.

* An initial reservation is provisional until admission succeeds. A failed candidate must release its provisional claims.
* A transfer stages a pending successor while retaining the admitted predecessor as owner. A different pending claimant must not displace that successor while its reservation is active.
* If acquiring the second hash fails, undo only the claims or renewals made for this candidate, restoring their previous state. Do not erase another candidate's ownership.
* Successful admission/indexing commits the successor's reservations. Spending removes the predecessor from live lookup and transfers retained hashes to the admitted successor. Hashes not retained by a successor are released.
* An aborted or expired pending transfer must leave the prior admitted owner intact. Admission validation performed as a dry run must not acquire or change reservations.

The deployed provisional timeout defaults to 900 seconds and is configurable. Expiration releases abandoned **provisional** work; it is not a lease on a successfully admitted account and MUST NOT expire an admitted owner merely through inactivity. Multi-process hosts must coordinate expiration, retries, commit, and rollback against the same shared ownership state.

The deployed store releases an owner's claims when its output is spent or evicted. Thus reservations protect the current live owner; they are not permanent reservations of every hash ever present in the account's history. Once a claim is genuinely released and has no pending successor, it can be claimed again. A reappearance of a released hash is not proof of the old account's identity. Administrative eviction is consequently a consequential state change, not a harmless way to clear a cache.

Hosts MUST keep live lookup and ownership state coherent across accepted spends, rejected submissions, restart, and reorganization handling. If evidence is rolled back or a previously selected transaction ceases to be valid, repair/replay the affected records and reservations rather than retain stale ownership. The existing first-admission model does not specify a separate universal reorganization ordering algorithm; operators must not advertise stronger convergence than they can establish.

### 9.5 History and legacy ambiguity

An accepted transaction returns its admitted output indexes and retains the relevant input coins as history under [BRC-64](/overlays/0064). The reference Topic Manager returns `coinsToRetain: previousCoins` when it admits outputs. A transaction with no admitted UMP outputs returns empty admission/retention instructions; engine spend notifications and history policy remain distinct from the lookup's live-record set.

Retained history is not a second live account. The lookup projection removes a spent or evicted output, while history retained by the engine can establish how a surviving rendition descends from an earlier one.

Older hosts could contain multiple unrelated records for the same hash. The deployed reservation migration seeds each hash from its first already-indexed occurrence, using existing insertion order, and does not silently delete other historical candidates. This is a local database migration rule, not evidence that that occurrence was first on the blockchain. Existing ambiguity remains visible to consumers and must be handled as described below. A migration MUST NOT declare every same-hash record to be part of one lineage.

## 10. The `ls_users` lookup service

The service name is exactly `ls_users`. Use a [BRC-24](/overlays/0024) lookup question with a plain JSON query object. The supported selectors are:

Send the question as the JSON body of `POST /lookup` with `Content-Type: application/json` on the selected overlay host.

```json
{"service":"ls_users","query":{"presentationHash":"<64 hex characters>"}}
```

```json
{"service":"ls_users","query":{"recoveryHash":"<64 hex characters>"}}
```

```json
{"service":"ls_users","query":{"outpoint":"<64 hex characters>.0"}}
```

Angle-bracket strings above denote syntax, not literal requests. Hash selectors are exactly 32 bytes expressed in hexadecimal. The service accepts either hex case and normalizes to lowercase. Outpoint syntax is defined in Section 1. Queries contain at least one selector, no unrecognized members, and only string selector values. Invalid lengths, malformed hexadecimal, object-valued selectors, and out-of-range output indexes are errors, not empty matches.

Clients SHOULD send exactly one selector. For compatibility with the deployed service, when several valid selectors are present the precedence is `presentationHash`, then `recoveryHash`, then `outpoint`; they are **not** combined with AND or OR. Every supplied selector is validated before this precedence is applied, so a malformed lower-priority selector still makes the query invalid.

The service indexes fields 6 and 7 of admitted outputs. A lookup returns references to live records, which the overlay engine resolves to the normal BRC-24 `output-list` answer, with `beef` and `outputIndex` for each entry. BEEF contains the transaction and whatever supporting evidence/history the host provides; its outer JSON encoding follows BRC-24. The topic's internal `{txid, outputIndex}` references are not a replacement client-facing response format. There is no UMP-specific response that contains plaintext keys or decrypted account data.

In the JSON transport, `beef` is an array of integer bytes from 0 through 255 and `outputIndex` is a zero-based integer into that entry's subject transaction. The outer object has `"type":"output-list"` and an `outputs` array. UMP assigns no meaning to optional BRC-24 `context` bytes. A clean empty answer has this exact shape:

```json
{"type":"output-list","outputs":[]}
```

BRC-24's compact binary response can carry the same logical results when supported by the transport. Neither encoding changes the UMP selector semantics or permits a client to skip transaction and descriptor validation.

The current lookup limits a response to the 100 most recently indexed matching records, newest insertion first. This exists to keep legacy ambiguity bounded and avoid indefinitely hiding a recent lineage tip behind old records. That ordering is not account priority, and the bound is not proof that all possible records were returned. There is no pagination selector in this profile. Clients encountering a saturated or incomplete response MUST account for possible missing history/candidates rather than claim completeness.

Use BRC-88 SHIP/SLAP discovery, explicit user-selected providers, or other supported overlay discovery to locate the topic and service. No hostname is an intrinsic part of UMP. Provider diversity improves availability but does not transform several agreeing replies into proof of a global registry's completeness.

## 11. Selecting the account from lookup evidence

### 11.1 Validate before choosing

A lookup result is evidence to validate, not an account chosen by array position. A consumer MUST:

1. Parse and validate its transaction/BEEF evidence using the wallet's applicable Bitcoin verification and chain-tracking rules. An output's field signature alone is not an SPV proof.
2. Derive the output's actual transaction ID, check that its index exists, and parse the authenticated UMP descriptor.
3. Check that the requested presentation or recovery hash actually matches the corresponding token field. For an outpoint query, check the exact transaction ID and output index.
4. Deduplicate identical outpoints while retaining all useful evidence. Two hosts may provide different history depths for the same output; a shallow first copy must not mask deeper evidence from another copy.
5. Resolve spend continuity before using a candidate's secrets or updating it.

### 11.2 Spend-based selection and legacy fallback

If matching candidates include predecessor and successor renditions, collect outpoints spent by each candidate's transaction and its available ancestors. A candidate is superseded if another candidate's history consumes it. Intermediate renditions need not appear as separate lookup results if their transactions are present in the evidence. Choose the sole unsuperseded candidate only when the evidence supports that conclusion; absent history is not proof of independence or current unspentness.

The deployed wallet also has a legacy disambiguation heuristic: among several surviving candidates it prefers the sole candidate whose creating transaction demonstrably spends an authenticated UMP output with the same presentation hash **or** the same recovery hash. This favors a continuing account over an independent re-registration. This heuristic is weaker than demonstrating descent from an already trusted outpoint; it MUST NOT be described as a universal first-registration proof or used to override conflicting, stronger continuity evidence. If no unique choice is established, report ambiguity.

Some WABs return an optional `umpTokenOutpoint` pin. Current clients use it only when ordinary lineage selection remains ambiguous and only if the pin equals one of the already validated matching candidates. A pin MUST NOT inject an unseen output, waive descriptor/evidence validation, override an unambiguous selected lineage, or become mandatory for independent recovery. It is an explicit extra trust input useful for legacy support, not an on-chain authority.

### 11.3 Empty, failed, and inconsistent lookups

Keep these outcomes distinct: a validated account; a clean empty response under the chosen provider policy; malformed or unsupported candidates; unavailable/incomplete discovery; and unresolved ambiguity. Malformed and failed peers must not override positive validated account evidence.

The current interactor waits for its selected hosts, resolves matching candidates, and can return “not found” when no validated match remains and at least one host returned a clean empty response. Other failed or malformed replies do not necessarily veto that result. This is an availability-oriented discovery policy, **not a cryptographic nonexistence proof**. A new implementation SHOULD make its absence policy explicit and use remembered outpoints, local snapshots, or WAB existing-account evidence to prevent accidental re-registration.

If a backend or a local record says the account exists but the overlay cannot establish its token, the client MUST offer retry or recovery instead of silently creating new roots. A WAB returning a different presentation key does not itself prove that the user's old account ceased to exist. A recoverable error must remain distinguishable from successful onboarding.

## 12. Local serialization compatibility

This section documents the Wallet Toolbox formats used to persist UMP state. They are useful to independent recovery/import tools but MUST NOT be confused with the Script field arrays. An implementation that does not import these local formats may still implement the on-chain protocol fully.

### 12.1 Binary token record

Define `VEC(bytes)` as Bitcoin CompactSize length followed by those bytes. CompactSize uses one byte below 253, `fd` plus a little-endian uint16 through 65535, `fe` plus a little-endian uint32 through 4294967295, and `ff` plus a little-endian uint64 thereafter. Writers use the shortest representation; readers MUST bound lengths before allocation and reject truncation.

All three local record versions start with a one-byte version and eleven `VEC` values in exactly the core-field order of Section 5.1:

```
local v1 = 01 || VEC(F0) ... VEC(F10) || VEC(UTF8(currentOutpoint))

local v2 = 02 || VEC(F0) ... VEC(F10)
              || profileFlag[1] || [VEC(profilesEncrypted)]
              || VEC(UTF8(currentOutpoint))

local v3 = 03 || VEC(F0) ... VEC(F10)
              || profileFlag[1] || [VEC(profilesEncrypted)]
              || kdfFlag[1]
              || [umpVersion[1] || VEC(UTF8(kdfAlgorithm)) || VEC(UTF8(kdfParams))]
              || VEC(UTF8(currentOutpoint))
```

Each optional group is present if and only if its preceding flag is `01`; `00` means absent. New writers MUST use only these two flag values. Current writers use local v2 without KDF metadata, including accounts without profiles, and local v3 with `kdfFlag=01` and `umpVersion=03` when metadata is present. A compatibility reader may encounter local v3 with `kdfFlag=00`, which means no metadata and therefore legacy PBKDF2; the normal writer does not produce it.

The record has no field signature, locking public key, transaction, or BEEF. It carries the outpoint separately because an on-chain output cannot include its own eventual transaction ID. The local version byte does not become an extra Script field. A local record must be reconciled against the authenticated on-chain descriptor before being treated as current. Some historical local readers tolerated colon-separated or otherwise noncanonical outpoint strings; emit the canonical form and do not pass those historical spellings directly into `ls_users`.

### 12.2 Session snapshot

The manager also supports these enclosing formats:

```
snapshot v1 = 01 || snapshotKey[32] || E(snapshotKey, payload)
snapshot v2 = 02 || snapshotKey[32] || activeProfileId[16] || E(snapshotKey, payload)
payload     = A[32] || VEC(serializedLocalTokenRecord)
```

Snapshot v1 implies the default profile; v2 includes the selected profile ID. The payload encryption uses the Section 3.2 envelope. A fresh random snapshot key is generated for each saved snapshot. The current outer snapshot version is 2 even when it contains a local v3 token. Bounds in current clients limit the full snapshot to 16777216 bytes.

The decryption key is included **inside the same snapshot**. The envelope is not password protection of an exported snapshot and does not conceal `A` from someone holding the snapshot bytes. Protect the whole snapshot using the platform's storage protections or an independently specified backup encryption format. Neither the outer version nor the active profile ID is GCM additional authenticated data in this legacy format. Importers MUST validate lengths, profile selection, and restored account state; they MUST NOT publish snapshots as if they were public UMP tokens.

## 13. Test vectors and conformance scenarios

All keys, salts, IVs, signatures, outpoints, and transactions in this section are artificial test material. They are public and MUST NOT be used to protect an actual account. Hexadecimal strings represent raw bytes. Unless stated otherwise, a digest is a single SHA-256 over the bytes, in digest order, not a Bitcoin transaction ID.

### 13.1 Factors, KDFs, and derivation

The base password and salt below exercise legacy PBKDF2, explicitly parameterized PBKDF2, and the v3 Argon2id default. The Unicode example checks UTF-8 handling without normalization. The explicit PBKDF2 vector changes only its iteration count to 20000.

```json
{
  "inputs": {
    "password": "UMP test password",
    "passwordUTF8": "554d5020746573742070617373776f7264",
    "salt": "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf",
    "presentationKey": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
    "recoveryKey": "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
    "recoveryKeyBase64": "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=",
    "rootPrimaryKey": "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f",
    "rootPrivilegedKey": "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
  },
  "kdf": {
    "legacyPBKDF2": "26bdd08c841a0d82e52002ac6e1bacadc631535b45efa41c8e61a51ba1ed898b",
    "explicitPBKDF2Iterations": 20000,
    "explicitPBKDF2": "e4d65939ad9542a2a76fa057e0b2ee4f60901e6d098797569caabf1bdf3d9046",
    "argon2id": "e9b3af6baf992a0cfca8608ed1e4ccbab8b73cb39b367b88842a4358aded517e",
    "unicodePassword": "café🔑",
    "unicodeUTF8": "636166c3a9f09f9491",
    "unicodeLegacyPBKDF2": "93d8b2fad4e55838a5bcc6e832da747128f7a6f3ec908e6e757b5e462f4dc726"
  },
  "derivation": {
    "lockingPrivateKey": "d782d5d8f28e4c0a81e5199d2d3c1e3295804dbabb1e457d34d0f39510cf73fd",
    "lockingPublicKey": "03df6ac569c2b43e7080b4313e8eb7e2aa9fded22cd1bfc737ba58834a233f6d1e",
    "wrappingKey": "57237aa4cc9eabd12dedf1003d007cef25ae652144cc90524025dbc10dafd682",
    "profilePrimaryKey": "8080808080808080808080808080808080808080808080808080808080808080",
    "profilePrivilegedKey": "8080808080808080808080808080808080808080808080808080808080808080"
  }
}
```

`lockingPrivateKey`, `lockingPublicKey`, and `wrappingKey` must be obtained from the Section 3.3 formulas. The two profile roots happen to be equal in this artificial fixture because of the selected pads; normal generation uses independent random pads and roots.

### 13.2 Core encryption fields

For an encrypted core field with index `i`, use a 32-byte IV all of whose bytes equal `0x20 + i`. Thus field 1 uses thirty-two `21` bytes, field 8 uses thirty-two `28` bytes, and so forth. The following array is the exact eleven legacy core fields, in index order:

```json
[
  "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf",
  "21212121212121212121212121212121212121212121212121212121212121214afaeda03ef301636464d7d5c7509ec3cd2529b23b601730fa36c2c2907a161f8ad37e2e2e513b7f6113b11c16245174",
  "222222222222222222222222222222222222222222222222222222222222222240e7193fe34a4ba03a83c53ef23857a7f56e6b7937619ade7d0c6da7ccde16c39c6e14130c280290310657e0e17603ce",
  "2323232323232323232323232323232323232323232323232323232323232323d8baa13b67236864bdc4b7d480a7e1b6a0081cae15d5e3fb37a0e6afdf2d234db2698e4c3333dcb4781f09d47583b431",
  "2424242424242424242424242424242424242424242424242424242424242424c806599299843f0ef400336bf624c6620a41291ecd6dedafbaca62373c84290276939bc4ba5c185b93b31e010d70b1c4",
  "2525252525252525252525252525252525252525252525252525252525252525746400da26b109ebed5cd792186ce1d68911fa9065ab44af8808afa6f499ba09b51dcd8ddce568b47df8fe525b3c5fcf",
  "630dcd2966c4336691125448bbb25b4ff412a49c732db2c8abc1b8581bd710dd",
  "72dbb7336c76780023f83da4c355f2eeea85733b13d3477697917790c1229084",
  "28282828282828282828282828282828282828282828282828282828282828286b2ece523fcca61e3044c168dbdc22960ca285b0d034fe9ecaed0088a41e0b2ab3b5cfaeb178ae8a938335a940d20219",
  "292929292929292929292929292929292929292929292929292929292929292989007c80f70e6d7a368406db15ec1f6031a5ab5e66947f88974b7060663dff05017a2b0b79e90bdb25c9bffa084766d1",
  "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a0c8c8f0442ea7796f632aa85217b3383461510de04d0971a3e840b1811c3e0ecc397b7c631fa6c78b752fb894e370abd"
]
```

For the v3 default-Argon2id fixture, preserve every legacy field except fields 1, 2, 4, and 9. Replace those four fields with the following values, using the Argon2id-derived password key and the same test IV convention:

```json
{
  "1": "21212121212121212121212121212121212121212121212121212121212121213aa2118b4059597616798308f01b802043daaec95d45b1548d9dcd6b0904cf828c89edf7049abdfaf723700f010ee7da",
  "2": "2222222222222222222222222222222222222222222222222222222222222222c9227649168bb4c0523aaaf4d3424f96851bf0f0bc1c9ed47bc14ba29c4d98a9943d669a05ece30f293670c49d9335a5",
  "4": "242424242424242424242424242424242424242424242424242424242424242463d275073ae2149bf1826156597657b095789b854472391985104a949ed5054475a36316f4e0b105144c8ee99b4005ff",
  "9": "2929292929292929292929292929292929292929292929292929292929292929460e0367dc8d4af42f0c64f9aa137f774f23c4b6b84da01c9d0096236a3d27f08dfceaf92e532610be26bb1937a2cd93"
}
```

For each fixture, every authentication path in Section 6.2 MUST recover the specified `A` and `V`. Unwrapping fields 8, 9, and 10 MUST recover `P`, that fixture's `W`, and `R`. In particular, recovering `W` must not be mistaken for recovering the password text.

### 13.3 Profile encryption

Encrypt the exact compact UTF-8 plaintext string below directly under `A`, with an IV of thirty-two `2b` bytes. The expected envelope is:

```json
{
  "plaintext": "[{\"name\":\"work\",\"id\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],\"primaryPad\":[192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223],\"privilegedPad\":[224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255],\"createdAt\":1700000000}]",
  "ciphertext": "2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b76b339fe21577315aff41a307a05bb07b78f3cc4680c9e70e853ae1f2fcd921bd5eb90193169976987b1a06afddf2d9794db9f7acda18eb585233fdf87cee51a651dd39dc1464f6b8c2cfdc0413c877ca0ae3a17a1af3224da42e6a1ef16abc205bf5960fac9d040c615656a7f9a984411eaa65d1d51e40f3890f831dcd80281eef7b2ab07d3670d75fd02d06efe5688c1e461501661ffbfc5971a9873f5ea43b663680e36bcc832b00306feb395ef3ea52923511c520abd4bc882b9148f51911cf4e429955b001cab0f874a3cad7a7ca187023284c4b659c74047e9278ecd2650bdb14fe1c0c697d978bc43ae39a87df7153d594f4b5015ba4eb739b7223fc711fcec05f84ef83e653f038746c9a7d99f5af7dd840743aef840cab7ee58eb068127977161d8ae7ca7837a140d52715b5c47867c970082b3650acd8e9f4a6919f18742b2f01aabb9fe8338f1d59ca6e177c4834e74f3880d5a7c444dca304d1fb022fd0ab0249249430efbb603352d0818bb990f86fbd015dae385fda578336e5bcf48628ac5b3"
}
```

### 13.4 Four complete script fixtures

Construct these arrays of protocol fields:

* `legacy`: the eleven legacy fields above.
* `legacyProfiles`: the legacy fields followed by the profile ciphertext.
* `v3`: the Argon2id core fields followed by byte `03`, UTF-8 `argon2id`, and the compact parameter JSON below.
* `v3Profiles`: the Argon2id core fields, profile ciphertext, and the same three metadata fields.

The metadata bytes are the UTF-8 encoding of this exact line, with no trailing newline:

```json
{"iterations":7,"memoryKiB":131072,"parallelism":1,"hashLength":32}
```

For each array, append the supplied DER signature and construct the full Section 5.3 script using the specified locking public key. These signatures verify over one SHA-256 of the concatenated protocol fields. The script byte length and hash include the public-key lock, all pushes, the signature, and the drops. In particular, the marker uses byte `53` as a Script opcode and each 80-byte envelope uses `4c50` as its push prefix.

```json
{
  "legacy": {
    "protocolFieldCount": 11,
    "signature": "3044022075a1b4714fde1247c288e754c6238104da5a058688f0e183d7756a1ce7b21cd7022046889977214ea25070df53dff3e3ba36dcccb7e2cc9240bff771c63e80337dd8",
    "lockingScriptBytes": 867,
    "lockingScriptSHA256": "c910ffe2e844e1b80f67f42477aed17ca5691840f2fa1e4c693bee75a416ff55"
  },
  "legacyProfiles": {
    "protocolFieldCount": 12,
    "signature": "304402204ed6733a4ca24354dff7cb1b4731f95deb76f36c4563c59acc95a2cac9a1191b02206a518acb049df32a157115871d658d3d80fba3baa227d6335ddf82ffd078b8e8",
    "lockingScriptBytes": 1294,
    "lockingScriptSHA256": "f815e08411607e7ba174d0d1d23e9b63888d2a8b7c107fae17fc97c699a15a5b"
  },
  "v3": {
    "protocolFieldCount": 14,
    "signature": "3044022041fbc6e35c81c9f83f15fe830630c20e86c86ef74c44270e33d157af59b4dad102203329b148ba0534d2c1898c3841aa387db7ad67491b40dd9d9b010ae86ae46990",
    "lockingScriptBytes": 947,
    "lockingScriptSHA256": "96d8493aa3955d96d166376cb3df00b1db23e294dcc70dfd1310c0bb711a5be0"
  },
  "v3Profiles": {
    "protocolFieldCount": 15,
    "signature": "30440220088f074c2309c101910fca2651ad2ed29bdd770eac588dbdf620445abee043fe0220212da5a7d937789a4fc34b40df3ef059a1f8ab1643e10744add1198b23f45c43",
    "lockingScriptBytes": 1373,
    "lockingScriptSHA256": "37891ef831b68f81560cc6a60f5a2c26d7b9e02e1efcb54348716170e57f94c7"
  }
}
```

These are byte-exact serialization vectors using the supplied signatures. Another valid ECDSA nonce can yield another valid signature and therefore a different script hash; conformance does not require every implementation to reproduce the signing nonce. A verifier must accept the supplied signatures.

### 13.5 A token-spend transition

The following is a deterministic transaction serialization and signing fixture. It is not a funded transaction or proof of mainnet/testnet admission; its funding reference is deliberately synthetic.

Construct the source transaction with version 1, one input, one output, and locktime 0. The input's displayed source transaction ID is 64 `1` characters, its index is 0, its unlocking script is the single byte `00`, and its sequence is `ffffffff`. The output has one satoshi and the `legacy` locking script from Section 13.4.

Construct the update transaction with version 1, one input, one output, and locktime 0. Its input spends output 0 of that source transaction, has sequence `ffffffff`, and uses the exact unlocking script below. Its output has one satoshi and the `v3Profiles` locking script. Use Bitcoin transaction serialization, little-endian integer fields, CompactSize lengths, and reversed transaction-ID bytes in the input outpoint. Verify the update's `41` transaction signature using the source's complete script and one-satoshi amount. The expected identifiers are:

```json
{
  "sourceTXID": "1c19754519e326506e3dc89195aae6899d01d2d7cc779483342bdc86e53a5b81",
  "sourceOutputIndex": 0,
  "sourceSatoshis": 1,
  "updateTXID": "57d59fef5e30bfd201b0603d7114beb5e5db06276084873330105560142146ca",
  "updateUnlockingScript": "483045022100b932fff36cb4e31988f8af0d495fcdc50976d0f3d74da5748d1636436340f04a022038353325ff224db984c42a1c447a604c7aa5b5c5834469d5835bd00e0b9742a341",
  "currentOutpoint": "57d59fef5e30bfd201b0603d7114beb5e5db06276084873330105560142146ca.0"
}
```

The update is the successor because it spends the source outpoint. It has no rendition counter. Looking up both outputs with that history available must select the update. This vector models an authenticated legacy-to-Argon2id migration with explicit metadata and preserved roots.

### 13.6 Local records and snapshots

Use the `currentOutpoint` from Section 13.5 for all three local-record fixtures. For local v1 use the eleven legacy fields and no flags; for local v2 use the legacy fields, `profileFlag=01`, and the profile ciphertext; for local v3 use the Argon2id fields, `profileFlag=01`, the profile ciphertext, `kdfFlag=01`, marker `03`, and the exact Argon2id metadata bytes from Section 13.4. Serialize according to Section 12.1. The expected byte lengths and hashes are:

```json
{
  "v1": {
    "bytes": 815,
    "sha256": "838dbcbb7b662f476455682a5e8a1cfc1a5efd97e37461381438ffeaa5db5ac8"
  },
  "v2": {
    "bytes": 1242,
    "sha256": "45712ee2f8abda14f5f6162a0bca80811a97efee4effd180bcafb31e1e8e0c28"
  },
  "v3": {
    "bytes": 1321,
    "sha256": "feabe05ade12a642a51c0b1ecd56786348c200d65cc7bb34891e39f0b6f3cb3d"
  }
}
```

For the enclosing snapshots, use the given snapshot key and IV, with the version-specific token record named below. Both payloads start with `A`; snapshot v1 implies the default profile and snapshot v2 explicitly selects the fixture's additional profile. The following hashes cover the entire outer snapshot, including the clear snapshot key and, for v2, the active profile ID:

```json
{
  "snapshotKey": "5555555555555555555555555555555555555555555555555555555555555555",
  "iv": "6666666666666666666666666666666666666666666666666666666666666666",
  "activeProfileId": "0102030405060708090a0b0c0d0e0f10",
  "v1": {
    "tokenRecord": "v1",
    "bytes": 931,
    "sha256": "22f06ea6483790c9e41f33d5a7a2ac3d21d3a496a68b5b57a3b144447d305a04"
  },
  "v2": {
    "tokenRecord": "v3",
    "bytes": 1453,
    "sha256": "14aff452c151285a896a9fd385981dcc88e13287b602d06a745aa7853e0d75fe"
  }
}
```

### 13.7 Parsing and cryptographic rejection cases

Apply each mutation independently to an otherwise valid fixture. These cases are requirements for descriptor consumers; they do not assert that every historical topic manager already made the same checks.

| Case                                                                                                                             | Expected result                                                                         |
| -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Each of the four Section 13.4 layouts                                                                                            | Parse successfully; distinguish the field signature, optional profile, and KDF metadata |
| Legacy fields with no metadata                                                                                                   | Derive with PBKDF2-HMAC-SHA512, 7777 iterations, 32 bytes                               |
| v3 algorithm `pbkdf2-sha512`, parameters `{"iterations":20000}`                                                                  | Derive the explicit PBKDF2 result in Section 13.1                                       |
| Same PBKDF2 metadata with `hashLength:64` added                                                                                  | Still derive 32 bytes, as implemented; never derive a 64-byte XOR factor                |
| Change one ciphertext byte without resigning                                                                                     | Reject field authentication                                                             |
| A correctly signed token whose used ciphertext fails GCM authentication                                                          | Reject that recovery attempt; do not create a new account                               |
| Missing signature or a DER-shaped last field that does not verify                                                                | Reject; do not strip it by appearance                                                   |
| Put UTF-8 `3` in the v3 marker field                                                                                             | Reject unsupported layout/version                                                       |
| Unknown KDF algorithm, missing required Argon2id member, fractional/string iteration value, or KDF JSON over the supported bound | Reject before performing the KDF                                                        |
| Argon2id `hashLength:64`                                                                                                         | Reject as an unsupported UMP factor length; do not truncate                             |
| Put the version after an empty profile placeholder                                                                               | Reject this producer layout; do not treat it as a real encrypted profile                |
| Move the public-key lock to the end, insert an extra opcode, or use a nonminimal data push                                       | Reject noncanonical UMP script                                                          |
| Use a 31-byte discovery hash or hash the Base64 text of `R`                                                                      | Reject format or fail to match the requested account, respectively                      |
| Wrong password, `P`, or `R`                                                                                                      | Hash mismatch and/or authenticated-decryption failure, never partial plaintext          |
| Corrupt/truncate a snapshot or reference a missing active profile                                                                | Import error; do not save a replacement empty-profile state                             |
| Read a valid snapshot without an additional outer protection mechanism                                                           | Recover `A`; this demonstrates why the snapshot is secret-bearing                       |

### 13.8 Overlay state transitions

These are state-machine vectors, independent of Bitcoin byte fixtures. Each transaction and claimed source output is assumed to have passed normal transaction validation. `a`, `b`, and `c` are distinct outpoints; `p0`, `p1`, `r0`, and `r1` are distinct 32-byte hash values. Except where stated, each row starts with admitted owner `a` holding `(presentation,p0)` and `(recovery,r0)`, and no other reservation. “Consumes” means inclusion in the transaction's actual recognized UMP input set.

| Candidate/event                                                                | Consumes                         | Expected result                                                                 |
| ------------------------------------------------------------------------------ | -------------------------------- | ------------------------------------------------------------------------------- |
| Initial `a(p0,r0)` in an empty store                                           | None                             | Reserve both, then admit/index and commit both                                  |
| Replay `a(p0,r0)`                                                              | None                             | Idempotent; no second record                                                    |
| `b(p0,r0)`                                                                     | None                             | Reject both-hash collision                                                      |
| `b(p0,r1)`                                                                     | None                             | Reject presentation collision                                                   |
| `b(p1,r0)`                                                                     | None                             | Reject recovery collision and roll back the provisional `p1` claim              |
| `b(p0,r0)`                                                                     | `a`                              | Stage and then commit successor ownership to `b`; `a` leaves live lookup        |
| `b(p1,r0)`                                                                     | `a`                              | Admit presentation rotation; transfer `r0`, acquire `p1`, release `p0` on spend |
| `b(p0,r1)`                                                                     | `a`                              | Admit recovery rotation; transfer `p0`, acquire `r1`, release `r0` on spend     |
| `b(p0,r0)` with evidence merely mentioning an ancestor of `a`                  | No current owner                 | Reject: a shared ancestor reference does not consume the current owner          |
| `b(p0,r0)` and `c(p0,r1)` in one transaction                                   | `a`                              | The later conflicting output cannot also acquire `p0`                           |
| Concurrent unrelated initial claims for the same hash in an empty shared store | None                             | At most one active provisional winner; at most one admitted owner               |
| Pending successor `b` fails or expires                                         | `a` had been the candidate input | Restore/retain admitted owner `a`; do not expire it                             |
| Dry-run validation of `b`                                                      | `a`                              | No reservation or ownership mutation                                            |
| Spend `a` with no successor retaining `p0`                                     | `a`                              | Remove live `a`; release `p0`, not a permanent historical lock                  |
| After actual release, new `b(p0,r1)`                                           | None                             | May acquire free hashes; this does not prove continuity with `a`                |

For a two-owner vector, start with `a(p0,r0)` and `b(p1,r1)`. Candidate `c(p0,r1)` consuming only `a` fails because `r1` belongs to `b`; consuming both satisfies the per-hash ownership checks. For a namespace vector, a new presentation hash equal to the **bytes** of another account's recovery hash does not conflict merely through that cross-namespace equality.

### 13.9 Lookup and selection scenarios

| Evidence or request                                                                               | Expected result                                                                                    |
| ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| The Section 13.1 presentation key                                                                 | Query `presentationHash` equal to legacy field 6, not the presentation public key                  |
| The Section 13.1 recovery key                                                                     | Query `recoveryHash` equal to legacy field 7                                                       |
| Two valid selectors in one query                                                                  | Apply presentation/recovery/outpoint precedence, not conjunction                                   |
| Valid presentation selector plus malformed recovery selector                                      | Query error, despite the presentation selector's precedence                                        |
| Same outpoint returned by several hosts with different history depth                              | One candidate; retain the deeper usable evidence                                                   |
| Source and update from Section 13.5, with their connecting history                                | Select the update as the sole unsuperseded candidate                                               |
| A three-rendition chain with the middle rendition absent from lookup but present in BEEF ancestry | Select the surviving tip using the available spend evidence                                        |
| Two unrelated validated same-hash initial tokens, neither with trusted continuity                 | Ambiguous; do not select the first response                                                        |
| One surviving proven same-identity continuation and one unrelated initial token                   | The deployed legacy heuristic prefers the continuation; do not call it a global first-claim proof  |
| Two surviving proven continuations                                                                | Remain ambiguous unless stronger evidence resolves the conflict                                    |
| A WAB pin naming an output absent from the validated matching candidate set                       | Ignore the pin; do not fetch-and-trust it as an override                                           |
| A WAB pin naming a validated candidate, but normal lineage selection is already decisive          | Normal lineage selection wins                                                                      |
| One validated matching account plus failed, empty, or malformed peers                             | Preserve the positive account evidence                                                             |
| Only failures and no clean empty reply                                                            | Indeterminate/unavailable, not new-account success                                                 |
| No validated match and one clean empty reply                                                      | Current interactor can return not-found; existing-account evidence still prevents blind onboarding |
| A response at its 100-record limit                                                                | Treat completeness as unproven; do not use insertion order as ownership priority                   |

## 14. Implementations and compatibility notes

This document was checked against the following pinned source snapshots. These links are evidence of implemented behavior, not normative dependencies needed to interpret the format:

* [Wallet Toolbox `CWIStyleWalletManager`](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/wallet/wallet-toolbox/src/CWIStyleWalletManager.ts): core factors, field order, legacy/v3 KDF handling, profiles, publication, lookup selection, and local serialization.
* [Wallet Toolbox `WalletAuthenticationManager`](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/wallet/wallet-toolbox/src/WalletAuthenticationManager.ts) and [WAB client](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/wallet/wallet-toolbox/src/wab-client/WABClient.ts): presentation-key delivery, account continuity, registration, optional pins, and staged phone changes.
* [UMP Topic Manager](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/overlays/topics/src/ump/UMPTopicManager.ts), [identity reservation store](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/overlays/topics/src/ump/UMPIdentityStore.ts), and [lookup service](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/overlays/topics/src/ump/UMPLookupService.ts): shared reservations, first-accepted admission, spend/eviction lifecycle, legacy migration, query precedence, and bounded result ordering.
* [SDK symmetric encryption](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/sdk/src/primitives/SymmetricKey.ts), [key derivation](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/sdk/src/wallet/KeyDeriver.ts), [PushDrop](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/sdk/src/script/templates/PushDrop.ts), and [canonical envelope validation](https://github.com/bsv-blockchain/ts-stack/blob/daca1565f383184bc3bfe0718b34a13e3583abd5/packages/sdk/src/script/templates/PushDropValidation.ts).
* [Metanet Client Desktop](https://github.com/p2ppsr/metanet-client-desktop/tree/1743ecb6fe9099f413f53cb9632a9e756d18ce8a), including its [wallet construction](https://github.com/p2ppsr/metanet-client-desktop/blob/1743ecb6fe9099f413f53cb9632a9e756d18ce8a/src/WalletContext.tsx), [recovery-key export](https://github.com/p2ppsr/metanet-client-desktop/blob/1743ecb6fe9099f413f53cb9632a9e756d18ce8a/src/components/RecoveryKeyHandler.tsx), and [recovery without the presentation factor](https://github.com/p2ppsr/metanet-client-desktop/blob/1743ecb6fe9099f413f53cb9632a9e756d18ce8a/src/pages/Recovery/LostPhone.tsx).
* [Metanet Explorer](https://github.com/p2ppsr/metanet-explorer-mobile/tree/a33b37cc30dcaa71dd1c59811fd26e8252511f2f), including its [wallet construction](https://github.com/p2ppsr/metanet-explorer-mobile/blob/a33b37cc30dcaa71dd1c59811fd26e8252511f2f/context/WalletContext.tsx), [recovery UI](https://github.com/p2ppsr/metanet-explorer-mobile/blob/a33b37cc30dcaa71dd1c59811fd26e8252511f2f/app/auth/recovery.tsx), and [recovery-key export](https://github.com/p2ppsr/metanet-explorer-mobile/blob/a33b37cc30dcaa71dd1c59811fd26e8252511f2f/components/RecoveryKeySaver.tsx).

The standalone [older UMP services repository](https://github.com/bsv-blockchain/ump-services/tree/be60909707e8b1d91b664b49fe60bc19455ce5b8) is historical evidence for field compatibility; its simpler shape-based Topic Manager is not evidence that first-accepted reservations are universally deployed. An operator must inspect the actual software serving its topic. Likewise, a particular UI's recovery menu is not the complete set of cryptographic recovery possibilities.

Several implementation distinctions must be preserved when applying this specification:

1. **Descriptor validation and historical overlay validation differ.** The contemporary wallet verifies the canonical envelope and field signature, while historical topic managers accept a broader set of shapes. Publicly indexable data is not automatically an authenticated, usable account. This specification's validation requirements do not certify those older hosts.
2. **Password migration must carry its KDF.** In the pinned manager, the legacy password-change path selects its configured new-account KDF, while the common factor updater preserves metadata from the old token. For a metadata-free token and the default Argon2id configuration, those two decisions do not form a valid migration. Implementers MUST follow Section 4.4 rather than copy that mismatch. Non-password updates correctly need to preserve the legacy interpretation; a password migration needs to pass the newly selected metadata with the newly derived factor. This is an implementation compatibility issue, not another implicit wire version to guess during login.
3. **Validate actual factor lengths.** The general KDF configuration validator accepts some output lengths that do not fit 32-byte XOR factors. PBKDF2's implemented fixed output length and Argon2id's required UMP factor length are specified separately in Section 4.3. Scalar-to-byte conversions must also preserve or restore leading zeros as described in Section 5.1.
4. **Replication and conflict convergence are different.** The production shared reservation store defines per-host first-accepted ownership. A process-local test store is not a substitute for coordination across serving replicas, and neither is a global ordering mechanism between disconnected hosts.
5. **Absence and recovery UI are policy boundaries.** A clean empty reply is not a nonexistence proof, and a WAB-based screen is not a reason to withhold the independent recovery algorithm. Implementations should expose their actual guarantees plainly.

The vectors were generated with `@bsv/sdk` 2.8.8, Node's AES-GCM/PBKDF2, and `hash-wasm` 4.12.0, then checked independently using Python's `hashlib`/`hmac`, `cryptography`, and the Argon2 reference binding. Independent checks covered KDF outputs, self-derived keys, every key envelope, profile encryption, all four field signatures and scripts, the update transaction signature and IDs, and the local record/snapshot bytes. The six factor-pair recovery cases and four authenticated field layouts were also checked against `@bsv/wallet-toolbox-client` 2.14.2. These are offline conformance fixtures, not evidence of live account publication or universal deployment conformance.

## 15. Security and operational considerations

A WAB is trusted to protect and correctly return one factor and to describe its authentication state honestly. Possession of `P` alone does not directly supply a root, but it permits offline password guesses against the published password/presentation envelope. Possession of `R` similarly permits guessing against its password envelope. Password strength and the account's actual KDF matter; “two of three” must not be advertised as making a weak password harmless. Legacy PBKDF2 support is an accessibility requirement, not a recommendation to keep creating weakly stretched accounts.

The recovery key should be stored independently of the presentation backend and ordinary session data. A backend holding both `P` and `R` can recover without the password. A root privileged key can recover the wrapped factors, and a root primary key can authorize token spends. Their different roles do not eliminate the need to protect both roots and the client that uses them.

Public discovery hashes and token histories reveal linkage across renditions that retain a hash or share a spend lineage. Profiles are encrypted, but their existence, ciphertext sizes, and account updates may still be observable. Sending discovery hashes to providers discloses those queries to the providers. Hashes of random factors are not permission to publish the factors, local snapshots, passwords, or WAB authentication payloads in diagnostics.

First-accepted reservations protect a live claim against later unrelated same-hash admission. They do not authenticate civil identity, settle namespace disputes, prove network-wide first publication, or make a compromised backend honest. Clients must validate continuity and preserve independent recovery evidence. Pins are support decisions with a trust cost, not cryptographic proof.

Backups and overlays serve different purposes. An overlay can provide the public encrypted descriptor and its history, but cannot replace private wallet storage, encrypted application files, or a recovery key that was never saved. Operators should retain enough transaction/history evidence to recover after failures and should not evict ownership records casually. Users should be able to change client, WAB, overlay providers, and storage arrangements without being forced to generate unrelated account roots.

## References

* [Ty Everett, *Computing with Integrity* (2020)](https://github.com/open-cash-standards/papers/blob/889f38bcbce8773718b248b1704a91e0a19567dd/computing-with-integrity/paper.pdf): conceptual background, particularly the key definitions and bootstrap process.
* [BRC-42: BSV Key Derivation Scheme](/key-derivation/0042), [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043), and [BRC-44: Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044).
* [BRC-48: Pay to Push Drop](/scripts/0048) and [BRC-100: Wallet-to-Application Interface](/wallet/0100).
* [BRC-22: Overlay Network Data Synchronization](/overlays/0022), [BRC-24: Overlay Network Lookup Services](/overlays/0024), and [BRC-64: Overlay Network Transaction History Tracking](/overlays/0064).
* [BRC-62: BEEF](/transactions/0062), [BRC-95: Atomic BEEF](/transactions/0095), and [BRC-76: Graph Aware Sync Protocol](/transactions/0076).
* [BRC-87: Topic and Lookup Naming](/overlays/0087), [BRC-88: Overlay Services Synchronization Architecture](/overlays/0088), and [BRC-183: A Framework for Strict and Federated Overlays](/overlays/0183).
* [BRC-184: Optional Metadata Registries and Their Stewardship](/wallet/0184) and [registry submission and maintenance guide](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md).
* [RFC 8018](https://www.rfc-editor.org/rfc/rfc8018): PBKDF2; [RFC 9106](https://www.rfc-editor.org/rfc/rfc9106): Argon2; [NIST SP 800-38D](https://doi.org/10.6028/NIST.SP.800-38D): GCM; [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648): Base64.
* [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174): requirement terminology.


# Identity, Certificates, Discovery, and Personal Trust in Applications

Ty Everett (<ty@projectbabbage.com>)

## Abstract

An application can address a person by a cryptographic identity key, discover attributes that the person has chosen to publish, and request further certified information directly from that person. The application need not create another username directory, require an account at a particular wallet vendor, or ask a central identity provider to authorize every interaction. The user's wallet supplies identity operations, applies the user's chosen trust relationships, and preserves personal associations through contacts.

This specification connects the certificate primitives of BRC-52, the application interface of BRC-100, the peer authentication and disclosure mechanisms of BRC-103, and the descriptive registries of BRC-184. It specifies public attribute revelation, `tm_identity` admission and withdrawal, `ls_identity` discovery, the deployed trust calculation, and encrypted contact records. It distinguishes a certifier's assertion, a user's personal assertion, proof of key control, a current revocation assessment, and permission to act. Worked application journeys and reproducible test vectors show how these pieces fit together.

## 1. Status, scope, and conformance

This is a specification and integration profile for existing identity mechanisms, authored to make their relationships durable and implementable. The words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY have the meanings in RFC 2119 and RFC 8174. Normative requirements apply to an implementation claiming the relevant role in this specification. Examples, motivation, application journeys, and observations about particular releases are informative.

An implementation may implement a certifier, wallet, application, public-revelation publisher, overlay host, or contacts client. It MUST identify its supported roles and any supported compatibility profiles. This specification does not claim that every historical release satisfies all requirements. Section 17 identifies concrete differences between the published interfaces and pinned implementations; those differences are not silently promoted into new wire formats.

[BRC-52](/peer-to-peer/0052) remains authoritative for the certificate primitive. [BRC-100](/wallet/0100) remains authoritative for wallet method and transport encodings. [BRC-103](/peer-to-peer/0103) and [BRC-104](/peer-to-peer/0104) govern authenticated exchange and its HTTP transport. This document explains those primitives sufficiently to follow and implement the identity lifecycle, and specifies the public identity and contact conventions surrounding them. It does not replace their interfaces, introduce a new identifier grammar, or allocate a universal human-name namespace.

The public identity output specified here has no version prefix. Protocol names, field names, byte encodings, and script shapes below identify the existing format. An incompatible replacement MUST use an explicitly distinguishable format and documented migration; a registry description or a client option cannot silently change what `tm_identity` accepts.

## 2. Identity as a relationship, rather than an application account

### 2.1 Keys, claims, and participants

An **identity key** is a valid compressed secp256k1 public key, conventionally 66 lowercase hexadecimal characters. The holder controls the corresponding private key through a wallet. The application obtains its user's current identity key with `getPublicKey({ identityKey: true })`. It obtains another party's identity key through an authenticated exchange, an independently checked contact, or discovery. A public key is not a legal name, email account, payment address, network endpoint, or proof that one human has only one wallet.

The same key can identify a party across independent applications. A person may also choose different keys or wallet profiles for different contexts. Applications MUST preserve the actual key and its provenance, rather than join identities merely because they share a display name, photograph, email string, or device. Account recovery that preserves the private key preserves this cryptographic identity. Moving to a different key requires an explicit continuity process, new certificates where appropriate, and deliberate contact updates; an equal name is not a rotation proof.

| Participant               | Function                                                              | Basis of authority                                                                                   |
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Subject                   | The key about which a certificate makes claims                        | The certificate names the key; a live protocol separately establishes its holder's control           |
| Certifier                 | Checks evidence and signs attributes bound to a subject               | The verifier elects to rely on that certifier for an applicable assertion                            |
| Verifier or relying party | Receives, checks, and uses an assertion                               | Its own policy and the user's trust choices                                                          |
| Wallet                    | Holds keys and certificates, mediates operations, resolves identities | Delegation from its user, with application permissions                                               |
| Application               | Uses identity in a particular interaction                             | User authorization and the application's disclosed acceptance rules                                  |
| Overlay host              | Validates, indexes, and serves public revelations                     | Its authenticated data and stated collection/availability policy; it does not certify the attributes |
| Registry publisher        | Describes protocol, basket, and certificate-type identifiers          | Its attributed descriptive statement under BRC-184                                                   |
| Contact author            | Saves a personal association between a key and attributes             | The saving user's own assertion, authoritative in that user's context                                |

One entity can perform several roles, but the authorities remain separate. Operating an overlay does not make its host a trusted certifier. Publishing certificate-type metadata does not make the publisher an acceptable issuer. A certificate's subject need not use the certifier's wallet, storage provider, payment processor, or applications.

### 2.2 Why this belongs beneath applications

Suppose Alice already uses one wallet for a marketplace, a messaging application, and a professional directory. A certifier can attest that Alice's identity key controls an email address. Alice can publish that attribute once. Bob's wallet can discover the assertion through available overlay hosts, recognize its issuer under Bob's trust choices, and return the identity to any authorized application Bob uses. Each application can display Alice, authenticate a session with her, request further evidence, or construct an appropriate payment without importing the certifier's account system.

The certificate provides a reusable claim. Publication provides discoverability. The wallet supplies user-selected interpretation. The application supplies the purpose of the interaction. Separating these functions makes identity useful beyond login: transaction histories can show familiar counterparties; encrypted documents can be addressed to known keys; messages can be attributed without a social platform owning the relationship; and private qualifications can be requested only when needed.

Ty Everett's *Computing with Integrity* (2020) described wallet-mediated certificate requests and the distinction between revealing a real-world name and proving a useful certified fact without that name. Its identity-verification discussion is conceptual background, not the wire definition for current certificates. [BRC-51](/opinions/0051) supplies useful payment, public-identity, and referral experiences; [BRC-89](/opinions/0089) describes the broader integration of identity, wallets, overlays, and peer communication. This specification uses the current BRC-52/100/103 mechanisms rather than the earlier Authrite-era certificate formats.

### 2.3 Five questions that must remain distinct

1. **Who controls this key now?** Establish this through a fresh authenticated exchange or other applicable proof of control.
2. **What did an issuer assert about that key?** Verify the complete certificate signature and the relevant revealed fields.
3. **Why do I rely on that issuer or association?** Apply the user's selected trust anchors or an explicitly saved personal contact.
4. **Is the assertion suitable and current for this purpose?** Check its meaning, scope, expiry where defined, revocation, and relevant changes in the underlying attribute.
5. **May this operation happen?** Obtain the applicable permission or satisfy the application's acceptance policy.

A successful answer to one question MUST NOT be presented as a successful answer to all five. A payment recipient's public identity key is enough to derive an interoperable destination with the appropriate payment protocol; it does not itself authorize spending or establish that the intended human controls that destination.

## 3. Certificates and their cryptographic meaning

### 3.1 Core object and identifiers

A certificate binds one subject key to a map of encrypted attribute values under one certifier's signature. Its signed core consists of the following data; the `signature` is the signature over the other members, not an input to itself.

| Member               | Representation and meaning                                                                         |
| -------------------- | -------------------------------------------------------------------------------------------------- |
| `type`               | Canonical Base64 encoding of exactly 32 bytes identifying a field schema and meaning               |
| `serialNumber`       | Canonical Base64 encoding of exactly 32 bytes identifying this certificate within its issuer's use |
| `subject`            | Compressed secp256k1 public key in hexadecimal                                                     |
| `certifier`          | Compressed secp256k1 public key of the signing issuer                                              |
| `revocationOutpoint` | Transaction ID, full stop, and zero-based output index, such as `txid.1`                           |
| `fields`             | Map from case-sensitive field names to Base64-encoded encrypted values                             |
| `signature`          | DER-encoded ECDSA signature represented as hexadecimal                                             |

A type is not an issuer key. Multiple issuers may issue the same type with the same field meanings, and the verifier still decides which issuers to rely on. A serial alone is not a global identity or globally unique lookup key: retain at least the issuer, type, and serial when selecting a particular certificate. Preserve the subject and signed bytes as well when checking the actual assertion. Reusing the same issuer/type/serial for incompatible certificates is an issuer error and MUST NOT become a last-response-wins update mechanism.

Field names are 1–50 UTF-8 bytes under BRC-52. Producers SHOULD use simple printable ASCII names with documented ordering and exact case. `userName` and `username` are different fields. Field values are strings at the plaintext layer; a date, number, boolean, or structured assertion requires a schema defining its string representation. The core has no universal issuance time, expiry, assurance level, jurisdiction, or legal-person status. If a type needs those facts it MUST define and certify them, or clearly state the external policy on which they depend.

### 3.2 Canonical binary and signature

The signature preimage is the BRC-52 binary core without its signature. JSON serialization is not the certificate preimage.

| Order | Bytes                                                                                                         |
| ----- | ------------------------------------------------------------------------------------------------------------- |
| 1     | Base64-decoded `type`, 32 bytes                                                                               |
| 2     | Base64-decoded `serialNumber`, 32 bytes                                                                       |
| 3     | Hex-decoded `subject`, 33 bytes                                                                               |
| 4     | Hex-decoded `certifier`, 33 bytes                                                                             |
| 5     | Hex-decoded revocation transaction ID, 32 bytes in its displayed hexadecimal order                            |
| 6     | Revocation output index as a Bitcoin VarInt                                                                   |
| 7     | Field count as a Bitcoin VarInt                                                                               |
| 8     | Each field in BRC-52 name order: VarInt UTF-8 name length, name bytes, VarInt UTF-8 value length, value bytes |
| 9     | DER signature bytes only when serializing the signed certificate                                              |

The field **value bytes in this preimage are the UTF-8 bytes of the Base64 string**, not the decoded encryption envelope. The transaction-ID bytes here are not reversed as they would be in a Bitcoin transaction input. VarInt means Bitcoin's compact-size encoding: one byte below 253, otherwise `fd` plus a two-byte little-endian value, `fe` plus four bytes, or `ff` plus eight bytes. Lengths count bytes, not Unicode characters.

BRC-52 specifies lexicographic ordering. The current SDK uses the host's `localeCompare` ordering; mixed-case, punctuation-rich, or non-ASCII field sets can therefore expose an ordering difference. A schema MUST make ordering unambiguous for its implementations, and implementations MUST preserve and verify the signed interpretation. They MUST NOT normalize field spelling or silently try a different certificate meaning after verification fails. The vectors use `email` followed by `name`, for which the relevant orderings agree. Section 17 records this compatibility boundary rather than defining a new signature format.

The certifier signs the preimage through BRC-100 with protocol `[2, "certificate signature"]`, key ID `type + " " + serialNumber`, and publicly verifiable counterparty semantics (`"anyone"`). Both identifiers use their original canonical Base64 strings. BRC-100 hashes `data` with SHA-256 before ECDSA signing; do not separately hash and then accidentally ask it to hash the digest again. Verification uses the same protocol and key ID, the issuer as counterparty, and the publicly known anyone root. Equivalently, derive the issuer's BRC-42 public child with private scalar `1` as the other party and invoice `2-certificate signature-<type> <serialNumber>`.

The final DER signature has no length prefix inside `CertificateBinary`. An enclosing format that appends a keyring MUST first length-prefix the complete signed certificate. Keyrings, decrypted caches, registry metadata, and trust scores MUST NOT be added to the core signature preimage. There is no current `validationKey` member.

### 3.3 Field encryption and keyrings

For each field independently, generate a random 32-byte revelation key `K_f`. Encode the plaintext string as UTF-8 and encrypt it with AES-256-GCM, a fresh random **32-byte IV**, a 16-byte authentication tag, and no additional authenticated data. The encoded field is:

```
Base64(IV[32] || ciphertext || authenticationTag[16])
```

This is the SDK/BRC-2 envelope. There is no padding or extra header. Replacing the IV with a commonly used 12-byte GCM IV is a format change. Authentication failure MUST reject that field; it MUST NOT yield partial plaintext. Fresh IVs are required for every encryption, including keyring re-encryption. Fixed IVs in Section 16 are test inputs only.

A **master keyring** holds an encrypted `K_f` for every field. A **verifier keyring** holds encrypted revelation keys only for the selected fields. Their BRC-100 derivation parameters are:

| Operation        | Protocol                              | Key ID                           | Encrypting and decrypting parties                               |
| ---------------- | ------------------------------------- | -------------------------------- | --------------------------------------------------------------- |
| Master keyring   | `[2, "certificate field encryption"]` | Exact field name                 | Subject and issuer, or an explicitly supported keyring revealer |
| Verifier keyring | `[2, "certificate field encryption"]` | `serialNumber + " " + fieldName` | Subject and selected verifier                                   |

The plaintext of each keyring encryption is the raw 32-byte `K_f`; the keyring value is the Base64-encoded BRC-2 envelope. The encrypting party supplies the other party as counterparty; the receiving party uses the sender as counterparty. BRC-42 derives matching symmetric keys through child-key ECDH, as used by BRC-100 encryption. These keys are neither a hash of the field name nor the subject's root private key.

A master certificate carries `masterKeyring`, also named `keyringForSubject` or `keyring` in particular storage/API contexts. A transmitted verifiable certificate carries `keyring`. A `proveCertificate` response calls the same verifier-specific map `keyringForVerifier`. An adapter MUST translate these names according to context without changing their cryptographic meaning. A master keyring MUST contain exactly the necessary nonempty entries for all signed fields; a verifier keyring MUST be a subset of those fields.

For binary extensions, prefix the complete signed `CertificateBinary` with its VarInt length, then append a keyring map: VarInt entry count, followed by each entry's VarInt name length, UTF-8 name, VarInt value length, and **Base64-decoded** keyring ciphertext. This distinction from the signed fields' Base64 **text** encoding is intentional. The enclosing protocol identifies whether the map is a master or verifier keyring.

### 3.4 What selective disclosure does and does not hide

The verifier receives the entire signed encrypted field map so it can verify the unchanged issuer signature. It receives revelation keys for only the authorized subset. Other field values remain encrypted, but their names, ciphertext lengths, certificate type, issuer, subject, serial, and revocation reference are visible. Reusing a certificate or identity key across interactions can link them.

Selective disclosure reveals selected certified values; it is not automatically a zero-knowledge predicate proof. To reveal “over 18” without a birth date using this mechanism, a certifier can issue an appropriately scoped and time-qualified `over18` field and the subject reveals that field. Decrypting a birth-date field and hiding it in the UI does not conceal it from the application that received it.

Once a verifier has plaintext or a revelation key, it can retain or disclose that information. Removing a wallet permission, withdrawing a public token, or revoking a certificate cannot make the recipient forget previously learned bytes. These operations change future authorization or reliance, not historical knowledge.

## 4. Certificate types, certifiers, and issuance

### 4.1 Define the assertion before implementing its form

A certificate type definition MUST specify its exact 32-byte identifier, case-sensitive fields, plaintext encodings, what each field asserts, who can issue it, and any validity or revocation policy required for relying on it. “Who can issue” describes acceptable evidence and issuer policy, not an exclusive cryptographic namespace allocation. A type SHOULD explain normalization, renewal, reassignment, and how a verifier can distinguish stronger and weaker evidence.

For an email-control certificate, the claim may be that the issuer verified access to a mailbox at issuance. It is not automatically a claim about legal identity, continued exclusive control, or ability to receive a blockchain payment. For an employment certificate, an employer should define the role, scope, and period. For a qualification, a professional body should define what the qualification permits and how it changes. An image field attests only the meaning the issuer actually checked; a supplied profile image is not inherently proof that a photograph depicts the key holder.

Type definitions SHOULD be documented through the BRC process and submitted to CertMap under [BRC-184](/wallet/0184). CertMap descriptors supply friendly names, descriptions, image references, and field presentation hints; they do not change the signed schema or appoint trusted issuers. Section 15 contains the registrations accompanying this specification.

### 4.2 Certifier responsibilities

A certifier MUST authenticate the subject or otherwise establish the binding required by its issuance policy, validate every assertion it will sign, and bind the certificate to the intended subject key. Signing arbitrary encrypted values without validating the corresponding claim is not attribute verification. In an encrypted issuance flow, the issuer needs revelation keys for the fields it must check.

The issuer MUST protect its signing and revocation authority, keep sufficient issuance and revocation records for its policy, and distinguish issuance success from a pending request or failed transaction. It SHOULD publish its schema, verification methods, contact route, renewal and correction procedures, and revocation limitations. It SHOULD minimize retained verification evidence and avoid placing private evidence on-chain merely to issue a certificate. A certifier may charge for its work, but its business relationship need not remain in the path of every subsequent verification.

Issuer key rotation is a trust change. A new key MUST NOT silently inherit a user's trust because it serves the same domain or displays the same logo. An authenticated transition can help the user decide; it does not erase the need to assess compromised old keys, old certificates, and the new issuer's authority.

### 4.3 Direct acquisition

With `acquireCertificate({ acquisitionProtocol: "direct", ... })`, the subject's wallet receives `type`, `certifier`, `serialNumber`, `revocationOutpoint`, `signature`, encrypted `fields`, `keyringForSubject`, and `keyringRevealer`. The latter is `"certifier"` or the explicit revealer key supported by the implementation. The wallet determines its own subject key; the caller cannot acquire a certificate for an unrelated subject by substituting that field.

Before storage, the wallet MUST verify the core signature and subject binding, require a complete master keyring, and authenticate/decrypt every supplied field through the correct revealer relationship. Storage MUST be associated with the correct wallet identity/profile and keep encrypted values and keyring roles intact. A failed acquisition MUST NOT be reported as a stored certificate. Acquisition does not publish attributes or grant applications future disclosure permission.

The successful API response is the core certificate. The stored master keyring is not automatically returned to ordinary callers. Some current direct-acquisition and proof implementations assume the issuer is the revealer even where the interface permits another key; implementers MUST check that distinction rather than claim universal support for arbitrary revealers.

### 4.4 Interactive issuance acquisition

The deployed `acquisitionProtocol: "issuance"` flow performs an authenticated POST to `signCertificate` relative to the requested certifier base URL. The subject passes plaintext `fields`, `type`, `certifier`, and `certifierUrl` to its own wallet; the wallet encrypts those fields before sending the issuance request. Over the authenticated connection the request body is:

```
{ clientNonce, type, fields: encryptedFields, masterKeyring }
```

The wallet creates independent field revelation keys and a complete master keyring for the issuer. The issuer authenticates the requesting subject, checks the challenge, decrypts and verifies the claims, creates its server challenge and serial, selects the revocation reference, and signs a core certificate. Its successful response is:

```
{ certificate: signedCoreCertificate, serverNonce }
```

The client MUST authenticate the server as the requested certifier, including checking the authenticated response identity, not merely trust the URL. It MUST check the server challenge, serial derivation, exact returned type, subject, issuer, encrypted field names and bytes, acceptable revocation reference, signature, and master-keyring decryptability before storage. The returned encrypted fields MUST be exactly those submitted; unrequested additions or changes require a new, explicitly understood issuance operation. An HTTP success code alone is not an acquired certificate.

The wallet's permission to acquire, the issuer's decision to certify, and the user's permission to reveal are three separate decisions. The issuer's verification ceremony—an email challenge, an in-person check, or an external account authorization—can occur before this exchange. BRC-100 does not require one universal ceremony or a specific SocialCert endpoint for that work.

### 4.5 Challenge and serial derivation used by issuance

The SDK challenge used here is a canonical Base64 encoding of 48 bytes:

```
random[16] || HMAC-SHA256(derivedKey, random[16])
```

Derive the HMAC key using `[2, "server hmac"]`, key ID equal to the SDK's UTF-8 decoding of the 16 random bytes, and the other issuance party as counterparty. The deployed decoder uses the WHATWG UTF-8 decoding behavior of `TextDecoder`: malformed sequences become U+FFFD and an initial UTF-8 BOM is removed. It is not hexadecimal or Base64 encoding of those bytes. The resulting string is encoded as UTF-8 in the derivation invoice. Verification uses the same raw 16 bytes and decoding rule. A verifier MUST require canonical Base64, the exact 48-byte length, and a successful HMAC verification result. These challenges are authenticated values, not inherently single-use or expiring credentials. The surrounding authenticated protocol and issuer transaction policy provide freshness, request binding, and retry handling.

Let `C` and `S` be the client and server challenge **Base64 strings**, respectively. The issuer computes:

| Serial derivation parameter | Value                                           |
| --------------------------- | ----------------------------------------------- |
| Protocol                    | `[2, "certificate issuance"]`                   |
| Key ID                      | `S + C`, string concatenation with no separator |
| Counterparty                | Subject for the issuer; issuer for the subject  |
| HMAC data                   | Base64-decode `C + S`                           |
| Serial                      | Base64 of the resulting 32-byte HMAC-SHA256     |

Each challenge encodes 48 bytes without Base64 padding, so decoding `C + S` produces the two 48-byte challenge bodies in client-then-server order. The key ID deliberately uses the opposite string order. Do not use the Base64 text itself as HMAC data or insert a space. Directly issued certificates may use an independently generated canonical 32-byte serial instead; the interactive serial rule is not a requirement for all BRC-52 certificates.

Retries MUST NOT fabricate success or issue a materially different assertion under an existing serial. A production issuer should associate the authenticated request with its issuance record and make an interrupted acquisition recoverable under its documented policy. No universal issuance idempotency API is introduced here.

## 5. Private disclosure and peer exchange

### 5.1 Selecting and proving a certificate

`listCertificates` lists certificates held by the calling user's wallet, filtered by issuer and type and subject to applicable permissions. It is not a search of public certificates belonging to other users. Its encrypted core fields are not a disclosure of their plaintext. `proveCertificate` locates a particular stored certificate and returns a verifier-specific keyring for `fieldsToReveal` and `verifier`.

A conforming wallet MUST find an unambiguous stored certificate matching the request, enforce the selected field subset, and bind the keyring to the requested verifier. It MUST NOT reveal additional fields, substitute a verifier, or disclose merely because the requester knows the serial. The user may authorize a continuing grant through the wallet's normal permission system; a new modal for every previously authorized use is not required.

The recipient reconstructs the BRC-52 verifiable certificate using the unchanged core and returned keyring. It MUST verify the signature before relying on decrypted values, decrypt through the subject/verifier relationship, and reject a field whose authentication fails. Only the authenticated decrypted fields are assertions available to that recipient. A supplied `decryptedFields` cache is not independent evidence.

### 5.2 BRC-103 in an application journey

BRC-103 authenticates peer keys and carries certificate requests and responses. A request names acceptable certifier keys and maps each requested certificate type to the fields required. A response contains the signed cores and selected verifier keyrings. The certificate subject MUST match the authenticated peer whose attributes are being relied upon, unless an explicit delegation protocol authorizes a different relationship. The verifier MUST check that returned certificates and fields satisfy the actual request and its acceptance policy.

Discovery does not replace this handshake. An overlay result can identify the key Bob intends to contact; the peer at a subsequently obtained endpoint must still authenticate as that key. Conversely, a valid handshake need not include a real name or any certificate: two parties can communicate pseudonymously when the application permits it. Authentication is proof of key control, and certificate exchange supplies additional meaning where needed.

BRC-103 is transport-independent; BRC-104 binds it to HTTP. A known key does not itself create an IP route or a live connection. Peers can exchange endpoints directly, use independently authenticated service advertisements, or use a message relay such as a compatible messagebox. The chosen transport must actually deliver messages, and endpoint advertisements and server identities need their own validation. A relay transports messages; it need not become the authority that assigns the participants their identities.

The authentication protocol does not automatically encrypt arbitrary message payloads. Applications needing confidentiality MUST use the appropriate wallet encryption or encrypted messaging protocol in addition to authentication. They SHOULD request only the fields needed for the transaction and bind consequential requests, such as payment instructions or KYC disclosure, to the authenticated conversation.

## 6. Revocation, withdrawal, and current reliance

### 6.1 Certificate revocation

The certificate's signed `revocationOutpoint` identifies a UTXO governed by the issuer's revocation policy. Spending a non-sentinel revocation UTXO revokes the certificate under BRC-52. A conforming verifier MUST reject a certificate as current when it has authenticated evidence that this outpoint is spent. The all-zero transaction ID with index zero disables UTXO revocation; it does not mean that a revocation check succeeded.

The issuer MUST accurately describe who can spend this output and whether it is shared by multiple certificates. One shared outpoint permits group revocation. A reference to an arbitrary output, a missing transaction, or an output that was never validly created does not establish a usable revocation mechanism merely because its text is signed. The verifier may rely on the issuer for the correctness of that arrangement under its policy, or independently validate the referenced output; it MUST NOT describe an unperformed check as independent verification.

Transaction inclusion and script validation do not prove that an output remains unspent. Current revocation assessment requires a maintained spend index, an appropriate service, or the verifier's own tracking, with an explicit freshness and availability policy. The verifier MUST distinguish **revoked**, **not observed revoked under the stated current check**, **revocation disabled**, and **status unavailable**. Expiry and the continuing validity of external attributes remain separate even when the revocation output is unspent.

An application making a consequential decision MUST specify the status evidence and freshness it requires. If that evidence is unavailable, it MUST not turn the uncertainty into a claim of current certification. It may still permit an interaction on another basis, such as a personally verified contact or an explicitly accepted uncertified key. Historical messages need not disappear merely because a certificate later expires or is revoked.

### 6.2 Four different operations

| Operation                        | Controlled by                             | Effect                                                                                                     | Does not do                                                                                    |
| -------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Revoke a certificate             | Whoever controls its revocation authority | Changes whether the issuer's assertion may be relied upon as current                                       | Erase the certificate or previously disclosed plaintext                                        |
| Withdraw a public revelation     | Subject controlling the revelation output | Removes that revelation from participating current discovery indexes, with Section 8's withdrawal tracking | Revoke the underlying certificate or erase chain history                                       |
| `relinquishCertificate`          | Subject's wallet under its permissions    | Removes a locally held certificate from ordinary wallet use                                                | Spend the issuer's revocation output or necessarily withdraw public copies                     |
| Remove a contact or trust anchor | The user controlling that local policy    | Changes that user's future interpretation                                                                  | Change another user's trust, revoke an issuer's certificate, or reassign the other party's key |

Applications and wallets MUST label these operations according to their actual effect. A UI may offer a combined workflow, but it must track each result separately. Losing a local certificate before preserving the ability to withdraw its public revelation can complicate cleanup. A transaction submission failure must remain distinguishable from successful withdrawal.

## 7. Public attribute revelation

### 7.1 Making selected fields public

Public discovery is an explicit publication choice. The subject takes a valid held certificate, selects at least one field, and obtains a verifier keyring for the public key of private scalar `1`:

```
0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
```

This is the SDK's `"anyone"` participant. Anyone can construct its wallet and decrypt the selected revelation keys. Public keyring encryption is a conventional interoperable envelope, not confidentiality against the public. The subject MUST be told that publication makes those selected attributes publicly readable and linkable, rather than merely visible to the publishing application.

The published JSON contains exactly eight members: `type`, `serialNumber`, `subject`, `certifier`, `revocationOutpoint`, `fields`, `signature`, and `keyring`. `fields` remains the **complete encrypted** signed map; `keyring` is the nonempty publicly revealed subset. Do not include `masterKeyring`, local trust metadata, unselected field keys, private proof keyrings, or cached plaintext. No unsigned display label can replace a signed field.

The producer MUST verify the certificate, require the requested subset to exist, and check that the wallet returns exactly that subset before constructing the publication. The standard SDK publisher requests a one-satoshi output and disables output randomization so its new token is normally output zero. Readers and hosts MUST use the actual returned output index: zero is a producer convention, not a topic-wide requirement.

### 7.2 Subject-signed identity output

Let `J` be the exact UTF-8 bytes of the public JSON object. Derive the subject's locking/signing child using `[1, "identity"]`, key ID `"1"`, counterparty `"anyone"`, and the subject's own child (`forSelf: true`). Sign `J` using that child, through the normal SHA-256/ECDSA data-signature operation. Construct the canonical signed PushDrop output:

```
PUSH33(subjectDerivedPublicKey) OP_CHECKSIG
PUSH(J) PUSH(DER_subjectFieldSignature) OP_2DROP
```

There are exactly two pushed data fields after `OP_CHECKSIG`. Push lengths use the direct length opcode through 75 bytes, `OP_PUSHDATA1` for 76–255, `OP_PUSHDATA2` for 256–65535, and `OP_PUSHDATA4` above that, with the Bitcoin little-endian length operands. No extra prefix, trailing field, alternate drop sequence, or appended signature field is part of this envelope.

The subject signature authenticates the exact JSON bytes, including the keyring. It is distinct from the issuer's signature, which authenticates the canonical encrypted certificate core. JSON property ordering and insignificant whitespace can vary between publications, but changing them requires a new subject field signature. Readers MUST verify the original bytes rather than reserialize before checking that signature.

An anyone-root verifier derives the expected subject public child with protocol `[1, "identity"]`, key ID `"1"`, and counterparty equal to the certificate subject. It MUST check both equality to the script's locking key and the subject field signature, then independently verify the issuer signature and decrypt the public keyring. Possession of a signed certificate alone does not authorize publication as its subject.

### 7.3 Publication and spend lifecycle

The subject creates and signs the transaction through BRC-100 and submits its transaction evidence to `tm_identity` through the BRC-22/SHIP publication mechanisms. Blockchain transaction validity, overlay acceptance, and successful lookup are separate observations. The publisher SHOULD preserve the transaction, outpoint, selected fields, and publication outcome so interrupted operations can be reconciled without accidentally creating unnecessary duplicate revelations.

The locking key remains under the subject's control even though its public derivation uses `"anyone"`. To withdraw, authenticate the source output, construct a transaction spending that exact outpoint, sign its actual input position with the identity derivation, and submit the spend to the relevant overlay hosts. Standard PushDrop spending uses a transaction signature, distinct from the stored JSON field signature. Additional funding inputs and change outputs are permitted. A client MUST verify the intended source, destination outputs, and returned transaction before treating a wallet-created action as its authorized withdrawal.

The SDK withdrawal helper searches by certificate serial, then checks the subject, expected locking key, certificate signature, and field signature before selecting outputs to spend. Because serial queries can return other issuers' or subjects' records, matching a serial alone MUST NOT authorize a spend. Multiple independently published subsets may need separate withdrawal. A later new revelation can disclose a different subset or use fresh keyring envelopes; it does not retroactively erase an earlier disclosure.

## 8. The `tm_identity` topic

### 8.1 Admission

An overlay engine first applies its normal transaction-evidence, consensus, and spend-validation requirements. The identity Topic Manager then examines each output independently. One malformed output MUST NOT prevent acceptance of other valid identity outputs in the same transaction.

A conforming current-format validator MUST:

1. Parse the exact canonical script in Section 7.2, with exactly two data fields and a valid compressed locking public key.
2. Bound the JSON field to 1–262144 bytes and the field-signature encoding to 8–80 bytes, then require a valid DER signature. The size bound alone does not establish signature validity.
3. Decode strict UTF-8 and a JSON object with exactly the eight specified top-level members. Require `fields` and `keyring` to be data maps, each with 1–100 entries, and require every keyring name to exist in `fields`. Validate BRC-52 types, encodings, field names, keys, and signature structure. Producers MUST NOT emit duplicate JSON member names; readers SHOULD reject duplicate members rather than depend on parser overwrite behavior.
4. Derive the expected subject locking key and verify the subject signature over the original JSON bytes.
5. Verify the complete certificate signature under the claimed issuer.
6. Decrypt every public keyring entry through the anyone wallet and authenticate its corresponding field ciphertext. Require at least one successfully disclosed attribute, and reject the candidate if a supplied revelation fails authentication.

The standard producer uses one satoshi. The pinned topic predicate does not impose a separate satoshi amount or output-index restriction. Ordinary transaction rules still apply. Hosts MUST NOT treat an arbitrary client-selected protocol/key ID as the `tm_identity` format merely because it also produces a valid PushDrop script.

Admission does not consult a universal trusted-issuer list, reserve human-readable attributes, or require CertMap inclusion. Different certificates may disclose the same name, handle, or email value. The same subject may have several issuers, types, and public revelations. The topic validates attributable statements; it does not assign exclusive ownership of an attribute to the first claimant. A verifier's trust choices and application context resolve meaning later.

The pinned admission validator checks signatures and public decryptability. It does **not** establish current certificate revocation status, issuer truthfulness, or that an application should accept the assertion. An implementation claiming current-reliance validation must add Section 6's status checks. Merely constructing a `Certificate` and calling `verify()` is a signature check, not that complete decision.

### 8.2 Spends, retained history, and withdrawal identity

The Topic Manager returns the admissible output indexes and an empty `coinsToRetain` list. Thus a spent identity output is not retained as a current topic coin simply because a successor exists. A transaction that spends an identity token and creates no new admissible identity output is still a meaningful spend notification; zero new admissions does not cancel withdrawal.

Current lookup storage also remembers withdrawn **revelations**, separately from output indexing. A revelation is identified by this tuple:

```
(type, serialNumber, subject, certifier, revocationOutpoint,
 certificate signature, public keyring entries)
```

Keyring equality means the same field names and exact ciphertext strings, independently of object insertion order. These values, rather than a friendly name or serial alone, identify the same signed disclosure. A different field subset or freshly encrypted keyring is a different revelation. The issuer signature commits the encrypted fields; storage MUST derive the tuple from the authenticated certificate, not accept an unauthenticated caller's withdrawal claim.

In the pinned store, the local `revelationId` is lowercase hex SHA-256 of UTF-8 compact JSON containing the seven tuple elements in the displayed order, with the last element represented as sorted `[fieldName, keyringValue]` pairs. The implementation sorts names with `localeCompare`. This hash is an internal index key, not a new on-chain field or cross-host consensus identifier. Implementations with different internal encodings MUST preserve the tuple's equality semantics. When reproducing the pinned hash, preserve its ordering and JSON rules; Section 16 uses a single keyring field, avoiding locale-dependent ordering.

When an authenticated spend of a revelation output is observed, the lookup service MUST record the withdrawal and suppress current discovery of that same revelation, including equivalent stored copies. The service validates that the spend notification contains exactly one input spending the indicated outpoint, has the authenticated source output available, and that the source is a valid identity revelation. The overlay engine remains responsible for proving the transaction and spend are valid; matching an input's textual outpoint alone is not proof of a valid spend.

The index lifecycle is:

| Event                                                        | Required effect in the current withdrawal profile                                                                    |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Admit an output with no matching withdrawal                  | Upsert by transaction ID and output index; index only authenticated public attributes                                |
| Re-admit the same output                                     | Idempotent update, not another counted record                                                                        |
| Admit an already withdrawn revelation                        | Do not restore it to current lookup                                                                                  |
| Observe an authenticated spend                               | Persist the revelation withdrawal and remove/hide matching indexed revelations                                       |
| Spend races with admission                                   | The resulting index must suppress the withdrawn revelation                                                           |
| Process stops between recording withdrawal and deleting rows | Lookup must still exclude rows joined to the withdrawal record                                                       |
| Evict an output for a non-spend reason                       | Remove that outpoint's row; eviction alone is not a certificate revocation or an authenticated revelation withdrawal |

The reference store checks for withdrawal before and after its admission upsert. It uses a persistent withdrawal collection, backfills older matching rows with the revelation identifier, and excludes withdrawn identifiers in lookup before pagination. Equivalent implementations need not use MongoDB but MUST preserve these effects, including across restarts. Legacy services that only delete the spent outpoint do not provide this withdrawal profile.

Withdrawal tracking prevents a participating current index from treating the same previously withdrawn disclosure as newly current. It is not data erasure, cannot force unrelated hosts to retain withdrawal evidence, and does not prevent the subject from deliberately publishing a new revelation. Chain reorganizations and rejected/unconfirmed spends require the host's documented transaction-state reconciliation. The pinned withdrawal store has no independent reorganization rollback API; a host MUST NOT claim automatic reversal of an incorrectly finalized withdrawal merely because it reprocesses an admission.

### 8.3 Replication and availability

The topic identifies a common public format, not one required server. Hosts can exchange available transactions through the overlay synchronization mechanisms, including BRC-76 GASP where supported. The format does not introduce a global ordering authority or require all hosts to have observed every publication and spend simultaneously.

Under [BRC-183](/overlays/0183), hosts seeking broadly shared public coverage may operate a fairly strict deployment: most nodes should have most records most of the time. A host intentionally serving a selected collection must disclose that federated collection policy. In either case it MUST apply the advertised validation and withdrawal profile to the records it claims to serve. A result's absence from one host is not proof that the subject has no identity, no certificate, or no public revelation elsewhere.

Replicating admission data alone does not establish complete withdrawal knowledge. Hosts implementing the persistent withdrawal profile MUST preserve authenticated spend/source evidence and arrange its propagation or reconstruction; this specification does not invent a separate tombstone wire message. Clients SHOULD have configurable provider discovery and avoid requiring a single certifier-owned host as the only source of that certifier's public assertions.

## 9. The `ls_identity` lookup service

### 9.1 Question and response

Use a BRC-24 question with `service: "ls_identity"` and an object-valued `query`. The current bounded query schema is:

| Query member       | Form and bounds                                                                                 |
| ------------------ | ----------------------------------------------------------------------------------------------- |
| `identityKey`      | A valid 33-byte compressed public key expressed as 66 hex characters                            |
| `attributes`       | An object with 1–32 named string values; names 1–50 UTF-8 bytes, values at most 500 UTF-8 bytes |
| `certifiers`       | 1–32 distinct valid compressed public-key strings                                               |
| `certificateTypes` | 1–32 distinct canonical Base64 strings, each decoding to 32 bytes                               |
| `serialNumber`     | Canonical Base64 encoding of 32 bytes                                                           |
| `limit`            | Integer 1–100; default 10                                                                       |
| `offset`           | Integer 0–100000; default 0                                                                     |

Unknown top-level members, malformed supplied members, invalid arrays, and unsafe field selectors MUST be rejected. Attribute field selectors MUST be ordinary field names, not database operators or paths: reject dots, dollar-sign syntax, NUL, and unsafe object-property names. Producers should use the certificate schema's exact field spelling. The special query key `any` is reserved for the search described below; it is not a demand that a certificate contain a signed field called `any`.

The pinned validator accepts either case in public-key hex, while several storage comparisons are textual. Clients SHOULD send canonical lowercase keys and MUST NOT infer that an uppercase query is guaranteed identical behavior on every legacy host. An omitted optional filter is distinct from an empty array: the current schema rejects empty `certifiers` and `certificateTypes` arrays. A wallet with no selected certifiers should complete its empty-trust decision locally rather than send a malformed or unintentionally unfiltered query.

The lookup service returns an overlay lookup formula selecting transaction/output references. The overlay engine resolves this to the ordinary BRC-24 `output-list` answer containing each output's transaction evidence (`beef`) and `outputIndex`. The public contract is not a MongoDB record, pre-decrypted identity, or a host's trust score. An application using raw lookup MUST validate the returned evidence and certificate before relying on it.

For example, the following selects public certificates for the synthetic subject used in Section 16:

```json
{
  "service": "ls_identity",
  "query": {
    "identityKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
    "limit": 10,
    "offset": 0
  }
}
```

### 9.2 Selector precedence

After validating all supplied members, the current lookup dispatcher applies the first applicable row:

| Priority  | Condition                                     | Effective selection                                                        |
| --------- | --------------------------------------------- | -------------------------------------------------------------------------- |
| 1         | `serialNumber` supplied                       | Certificates with that serial; other selection filters are not applied     |
| 2         | `attributes` supplied                         | Attribute search, optionally restricted to `certifiers`                    |
| 3         | `identityKey` and `certificateTypes` supplied | That subject and one of those types, optionally restricted to `certifiers` |
| 4         | `identityKey` supplied                        | That subject, optionally restricted to `certifiers`                        |
| 5         | `certifiers` supplied                         | Certificates from one of those issuers                                     |
| Otherwise | No supported selector                         | Error                                                                      |

`certificateTypes` alone is not a supported query. Combining selectors does not imply an intersection of all of them. In particular, callers MUST NOT rely on a certifier filter accompanying a serial query being applied. Validate the returned subject, issuer, type, and intended serial at the client boundary. Clients SHOULD send only the members belonging to the intended branch.

Stored public attributes are decrypted, authenticated values from the supplied keyring, never all encrypted fields decoded as if they were plaintext. The index's internal substitution of decrypted values for its local `certificate.fields` is a database representation only; returning the source transaction restores the original signed certificate with encrypted `fields`.

### 9.3 Attribute matching and deployed compatibility profiles

Named attribute searches normalize query whitespace by trimming and collapsing runs of whitespace to one space. Empty normalized named values are ignored; if none remain, no records match. Multiple effective named attributes are ANDed **within the same certificate**. They are not a join of an email in one certificate and an employer name in another certificate for the same subject.

For ordinary named fields, the deployed fuzzy matcher escapes regular-expression metacharacters, splits the normalized query into space-separated tokens, and looks for those literal tokens in order, case-insensitively, with arbitrary intervening characters on the same line. This is ordered substring matching, not edit distance or a guarantee of exact equality. A query for `alice` may therefore match a longer value containing that string.

The `any` search operates over the publicly decrypted field values joined with spaces in their stored enumeration order, excluding the exact names `profilePhoto` and `icon`. It takes precedence over other members inside `attributes`. After query whitespace normalization, fewer than two UTF-16 code units returns no matches. Excluding those two conventional image fields does not make all other values safe to display or automatically exclude every possible URL field.

Two deployed profiles require explicit distinction:

| Behavior                                | Current TS Stack storage profile                                  | Corrected standalone identity-services profile                       |
| --------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------- |
| Named `userName`                        | Exact, case-sensitive equality to the whitespace-normalized query | Anchored, escaped, case-insensitive equality to the normalized query |
| Other named fields                      | Ordered literal substring matching                                | Same                                                                 |
| `any`, normalized length 2              | Ordered literal substring matching                                | Same                                                                 |
| `any`, normalized length greater than 2 | MongoDB default English full-text search                          | Ordered literal substring matching                                   |

The full-text branch uses tokenization and stemming, with default case/diacritic-insensitive text matching, ignored stopwords, quoted phrases, and negation. It is not an email-address equality test. The exact MongoDB behavior is part of this implementation profile, not an alternative certificate interpretation. See the [MongoDB `$text` definition](https://www.mongodb.com/docs/manual/reference/operator/query/text/) and the pinned sources in Section 17.

The pinned wallet's result binding conservatively implements complete English tokens, phrases, stopwords, and exclusions for the full-text branch, but not all server-side stemming or custom index languages. A stem-only result can be discarded. That wallet does not automatically adopt a standalone host's substring profile. A producer or consumer MUST declare which profile it implements and MUST NOT present fuzzy or full-text search as an exact identity binding. No query-field negotiation between these profiles currently exists. A future standardized search-mode extension requires explicit coordination rather than adding undocumented members to `ls_identity`.

For a payment destination or other exact-attribute decision, use search to discover candidates, then compare the authenticated value under the certificate type's explicit normalization rules and let the user select the intended subject. Email domains and mailbox local parts, international telephone numbers, and platform handles have different normalization policies; a universal lowercase-and-strip-punctuation rule MUST NOT be invented by a resolver.

### 9.4 Pagination, duplicates, and freshness

The pinned service excludes withdrawn revelations before applying offset and limit. It does not specify a stable sort, a relevance order, a first-claim order, or a snapshot cursor. Therefore, the first row has no special identity authority, and offset pages can shift as data or query plans change. `limit` bounds returned outputs, not the number of unique people.

Multiple outputs may carry the same certificate or different public subsets of it. Clients SHOULD deduplicate authenticated outpoints and retain enough certificate/revelation provenance to interpret repeated records. A host may be missing newer publications or withdrawals. An `output-list` is evidence to validate, not a proof of global completeness or current unspentness. Timeouts, invalid responses, resource-limit failures, and an empty successful answer MUST remain distinguishable in the caller's operational state, even where a legacy UI ultimately displays no matches.

## 10. BRC-100 discovery as an application capability

### 10.1 The two directions

`discoverByIdentityKey` answers: **What publicly revealed, locally accepted attributes are associated with this key?** An application already has a key from a payment, authenticated message, document author, or saved relationship and wants intelligible identity information.

`discoverByAttributes` answers: **Which subjects have publicly revealed certificates matching these attributes under my trust choices?** An application starts with a familiar attribute and needs candidate identity keys. This is what enables a recipient picker based on an existing email, phone number, or social handle without creating another application username.

Both calls concern discovery of public assertions about others, rather than disclosing the caller's private certificates. Public queries can still reveal the caller's interests to providers and can disclose a user's locally saved contacts if contacts participate. The wallet's normal permission model therefore remains relevant.

```typescript
const candidates = await wallet.discoverByAttributes({
  attributes: { email: 'alice@example.invalid' },
  limit: 10,
  offset: 0,
  seekPermission: true
})

const identity = await wallet.discoverByIdentityKey({
  identityKey: selectedSubjectKey,
  seekPermission: true
})
```

The first call is candidate discovery, not automatic selection of `candidates.certificates[0]`. The second can enrich an already selected key with other public assertions. Those assertions remain individually attributed to their issuers and subject to application relevance checks.

### 10.2 Arguments, results, and wire identity

The public BRC-100 argument shapes are:

| Call                    | Required argument                       | Optional arguments                  |
| ----------------------- | --------------------------------------- | ----------------------------------- |
| `discoverByIdentityKey` | `identityKey`                           | `limit`, `offset`, `seekPermission` |
| `discoverByAttributes`  | `attributes: Record<fieldName, string>` | `limit`, `offset`, `seekPermission` |

The wallet-level default page size is 10, with a maximum requested limit of 10000 and nonnegative offset. This is not the same bound as one overlay host's 100-output maximum. The wallet must coordinate its own acquisition, validation, deduplication, trust calculation, and pagination. If it queries more than 32 selected issuers, it must partition the issuer filter into valid host-sized requests and combine authenticated evidence before applying the intended trust policy; it must not silently discard selected anchors. A conforming implementation MUST honor the caller's limit and offset for its declared result collection, rather than merely insert them in a cache key.

Each normal certificate result contains the BRC-52 core and:

| Additional member                              | Meaning                                                      |
| ---------------------------------------------- | ------------------------------------------------------------ |
| `publiclyRevealedKeyring`                      | The subject's authenticated public-verifier keyring          |
| `decryptedFields`                              | Authenticated plaintext corresponding to that keyring        |
| `certifierInfo.name`, `iconUrl`, `description` | Locally selected descriptive issuer information              |
| `certifierInfo.trust`                          | That user's assigned issuer weight, normally an integer 1–10 |

The outer response contains `certificates` and `totalCertificates`. The count MUST be interpreted within the implementation's stated collection and pagination semantics; it is not a count of globally existing identities or a completeness proof. The pinned trust transformer returns the length of its accepted result array. Several certificates may name one subject.

The BRC-100 binary call codes remain `21` and `22`. Code 21 carries the 33-byte identity key; code 22 carries the length-prefixed attribute string map. Both carry limit, offset, and the optional permission flag using BRC-100 encodings. The response carries signed certificate data, issuer display information and trust, the public keyring, and plaintext fields. Implementers MUST use those defined encodings rather than invent a new discovery response because a UI wants a name and image.

`forceRefresh` is an in-process Wallet Toolbox extension, not a field added to BRC-100 binary methods by this specification. Likewise, SDK `useContacts`, `overrideWithContacts`, and `parallel` options belong to the higher-level resolution helper, not the two standard wallet argument objects. They require explicit adapter support when crossing a wallet transport.

### 10.3 Validation pipeline

Before returning an overlay-derived identity as accepted, a conforming wallet MUST:

1. Apply the user's current selected issuer policy and appropriate request permission.
2. Acquire bounded transaction/output evidence from the configured providers, preserving the distinction between received data and verified data.
3. Validate transaction evidence in the wallet's network and chain context, and authenticate the exact output, subject-bound script, publication signature, issuer signature, and public field decryptions.
4. Rebind the verified result to the query: the requested subject for a key query, or the declared attribute matching profile for an attribute query. A valid unrelated certificate is not a valid answer.
5. Remove duplicate contributions before calculating trust, apply Section 11's selected policy, and preserve source attribution.
6. Apply the promised pagination and return the actual checked values. Perform the current-reliance/status checks required for any claim of current certification.

The current wallet verifies transaction evidence with an independently supplied chain tracker and treats its overlay-response cache as untrusted evidence requiring revalidation. Signature validity and chain inclusion still do not establish current revelation unspentness or certificate non-revocation. A wallet MUST NOT label those additional checks as completed unless it actually performed them under a stated policy.

Cache keys and invalidation MUST respect network, wallet/profile, query, selected trust policy, and relevant source context. Changing the user's issuers or threshold MUST affect subsequent decisions, including cached results. Rendering caches in an application are not perpetual trust grants. Late responses for an old query, profile, or trust configuration MUST NOT overwrite a more recent selection.

### 10.4 The wallet serves the user's trust choices

A wallet implementing this profile MUST provide a practical way for its user to add, remove, and change valid trust anchors and local identity associations. It MUST NOT require a vendor partnership, issuer registration with the wallet company, a particular certifier's certificate, or inclusion in a descriptive registry before honoring an otherwise supported user-selected anchor. Importing an anchor from a known HTTPS domain is a convenience, not a prerequisite to entering or independently authenticating its key.

The wallet MUST carry out an authorized supported discovery or disclosure operation according to those choices. It MUST NOT replace a user's selected issuer with the wallet vendor's preferred issuer, suppress a personally accepted contact merely because an overlay has no certificate for it, or make an unavailable metadata service a prerequisite to identity use. Honest errors for invalid data, unavailable evidence, unsupported formats, or actual user denial remain necessary; silently overriding the user's trust policy is not an error-handling strategy.

Applications may have their own disclosed requirements for a specific operation—for example an employer may require its own employment credential, and a verifier may require a particular qualification. These are claims about the application's transaction policy, not authority for a wallet to prohibit the user's general identity relationships. An application SHOULD state the missing assertion and acceptable evidence rather than label the entire person invalid.

`seekPermission` controls whether the wallet may seek a required grant; it does not create one. Callers SHOULD set it explicitly because published documentation and current SDK defaults differ. A conforming adapter MUST honor explicit `false` by returning the appropriate permission error if a grant is required, and explicit `true` by using the normal user-choice path. The pinned permission wrapper's handling is characterized in Section 17. A wallet may reuse valid prior grants; repeated prompts must not become an artificial barrier to a user-authorized capability.

## 11. User-selected trust and identity interpretation

### 11.1 Appointing a trust anchor

A trust anchor here is a certifier public key deliberately selected by the relying user, with its local description and policy. Trust is not established by a high search position, a public key's age, a registry listing, or an issuer naming itself trustworthy. The user can recognize an organization, inspect its verification policy, obtain its key through an independent channel, or import its suggested details using [BRC-68](/peer-to-peer/0068).

BRC-68 publishes `metanet.trust` in an HTTPS `/manifest.json`, with `name`, `note`, `icon`, and `publicKey`. Legacy `babbage.trust` may be accepted for migration. Fetching that file obtains a domain-attributed proposal for an anchor; selecting it is the user's decision. It MUST NOT silently install future replacement keys. Direct key entry and personal contacts keep a domain or manifest from becoming a mandatory identity authority.

Certifier trust, registry-publisher preference, overlay-provider selection, and permission to a particular application are different relationships. Some wallet settings currently share an entity list across several of them. Implementations MUST disclose that coupling and MUST NOT infer one kind of authority solely from another. The user should be able to understand why an assertion was accepted and change the relevant relationship.

[BRC-207's attested-admission profile](/wallet/0207#attested-admission-and-offline-token-graphs) explicitly reuses the selected entity identities for token admission when the user enables that policy with the coupling disclosed. Its per-anchor, common-signer and unanimity rules use membership, not the certificate weights below. Its asset-declared fixed-identity mode instead uses that identity without a list membership test. Those rules do not alter certificate lookup or its thresholds.

### 11.2 Deployed weighted policy

The Wallet Toolbox trust profile has `trustLevel`, a positive integer threshold, and `trustedCertifiers`, each with a distinct `identityKey`, descriptive metadata, and integer `trust` from 1 through 10. Current settings validation permits at most 256 issuers and thresholds from 1 through 2560. The numbers express the user's policy, not probabilities, global reputations, or universally comparable assurance levels.

For the verified, query-matching certificate set available to that lookup:

1. Ignore certificates whose issuer is absent from the selected list.
2. Group by subject identity key.
3. Within each subject, deduplicate certificates by `(type, serialNumber, certifier)`.
4. Sum the selected weights of **distinct contributing issuer keys**, once per issuer for that subject.
5. Keep the subject's eligible certificates if that sum is at least `trustLevel`.
6. Attach issuer metadata from the user's settings and order the retained certificates by issuer weight descending. Equal-weight ordering is not an identity-authority rule.

Two certificates or ten copied outputs from one issuer do not supply two independent trust contributions. Separate keys operated by the same organization are not necessarily independent authorities either; selecting and weighting them is the user's responsibility. The calculation uses the evidence actually available for the query, so a limited or incomplete result can fail to accumulate sufficient weight even when further certificates exist elsewhere.

For example, Bob gives a professional association weight 4, an employer weight 3, and a social-account verifier weight 2, with threshold 5. An available association certificate plus an employer certificate for Alice contributes 7; ten association certificates alone still contribute 4. Removing the employer from Bob's selected list immediately changes that result. Carol may choose a different policy and receive a different accepted result for the same public evidence.

This threshold qualifies a subject's discovery result under that policy. It does **not** mean that every issuer jointly attested every field, or that their certificates agree. If one issuer says an email is Alice's and another certifies a professional qualification, the second issuer did not thereby verify the email. An application MUST retain per-certificate attribution and evaluate the particular claim needed for its task. It must not flatten a collection into an unattributed “verified person” object.

The pinned transformer processes at most 256 supplied certificate entries and retains the first encountered certificate for a repeated issuer/type/serial. Separate revelations of the same core can expose different subsets, so that behavior does not promise the union of all public fields. A client combining compatible revelations MUST authenticate each contributing publication and preserve the unchanged certificate core and the provenance of each revealed field. Conflicting signed cores under one logical certificate identifier require explicit handling, not silent merging.

### 11.3 Choice, uncertainty, and personal authority

An unknown identity is a key for which the current lookup and policy did not supply an accepted public description. It is not proof that the key has no legitimate holder. Users can still authenticate a known key, obtain a certificate privately, or make their own contact assertion.

A user may directly decide, “This is Alice's key; I know her as Alice from the workshop, and this is the photo I use.” That decision needs no third-party issuer's permission. In that user's context, the saved association can take precedence over external descriptions. Its local authority is positive and intentional; it is not a defective certificate awaiting vendor approval. Section 12 specifies its persistence and presentation boundaries.

When two accepted subjects share an attribute, the wallet/application MUST preserve the ambiguity rather than choose the first or highest-ranked record as the unique owner. Helpful resolution includes other certified attributes, an existing contact, a directly checked key, and a fresh authenticated conversation. The relevant question is which key the user intends to interact with, not which vendor owns a global spelling.

## 12. Contacts and direct personal assertions

### 12.1 Meaning and consent

A contact associates an identity key with a user's own name, image, and optional contextual notes. Saving it is a personal trust action, analogous to installing a local trust anchor. The user can make the association after meeting someone, checking a key over an independent channel, inspecting an authenticated exchange, or knowingly accepting information supplied by the person. A wallet MUST allow that personal decision without demanding a third-party certificate first.

The contact author can describe their own key or another party's key. A record saved by Bob saying “this key is Alice” is Bob's local assertion, not an assertion signed by Alice merely because she is the contact's subject. Alice can separately issue a self-signed BRC-52 certificate or send a signed introduction; those are distinct objects with their own signatures and reliance rules. An automatic import of a network result MUST NOT become an authoritative contact without the user's or an appropriately authorized application's deliberate acceptance.

The contact's storage authentication proves that the wallet stored those bytes. Its identity authority comes from the saving user's decision. A photo, name, or badge can be personally selected and can override an overlay's display for that user. Applications MUST preserve this local attribution when exporting or showing the result to another user, rather than inventing a third-party certification claim.

### 12.2 Encrypted contact record

The SDK ContactsManager stores a one-satoshi signed PushDrop output in the BRC-46 basket `contacts`. Its plaintext is UTF-8 JSON with these required string members and optional JSON metadata:

| Member           | Meaning and current writer bounds                                                                                      |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `name`           | User's display name for this key; nonempty, at most 500 UTF-8 bytes                                                    |
| `avatarURL`      | Image reference; may be empty, at most 2048 bytes                                                                      |
| `abbreviatedKey` | Display abbreviation; may be empty, at most 256 bytes; never used instead of the full key for cryptography             |
| `identityKey`    | Canonical lowercase compressed secp256k1 public key                                                                    |
| `badgeIconURL`   | Optional display image reference represented as a string, at most 2048 bytes                                           |
| `badgeLabel`     | Local descriptive label, at most 1000 bytes                                                                            |
| `badgeClickURL`  | Empty or credential-free HTTPS navigation URL, at most 2048 bytes; local-development HTTP is a compatibility exception |
| `metadata`       | Optional bounded JSON object, such as personal notes or an independently obtained provenance description               |

Unknown top-level contact members are rejected by the current writer. Metadata must be JSON data, with finite numbers and no accessors or unsafe object keys; the current limits are depth 10, 2000 nodes, 200 keys per object, 1000 entries per array, and 32 KiB of encoded metadata. The complete plaintext is at most 64 KiB. These are current storage-client bounds, not a requirement to reveal personal notes to applications or put them in certificate metadata. Resource references MUST be treated as untrusted content even after contact decryption.

On creation, generate 32 random bytes and use their canonical Base64 string as the contact record's `keyID`. Encrypt the complete plaintext through BRC-100 using `[2, "contact"]`, that key ID, and counterparty `"self"`. The same tuple, key ID, and self counterparty derive the signing and locking child for:

```
PUSH33(ownerDerivedPublicKey) OP_CHECKSIG
PUSH(encryptedContactBytes) PUSH(DER_ciphertextSignature) OP_2DROP
```

The encrypted bytes use the BRC-2 AES-GCM envelope; the data signature covers the exact ciphertext envelope. Both derivations use the saving wallet's root/profile, not the contact's identity private key. A reader MUST check the expected locking key and data signature before decrypting and accepting the local record. A different layout or unauthenticated JSON in custom instructions is not a valid contact body.

The wallet output carries `customInstructions` containing exactly the JSON object `{ "keyID": "<32-byte Base64 value>" }`. This key ID is wallet tracking metadata, not another field in the on-chain PushDrop payload. Backup and migration MUST preserve it, the basket association, and the owner's appropriate key context; the public chain by itself does not reveal a missing random derivation key ID.

For private lookup indexing, compute a BRC-100 HMAC-SHA256 using `[2, "contact"]`, key ID equal to the contact's canonical identity-key **hex string**, counterparty `"self"`, and data equal to the UTF-8 bytes of that same hex string. Store the tag:

```
identityKey <lowercase hex of the 32-byte HMAC>
```

The tag is not a public SHA-256 of the key and is not the record's random encryption key ID. It helps locate a candidate output; a reader MUST still decrypt/authenticate that output and check the full stored `identityKey`. Tags, basket names, and custom instructions are wallet metadata rather than a public identity overlay index.

### 12.3 Create, read, update, remove

Creation stores the encrypted output, tag, basket, and custom instructions together. Reads request locking scripts and custom instructions from `contacts`, verify the canonical signed envelope and expected key, authenticate the encryption, parse bounded UTF-8 JSON, and validate the contact object. Malformed records must not acquire authority merely because they are in the named basket.

An update finds the authenticated output for the key, spends it, and creates a replacement contact output with updated encrypted data. The pinned implementation reuses that record's random key ID while generating a fresh encryption IV. It refuses an ambiguous multi-output update rather than choose arbitrarily. A conforming writer MUST bind the actual input to the intended old outpoint and check that the signed transaction preserves the authorized outputs. The full identity key, not its display abbreviation or label, selects the association.

Removal spends the authenticated contact output without creating its replacement. It changes local trust/presentation, not the subject's identity, certificates, or public revelations. The current removal helper stops after one matching output; callers reconciling duplicates must not assume that this proves all historical or concurrently created records are gone. Failed spends MUST NOT be reported as successful persisted removal.

The current reader loads at most 1000 contact outputs and caches a decrypted snapshot. An empty-basket cache and concurrent-load coalescing improve ordinary application responsiveness. Save/remove operations invalidate or update that cache; an explicit force refresh reloads it. A cached result is not a promise of completeness for a larger basket or of instantaneous cross-device synchronization. Implementers supporting larger address books must paginate and reconcile deliberately.

Encrypted contact persistence can reveal that outputs were created, updated, and spent. Wallet output descriptions and other storage metadata may also contain labels. Implementers SHOULD protect that metadata and avoid treating encryption of the contact body as privacy for every associated record or access pattern.

### 12.4 Resolution precedence and transport boundaries

The current SDK `IdentityClient` has contacts disabled by default. Passing `{ useContacts: true }`, the legacy boolean `true`, or `overrideWithContacts: true` opts into the user's personal associations. The legacy `overrideWithContacts` option takes precedence if both flags are supplied.

For identity-key resolution with contacts enabled, a local hit can return immediately and avoid the overlay query. With `parallel: true`, both sources are queried and a contact wins on a matching key. For attribute resolution, the sequential helper first matches the saved contact's `name` and `identityKey`, case-insensitively with equality; it does not treat arbitrary metadata or an `any` query as a contact search index. A local match can short-circuit. Otherwise it obtains overlay results and substitutes saved contact display data for matching subject keys. The parallel attribute path performs that substitution over the overlay result set rather than unioning all local contacts into the search results. These are helper behaviors, not general promises that every contact field is searchable.

Wallet Toolbox can separately accept an explicitly installed `ContactSource` before overlay discovery. Its in-process adapter synthesizes a result with a default `contact` type, empty certificate-proof fields, and a local priority that may be represented as `Infinity`. This expresses a local policy decision; it is **not a valid BRC-52 certificate or BRC-100 binary certificate result**. The result cannot be made portable by casting it to the certificate interface or serializing infinity into an unsigned byte.

A portable implementation MUST preserve the distinction. It can use ContactsManager's existing basket/encryption operations and merge local contact display records at the application/helper layer. A wallet transport wishing to carry a new explicit local-assertion result needs a separately specified extension; this specification does not silently add one to call codes 21 and 22. Until then it MUST NOT invent issuer signatures, type IDs, or serials merely to squeeze a contact into the certificate wire format. This boundary limits one representation, not the user's ability to appoint personal trust or an application's ability to honor it.

## 13. Application journeys

### 13.1 From a familiar attribute to payment and a continuing relationship

Alice wants people who already know her email address to find her wallet identity. A SocialCert-style issuer authenticates Alice's subject key, verifies access to the mailbox under its published policy, and issues an email-control certificate. Alice acquires and retains the certificate and master keyring in her wallet. Acquisition alone does not publish her address.

Alice chooses to make the email field public. Her wallet produces an anyone-verifier keyring for that field and creates the subject-signed identity output. The overlay validates the output and indexes the decrypted email with the authenticated certificate. The issuer need not operate the only overlay, and Alice need not create a separate username at Bob's payment application.

Bob recognizes and selects that issuer's key in his wallet's trust settings. His payment application can now request:

```javascript
const result = await wallet.discoverByAttributes({
  attributes: { email: 'alice@example.invalid' },
  limit: 10,
  offset: 0,
  seekPermission: true
})
```

This call is an illustration, not an assertion that the synthetic address exists. `seekPermission` is explicit because of the compatibility difference in Section 10. An application can make the corresponding call through the BRC-100 transport; it need not know which overlay host the wallet uses or build its own certifier allowlist. The result carries the subject key, signed encrypted certificate, public keyring, decrypted fields, and the wallet's attributed certifier information.

The application checks the returned field and its meaning, shows the full intended association and issuer attribution, and lets Bob resolve ambiguity. Search matching can be fuzzy; a search hit is not proof of exact mailbox equality. The application MUST compare the desired attribute using its type's documented comparison rules before presenting a result as an exact match. If several subjects qualify, it MUST NOT select a payment recipient solely because it is the first lookup result. It can ask Bob to distinguish them, use a saved contact, or obtain an independent confirmation.

Once Bob has selected Alice's identity key, the parties can authenticate directly through BRC-103 and use BRC-100 encryption or signing operations with each other as counterparties. An identity key is a cryptographic destination, not a routable socket address. Their application must obtain a usable endpoint or message-delivery route separately, such as an independently exchanged endpoint or a message-box service. Any relay or endpoint MUST be bound to the intended peer through authentication; control of a convenient server name must not silently replace Alice's key.

Suppose the payment requires additional customer information. Bob's application can request the relevant certificate type and selected fields over the authenticated session. Alice's wallet presents the requested disclosure and verifier identity to Alice and produces the verifier-specific keyring after authorization. Bob verifies the certificate, session binding, issuer policy, relevant fields, and current revocation status. Alice's public email listing has not authorized the disclosure of her address, date of birth, or other private information. A jurisdiction's requirements for accepting customer information are outside this protocol; the mechanism makes appropriately certified information exchange possible.

For payment, the parties use the appropriate payment protocol, for example [BRC-29](/payments/0029). They agree the amount and the derivation information needed by the recipient, derive the destination using the selected identity keys and BRC-29 parameters, create the transaction, and deliver its BEEF and payment metadata so the recipient can `internalizeAction`. Discovery is not a complete payment-delivery protocol. The application MUST bind the displayed recipient, selected identity key, payment derivation, and transaction approval to the same intended party.

Finally, Bob saves Alice as a contact after confirming the association. He may use a personal name and photograph meaningful to him. Later applications can resolve the same key through that saved association without asking SocialCert or its overlay for permission. Removing the certifier from Bob's trust settings need not erase his independently established contact. Conversely, saving a contact does not make every past or future claim from that key true.

### 13.2 Reverse resolution in transaction history and messaging

A wallet or application already holding Alice's identity key can call:

```javascript
const result = await wallet.discoverByIdentityKey({
  identityKey: aliceIdentityKey,
  limit: 10,
  offset: 0,
  seekPermission: true
})
```

It can show the returned certified name or profile information beside a payment or message. A contacts-aware helper can substitute Bob's own saved label and photograph. The application SHOULD make it possible to inspect the underlying key and whether the display came from a contact or a particular certificate. A pleasant display name must not obscure that two similarly named counterparties have different keys.

This allows a new application to inherit useful identity context from the user's wallet without importing the user's entire address book into the application's server. Discovery permissions and contact-basket permissions still govern what that application can request. Applications SHOULD avoid public lookups when an adequate local association is available, especially where a lookup would reveal an otherwise private relationship.

### 13.3 Personal recognition without a certifier

Bob meets Alice in person. They exchange identity keys through their chosen authenticated or independently checked channel. Bob saves Alice's key, his own label, and a photograph in contacts. His wallet authenticates and encrypts that association under Bob's own keys. No external certifier, registry record, public revelation, or common naming platform is needed.

When Alice subsequently messages Bob, proof that the peer controls the saved key lets Bob apply his own association. The wallet MUST permit this use of Bob's personal authority. It must not require a vendor-selected issuer to validate his choice. An application that needs a separately certified qualification may still ask for that qualification, while preserving Bob's contact as his personal identification of the counterparty.

### 13.4 Qualifications, age assertions, and referrals

A professional directory can discover people by a publicly certified qualification, then request a more detailed private certificate from the selected peer. An employer can certify a role or organization membership, with a defined validity period and revocation policy. A service can ask for a certified age predicate without requesting a name or birth date. These types need explicit schemas; neither a generic trust score nor an email certificate supplies the missing qualification.

A referral application can address and authenticate a referred party by key, display public or locally recognized identity information, and negotiate the service directly. A document-sharing application can encrypt for a selected counterpart using the relevant BRC-100 protocol and key ID. Identity resolution supplies the counterparty; the service's own transaction, authorization, and encryption protocols determine what happens next. These applications reuse the identity machinery instead of each inventing an issuer directory, profile database, or key-to-name authority.

### 13.5 Social attributes and continuity

Email, telephone, X, and Discord examples illustrate different ways an issuer can verify an existing relationship. The pinned SocialCert backend actively configures email, X, and Discord types. A telephone type remains defined in its source and client conventions but is disabled in that backend configuration. This specification describes the telephone schema for compatibility; it does not assert that the cited deployment currently offers telephone issuance.

The platform or carrier still controls its underlying account, handle, mailbox, or number. An issuer's evidence may become stale after reassignment, account loss, or policy changes. A durable application MUST distinguish a claim about verification at issuance from continuing control. Renewal, expiry fields, revocation, and fresh interaction can address different parts of that problem; none can be inferred from an attractive badge.

Already obtained certificates and subject-controlled public revelations can remain independently verifiable and retrievable when an issuer website disappears, subject to data availability and the chosen validity policy. A saved personal contact can continue to identify a known key even after a social handle changes. This preserves relationships without claiming to preserve control of a defunct platform account. If the identity private key changes, use an explicit continuity procedure rather than automatically transferring trust to whoever next obtains the same handle.

## 14. Relationship to Paymail and BRC-169

Paymail makes human-readable addresses useful for discovering capabilities, obtaining payment destinations, and delivering transactions. Its address namespace and capability discovery depend on the address's domain and the services that domain advertises. Self-hosting and multiple providers are possible; it is not a single global operator. Nevertheless, a user of a provider-controlled domain relies on that provider for that name's resolution and continuity. [BRC-28](/payments/0028) describes Paymail's payment role.

[BRC-169](/peer-to-peer/0169) defines human-readable ecosystem handles and certified resolution. It already uses existing certificate and trust mechanisms in parts of its design. Its ecosystem domain remains the authority for allocation and resolution within that handle namespace, with associated directory and delivery information. That is a meaningful choice when an application specifically needs an authoritative domain namespace. It should not be confused with a universal prerequisite for finding people, learning their keys, or paying them.

This specification permits an alternative application architecture: discover a subject-controlled public assertion under a user's selected issuer trust, authenticate the selected key, and interact using that key. There is no mandatory new ecosystem handle, global directory, wallet-provider account, or single overlay host. Multiple issuers can attest existing attributes, multiple hosts can serve authentic public records, and users can preserve direct associations in contacts. Applications that only need counterparty identification and subsequent interaction SHOULD first use these existing capabilities rather than require another naming system.

The distinction is where authority lies. A domain resolves its own names. A certifier signs an attributable claim. The subject chooses publication. The relying user chooses issuer trust and personal associations. An overlay supplies available evidence. Neither design abolishes dependencies: public records still need hosting and synchronization, certified claims need suitable issuers, and network interaction needs a delivery route. The distributed approach makes these roles independently replaceable and keeps prior cryptographic relationships portable.

An application MAY retain Paymail or an ecosystem handle as an input or compatibility feature. It MUST disclose the additional namespace authority and MUST NOT treat success in one namespace as blanket acceptance of unrelated claims. If a discovered certificate names a familiar email or social handle, its assurance comes from the chosen issuer's claim and policy, not from resemblance to an address accepted by a different payment system.

## 15. BRC-184 registration requests

### 15.1 Scope and submission

This section is a protocol, basket, and certificate-type registration request under BRC-184. The accompanying BRC pull request is the public review thread. The initial requested publisher is **Metanet Trust Services**, separately on **mainnet and testnet**, through the [registry submission process](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md), with **@ty-everett** tagged in the request. The maintainer is Ty Everett, `ty@projectbabbage.com`; ordinary discussion belongs in the public thread, not urgent email.

Publication remains a separate publisher action. Until an operator records its publisher public key, network, actual current record outpoints, and outcome in the review thread, these are requested descriptions, not claims of on-chain registration. Before publishing, the operator MUST inspect existing records for the exact identifiers and correct or update its existing descriptions where appropriate rather than imply the established identifiers are newly allocated. A record is scoped by network, publisher, registry, and exact identifier. Other publishers may independently describe the same identifiers.

For all requested records, `documentationURL` is the public URL of this complete specification at the reviewed Git commit, without a URL fragment. `iconURL` is the corresponding raw-file URL for [the accompanying identity icon](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/media/0189-identity.svg), an original SVG dedicated to the public domain under CC0-1.0. The submission thread supplies the resolved immutable URLs. The icon carries no issuer badge or endorsement. For CertMap field descriptors below, `fieldIcon` uses that same icon URL. The publisher should preserve a usable licensed copy if hosting changes.

The author and initial requested operator contact overlap. That relationship MUST be disclosed in the review; an uninvolved reviewer should be sought where practicable, without pretending one has approved the request. Corrections and disputes follow BRC-184 and retain the exact identifier, source evidence, reason, and affected record history. Metadata remains optional presentation, not wallet permission, issuer trust, or identifier exclusivity.

### 15.2 ProtoMap

Each row supplies an exact `[securityLevel, protocol]`, requested `name`, and requested `description`. Key IDs and counterparties are part of the protocol definition, not fields to concatenate into the registry's protocol string.

| Exact protocol tuple                  | Name                              | Description                                                                                                                  | Key IDs and counterparties                                                                                              |
| ------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `[2, "certificate signature"]`        | Certificate signatures            | Signs or verifies the encrypted BRC-52 certificate core under its certifier's derived key.                                   | `type + " " + serialNumber`; publicly verifiable issuer/anyone derivation                                               |
| `[2, "certificate field encryption"]` | Certificate field revelation keys | Protects per-field revelation keys for certificate acquisition and selective disclosure.                                     | Field name for master keys; `serialNumber + " " + fieldName` for verifier keys; subject and issuer/revealer or verifier |
| `[2, "certificate issuance"]`         | Certificate issuance serials      | Authenticates the serial binding for the interactive certificate issuance exchange.                                          | `serverNonce + clientNonce`; issuer and subject; HMAC over decoded `clientNonce + serverNonce`                          |
| `[2, "auth message signature"]`       | Authenticated peer messages       | Authenticates BRC-103 handshake, certificate-exchange, and application messages under the communicating peers' derived keys. | BRC-103 message/session nonce pairs, joined by one space in the order specified for the message; peer counterparty      |
| `[2, "server hmac"]`                  | Peer challenge authentication     | Authenticates nonce bytes used in peer challenge and certificate issuance exchanges.                                         | UTF-8 decoding of the 16 nonce bytes; the particular peer, or self where the calling protocol specifies it              |
| `[1, "identity"]`                     | Public identity revelation        | Signs and controls a public output revealing selected certified identity fields.                                             | `"1"`; subject with `"anyone"`; public-verification semantics                                                           |
| `[2, "contact"]`                      | Personal contacts                 | Encrypts and authenticates personal key-to-identity associations and derives private contact lookup tags.                    | Random 32-byte Base64 record key ID; or canonical subject hex for tag HMAC; `"self"`                                    |
| `[1, "identity key retrieval"]`       | Identity key access permission    | Names the wallet permission scope for returning the user's identity public key to an application.                            | Permission-scoped identity with self; returns the identity key rather than a child of this descriptive permission tuple |
| `[1, "identity resolution"]`          | Identity discovery permission     | Names the wallet permission scope for discovering public certified identities under the user's trust choices.                | Permission-scoped identity with self; no additional public payload or identity certificate format                       |
| `[1, "certificate list"]`             | Certificate listing permission    | Names the wallet permission scope for listing stored certificates for an application.                                        | Permission-scoped identity with self; not a certificate-signature or field-encryption key                               |

These registrations distinguish level 1 from level 2. In particular, public identity is `[1, "identity"]`, while contact persistence is `[2, "contact"]`. The permission tuples describe existing wallet authorization namespaces, not a grant to applications and not a new cryptographic use of an unspecified key ID. Requesting their metadata does not require an application to invoke a dummy signing or encryption operation.

The pinned permission manager also constructs certificate acquisition and relinquishment scope labels by appending a certificate type to `certificate acquisition` and `certificate relinquishment` . These are parameterized permission labels. A Base64 type can contain characters outside BRC-43's protocol-name grammar, so the literal construction cannot be assumed to be a portable cryptographic protocol identifier. This document does not invent a normalization or request a misleading wildcard ProtoMap record for either family. A future canonical mapping needs its own precise specification and migration. Per-verifier certificate disclosure permissions are likewise not a single new literal tuple to fabricate for registration.

`tm_identity` and `ls_identity` are overlay topic and service names, not BRC-43 wallet protocol tuples. They are specified here but MUST NOT be published as invented ProtoMap tuples merely to obtain descriptions. The registered authentication signature tuple retains BRC-103's message-specific nonce and payload rules. It is not a blanket encryption protocol, and this request does not allocate a new authentication format.

### 15.3 BasketMap

| Exact basket | Name              | Description                                                                                                                                                            |
| ------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contacts`   | Personal contacts | Encrypted, owner-authenticated personal associations between identity keys and display attributes, including the derivation metadata required to read and update them. |

The basket description grants neither read access nor permission to spend a contact output. Contact backup must preserve custom instructions as well as transaction data. The public identity helper does not establish an additional mandatory wallet basket; this request does not invent one.

### 15.4 CertMap and concrete type semantics

The following existing SocialCert-related types are requested with the exact identifiers and field spellings shown. These are type descriptions, not issuer appointments. An implementation MUST still select and verify a particular issuer and its evidence policy. The type identifier alone does not prove issuance by SocialCert. The friendly names and descriptions below are the requested metadata values.

| Type, canonical Base64                         | Name              | Description                                                                                                                                                                      |
| ---------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `exOl3KM0dIJ04EW5pZgbZmPag6MdJXd3/a1enmUU/BA=` | Email control     | A certifier's assertion that a subject demonstrated access to the specified email account under the issuer's verification policy.                                                |
| `vdDWvftf1H+5+ZprUw123kjHlywH+v20aPQTuXgMpNc=` | X account         | A certifier's assertion binding an X account user name and associated profile-photo reference to a subject key under the issuer's verification policy.                           |
| `2TgqRC35B1zehGmB21xveZNc7i5iqHc0uxMb+1NMPW4=` | Discord account   | A certifier's assertion binding a Discord user name and associated profile-photo reference to a subject key under the issuer's verification policy.                              |
| `mffUklUzxbHr65xLohn0hRL0Tq2GjW1GYF/OPfzqJ6A=` | Telephone control | A certifier's assertion that a subject demonstrated access to the specified telephone number under the issuer's verification policy; retained for existing schema compatibility. |

`fields` in each CertMap record is an object keyed by exact field name. Each descriptor contains `friendlyName`, `description`, `type`, and `fieldIcon`; this table supplies the first three and Section 15.1 supplies `fieldIcon`.

| Applicable type   | Exact field    | `friendlyName`        | `description`                                                                                                                       | `type`  |
| ----------------- | -------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------- |
| Email control     | `email`        | Email address         | The mailbox address whose access the issuer checked; this field alone does not assert a legal name or continuing exclusive control. | `text`  |
| X account         | `userName`     | X user name           | The platform user-name string bound by the issuer; exact signed case and spelling must be preserved.                                | `text`  |
| X account         | `profilePhoto` | X profile photo       | The profile-photo reference associated with the verified account; interpret its encoding under the issuer's documented schema.      | `other` |
| Discord account   | `userName`     | Discord user name     | The platform user-name string bound by the issuer; exact signed case and spelling must be preserved.                                | `text`  |
| Discord account   | `profilePhoto` | Discord profile photo | The profile-photo reference associated with the verified account; interpret its encoding under the issuer's documented schema.      | `other` |
| Telephone control | `phoneNumber`  | Telephone number      | The telephone-number string whose access the issuer checked; it does not imply permanent ownership or a particular legal person.    | `text`  |

`profilePhoto` uses the conservative descriptor `other` because the signed representation must be interpreted before deciding it is a fetchable image URL. A publisher may use `imageURL` only after documenting and verifying that constraint for the applicable schema; it must not induce applications to fetch arbitrary signed strings. Images and links remain untrusted external resources.

Historical X registry examples also use `username` with a lowercase `n`. A retained description for that historical field, where needed, is `{ "friendlyName": "Legacy X user name", "description": "Historical spelling; distinct from userName and interpreted only when present in the signed certificate.", "type": "text", "fieldIcon": "<the Section 15.1 icon URL>" }`. It is not a replacement name or a rule for modifying existing certificates. Operators MUST inspect existing records and preserve necessary historical descriptions rather than silently rename a signed field.

These deployed types do not supply a universal normalization or expiry rule. The issuer's evidence and application purpose determine exact-match handling; an application must not invent, for example, lowercasing the local part of every email address or stripping telephone prefixes as a cryptographic equivalence rule. For new issuance, issuers SHOULD document canonical representations and include appropriate time/scope information through a compatible type definition. An incompatible new signed schema must not silently repurpose an established type. The synthetic test type in Section 16 is test data and is **not** another CertMap registration request.

## 16. Test vectors and acceptance cases

### 16.1 Cryptographic fixture

The following is entirely synthetic test material. The private scalars, fixed revelation keys, and fixed IVs MUST NOT be used for real accounts. The revocation outpoint is a serialization fixture, not an assertion that the output exists, is unspent, or provides a usable revocation service. These vectors test cryptographic content and formats; they do not establish blockchain acceptance or live overlay completeness.

Interpret hex as bytes and Base64 canonically. `publicRevelation.payload` and `contact.plaintext` are serialized as compact UTF-8 JSON in the property order shown, without extra spaces or a trailing newline. SHA-256 values are single hashes. Reconstruct the two scripts from their locking public key, payload or ciphertext, and provided DER field signature using Section 7.2. The complete certificate signature is included, so lengths and hashes suffice to check the reconstructed binary without repeating it as another long hex string.

The master wrappers use the subject/issuer relationship and bare field names; `privateName` discloses only `name` to scalar 4; `publicEmail` discloses only `email` to anyone. The contact belongs to scalar 5 and names scalar 2. HMAC key derivation uses BRC-100's BRC-42 symmetric-key operation, not the child private scalar or a direct HMAC with the root private key. The issuance fixture uses fixed readable 16-byte challenges to make the unusual string/data ordering inspectable. Its generated serial is a separate fixture and does not replace the certificate's fixed serial of 32 `22` bytes.

```json
{
  "keys": {
    "subject": {
      "privateScalarHex": "0000000000000000000000000000000000000000000000000000000000000002",
      "publicKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5"
    },
    "certifier": {
      "privateScalarHex": "0000000000000000000000000000000000000000000000000000000000000003",
      "publicKey": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
    },
    "verifier": {
      "privateScalarHex": "0000000000000000000000000000000000000000000000000000000000000004",
      "publicKey": "02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13"
    },
    "contactOwner": {
      "privateScalarHex": "0000000000000000000000000000000000000000000000000000000000000005",
      "publicKey": "022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4"
    }
  },
  "fieldInputs": {
    "email": {
      "plaintext": "alice@example.invalid",
      "keyHex": "4141414141414141414141414141414141414141414141414141414141414141",
      "ivHex": "5151515151515151515151515151515151515151515151515151515151515151"
    },
    "name": {
      "plaintext": "Alice Example",
      "keyHex": "4242424242424242424242424242424242424242424242424242424242424242",
      "ivHex": "5252525252525252525252525252525252525252525252525252525252525252"
    }
  },
  "certificate": {
    "type": "ERERERERERERERERERERERERERERERERERERERERERE=",
    "serialNumber": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=",
    "subject": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
    "certifier": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
    "revocationOutpoint": "3333333333333333333333333333333333333333333333333333333333333333.1",
    "fields": {
      "email": "UVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVEfKogOtq1r5zAJRnUSz103d7+0sYMV6N2Gb8xP5gaVEKUhAt8q",
      "name": "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIBbYFny64VpL4UOEru9WtI0rwQGOFUBN6oRx+yLA=="
    },
    "signature": "3045022100d31674450017aa3eb449ea890a97694f2141ba34aa037c8d77c4d3e1ff9a348b02201633ad9389b69856b6c46d5d704a7471a7c91286d11db9a094766aee74edbcbf"
  },
  "certificateBinary": {
    "withoutSignatureLength": 353,
    "withoutSignatureSHA256": "296e5e07d39301a5530b9ae2b0b14624ea4089ab20eaa860fe4a81fb22e5a624",
    "withSignatureLength": 424,
    "withSignatureSHA256": "2ba78e3bc714a0940bccfc493957d8dfa0487273a0b1bacc793adb1929917550",
    "derivedSigningPublicKey": "02818a006c82d870268dcd8539ff05323a20dcc164762b120e265d52882b616234"
  },
  "wrappers": {
    "masterEmail": {
      "field": "email",
      "keyID": "email",
      "counterparty": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
      "derivedKeyHex": "a7b7919cf3290082a206d9e53477e0fc7875eeea32724f93938eabf11876b6dc",
      "ivHex": "6161616161616161616161616161616161616161616161616161616161616161",
      "ciphertextBase64": "YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFsI2cn7ANzJATt2oH2Yj1towY5QgBAgiTfdha4RsvWCremov+Aa8KoX/1Pr8Un7U4="
    },
    "masterName": {
      "field": "name",
      "keyID": "name",
      "counterparty": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
      "derivedKeyHex": "4446a008b8c96887c4473f8b2ea9a40f1361bac2698d80594c639cf1bc3fe44a",
      "ivHex": "6262626262626262626262626262626262626262626262626262626262626262",
      "ciphertextBase64": "YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJpJbPrzDkz37u3azKcN6LA265ahzhp77FStWw8eDOrastX7KeFjA40g1/iC1v64FE="
    },
    "privateName": {
      "field": "name",
      "keyID": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI= name",
      "counterparty": "02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13",
      "derivedKeyHex": "944165fe7b72c776f29c5508dddcbe9c61b5e5fd2a10c36671d091f7aaa87755",
      "ivHex": "7171717171717171717171717171717171717171717171717171717171717171",
      "ciphertextBase64": "cXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXGMm+yffJMsUvp6/+TM6ou3MI05v+fhM3j3Lj2ODJrpVNEtawBoIT+ZrArRr1xQjwE="
    },
    "publicEmail": {
      "field": "email",
      "keyID": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI= email",
      "counterparty": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
      "derivedKeyHex": "4c3d6500dac678b99372437a07303a3d2019ef71eab2067ed36926f2f55a5f8d",
      "ivHex": "7272727272727272727272727272727272727272727272727272727272727272",
      "ciphertextBase64": "cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJHQ8mwg3g7Elcs/YVEe8/ybZCR/bHCIA4RUaNZwiQ3nFbQEeWiMLhc8UpKm/ytSS8="
    }
  },
  "publicRevelation": {
    "payload": {
      "type": "ERERERERERERERERERERERERERERERERERERERERERE=",
      "serialNumber": "IiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiI=",
      "subject": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
      "certifier": "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9",
      "revocationOutpoint": "3333333333333333333333333333333333333333333333333333333333333333.1",
      "fields": {
        "email": "UVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVFRUVEfKogOtq1r5zAJRnUSz103d7+0sYMV6N2Gb8xP5gaVEKUhAt8q",
        "name": "UlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIBbYFny64VpL4UOEru9WtI0rwQGOFUBN6oRx+yLA=="
      },
      "signature": "3045022100d31674450017aa3eb449ea890a97694f2141ba34aa037c8d77c4d3e1ff9a348b02201633ad9389b69856b6c46d5d704a7471a7c91286d11db9a094766aee74edbcbf",
      "keyring": {
        "email": "cnJycnJycnJycnJycnJycnJycnJycnJycnJycnJycnJHQ8mwg3g7Elcs/YVEe8/ybZCR/bHCIA4RUaNZwiQ3nFbQEeWiMLhc8UpKm/ytSS8="
      }
    },
    "payloadUTF8Length": 863,
    "payloadSHA256": "adf73cef20e6a9f2827b49c00703e2a34cc32faef7ac7d25439c1b1641a4826d",
    "lockingPublicKey": "03ea9865ff14e36433501a70bd172ab83958fc63c7cd6bf6e273f014f4c0960371",
    "fieldSignatureHex": "3045022100cd306376fadadaae9b44b12d4353a499bf6dc7f338a132c5d1cde9bb4eff259902206e7176197daa3184abae448a3c9fe189943a03e330cc547e91ef5d89b8946f60",
    "scriptLength": 974,
    "scriptSHA256": "a4dd9d3d5e0ccd11531894237ef20684689e69034924d26442d408e8c2a714a7",
    "revelationID": "78bf82ae1936cce6f98dfbffc8d22ae3822eed6e7f00179603a24671dd150b50",
    "decryptedFields": {
      "email": "alice@example.invalid"
    }
  },
  "contact": {
    "plaintext": {
      "name": "Alice at the workshop",
      "avatarURL": "https://example.invalid/alice.png",
      "abbreviatedKey": "02c6047f94...",
      "identityKey": "02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5",
      "badgeIconURL": "",
      "badgeLabel": "Saved by me",
      "badgeClickURL": "",
      "metadata": {
        "source": "met in person"
      }
    },
    "keyID": "gYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYE=",
    "derivedKeyHex": "7c2a3744a8ac624aa63f6f1e08e16a87bd86d52bcdf17cf93492a6461c358725",
    "ivHex": "8282828282828282828282828282828282828282828282828282828282828282",
    "ciphertextBase64": "goKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoICjmzAOMfDajIeoFMXebz3/TZKmmhJg8MB63r01S6sF8sGoxIc/igmi1lXW2uXKmBOR2JhMgoHGTxHZcQI5364N8kqcir14T1aJqyJrZPiEFjGF22IY6lodLyp/ngSNvXE6uW8SHIxvoB3v+HeGx6/Rj3H6IzUTp1zT+Jau8pBEEubTDiETNrq7ebVNPhJeU9k12whqpgP7J3TOBgp+DKIXqPCybzKHPyiBsTrpuKpdEy1WfS758cMd8FlZR6LxB/Q73Db3Gnhq2OZOJgEkI19eJJpJup86EhGYh36iUvqrsw3TL7/1x9UT2QYcggkBl8aOgjYR95JKirjgyZDXZdWTNUT3kID9X5KMMM/CXyGAE/xCfypLRDzF0YtBvdft0OtierHwd+SfjlVD7F5v2MrhmAQTfZ0E3CvHQ==",
    "lockingPublicKey": "0384ebefcb1e6421b01b0dbd27b925f3fee9cd86668c6fdc61c39b5e286c482555",
    "fieldSignatureHex": "304402202162d792eac035c06b43a1cde8f0403214978c0d06ab9b4f87fdfa7e917706d5022045b8e2063b50462a4f5e4587f21a95d7eefaaad4c3379b349392e27844b308dd",
    "scriptLength": 456,
    "scriptSHA256": "b9ae0f0ae8d50fb92ccff9361c31f08c040680aea482d235a154e0396cd5f961",
    "tagHmacKeyHex": "373db691dfd26a2a5aacf4fb95e5f47eb695f20b0295b48d2e01eae77528d6b3",
    "tag": "identityKey 24bf289dd486a165f60c0aa96bc6dec0a5d8a4f9c6d0af95a6afc60b6eb2cfb5",
    "customInstructions": "{\"keyID\":\"gYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYE=\"}"
  },
  "issuance": {
    "clientChallenge": {
      "randomHex": "30313233343536373839616263646566",
      "keyID": "0123456789abcdef",
      "derivedKeyHex": "893ee146ce0da0073c2866472b1e39d541e9af30c94608c98140a107afa9247f",
      "hmacHex": "9542cc4c6773945c4fe7c8bc3e192a3edee4e13aacbe2df5a2b5be2f51f41cd9",
      "nonce": "MDEyMzQ1Njc4OWFiY2RlZpVCzExnc5RcT+fIvD4ZKj7e5OE6rL4t9aK1vi9R9BzZ"
    },
    "serverChallenge": {
      "randomHex": "66656463626139383736353433323130",
      "keyID": "fedcba9876543210",
      "derivedKeyHex": "c54420ba7f2daffb1424b6505fdfc38b05e37e7157dc5f4940545ef02d866ec9",
      "hmacHex": "d06dddea230d8ea9372cc0e46ab27f6fa4c3ea310d8c42dbdeaf60fd5640d702",
      "nonce": "ZmVkY2JhOTg3NjU0MzIxMNBt3eojDY6pNyzA5Gqyf2+kw+oxDYxC296vYP1WQNcC"
    },
    "keyID": "ZmVkY2JhOTg3NjU0MzIxMNBt3eojDY6pNyzA5Gqyf2+kw+oxDYxC296vYP1WQNcCMDEyMzQ1Njc4OWFiY2RlZpVCzExnc5RcT+fIvD4ZKj7e5OE6rL4t9aK1vi9R9BzZ",
    "derivedKeyHex": "aedda56b11492efe61485f92f1ea419d8fc4b769ef19c0c99a9b88e92c572c1a",
    "dataHex": "303132333435363738396162636465669542cc4c6773945c4fe7c8bc3e192a3edee4e13aacbe2df5a2b5be2f51f41cd966656463626139383736353433323130d06dddea230d8ea9372cc0e46ab27f6fa4c3ea310d8c42dbdeaf60fd5640d702",
    "serialNumber": "RMoclwkdq9MSSTZyLj183KaFfbt1cIhFcQF7yAHudyo="
  }
}
```

Expected results: the issuer signature verifies; both master field keys decrypt for their intended parties; scalar 4 reveals `name: "Alice Example"`; the anyone wallet reveals exactly `email: "alice@example.invalid"`; the public identity and contact field signatures verify; the contact owner recovers the exact JSON object; and the two parties derive the same issuance serial. The public revelation does not reveal `name` even though that encrypted field remains in the signed certificate.

The fixture was generated with `@bsv/sdk` 2.8.10. Its derivations, encodings, ECDSA signatures, AES-GCM envelopes, scripts, HMACs, and hashes were also checked independently with Python and `cryptography` 50.0.1. Another signer may produce a different valid ECDSA signature; it should verify these provided signatures and reproduce their associated byte/hash results when using the fixture's exact signatures.

### 16.2 Identity and lookup acceptance cases

These are deterministic conformance cases for the specified behavior, separate from a claim that a live service has passed a test suite. All records below are assumed to have valid authenticated transaction evidence unless the case says otherwise. No submission to a public service is necessary.

| Case                                                                                      | Expected interpretation                                                                                              |
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| The Section 16.1 identity script, with its public email keyring                           | A valid current-format identity payload; its synthetic transaction/revocation evidence still needs separate handling |
| The same certificate held privately with only the master keyring                          | Usable for the holder's authorized disclosures; not automatically a public identity publication                      |
| A public result includes an unsigned plaintext cache                                      | Recompute plaintext from authenticated keyring/fields before relying on it                                           |
| Two valid revelations disclose the same email for different subject keys                  | Both may be admitted; no first-result or first-claim ownership rule                                                  |
| Two outputs carry the exact same revelation tuple and one authenticated spend is observed | Persist withdrawal and suppress that revelation's indexed copies                                                     |
| A previously withdrawn tuple is later re-admitted or an old cached row remains            | Do not restore it to current lookup in the persistent withdrawal profile                                             |
| A fresh keyring envelope reveals a different subset of the same valid certificate         | A distinct revelation; inspect its own publication and withdrawal state                                              |
| Query has serial plus a different issuer filter                                           | Dispatch by serial; client must independently check the issuer it intended                                           |
| Query has only `certificateTypes`                                                         | Reject as no supported selector                                                                                      |
| Query has an empty `certifiers` array                                                     | Reject the malformed supplied filter; no-trust wallets should decide locally                                         |
| Certificate A reveals email; certificate B for the same subject reveals employer          | A named email-and-employer attribute query does not join A and B                                                     |
| Stored `userName` is `Alice`, query is `alice`                                            | No match in TS Stack exact-case profile; match in corrected standalone profile                                       |
| Stored email is `alice@example.invalid`, named email query is `alice`                     | Candidate fuzzy match, not proof of exact email equality                                                             |
| `any` is one UTF-16 code unit after normalization                                         | No matches                                                                                                           |
| Public source includes a field whose name is `profilePhoto`                               | Exclude that value from `any`; do not automatically exclude it from its explicit named-field search                  |
| Host lacks a recent spend or publication                                                  | Its answer is incomplete/stale evidence, not a global identity verdict                                               |

### 16.3 Trust calculation and personal resolution

Let Bob select issuer A with weight 3 and issuer B with weight 2, with threshold 5. Issuer C is unselected. Each record below is already cryptographically verified and appropriately bound to the query.

| Evidence for one subject                                        | Distinct selected weight | Result                                                                   |
| --------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------ |
| Three different certificates from A                             | 3                        | No qualifying public discovery result                                    |
| One certificate from A and one from B                           | 5                        | Subject qualifies; return attributed accepted certificates               |
| A certificate from A repeated in multiple outputs, plus B       | 5                        | Repetition adds no issuer weight                                         |
| A and C                                                         | 3                        | C does not contribute or become a selected issuer                        |
| A and B, then Bob removes B before interpreting cached evidence | 3 under the new policy   | Recompute; cached evidence does not preserve the old trust decision      |
| A signs email and B signs employer                              | 5 for the subject        | Neither issuer is thereby represented as having signed the other's field |

With threshold 3, A alone can qualify; that is Bob's policy choice. Trust numbers are local weights, not probabilities or a network reputation consensus. A direct saved contact can independently identify the subject under Bob's personal authority. It is not an extra invented issuer certificate or a reason to fabricate a signature.

For contacts-aware sequential key resolution, an authenticated saved contact for the key wins without needing an overlay response. With contacts disabled, a contact is not consulted by that helper. For attribute resolution, a saved name equal to the query can short-circuit the sequential contact helper, while an arbitrary metadata email or `any` value does not become a supported local search key. In the parallel attribute helper, contacts override corresponding overlay subjects; they do not automatically add every local contact to the result set.

## 17. Implementation evidence and compatibility

### 17.1 Pinned sources

The reviewed implementation baseline is the following public source, rather than an unversioned promise about every deployment. The specification above is the protocol description; these links let readers examine how current implementations realize it.

| Source                                                | Revision and relevant areas                                                                                                                                                                                                                           |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TS Stack SDK and Wallet Toolbox                       | [`c9195d8417579b9c3c56c5c9f29022e1a0019806`](https://github.com/bsv-blockchain/ts-stack/tree/c9195d8417579b9c3c56c5c9f29022e1a0019806): SDK package 2.8.10 and Wallet Toolbox 2.14.3                                                                  |
| Certificate primitives and peer exchange              | [SDK auth](https://github.com/bsv-blockchain/ts-stack/tree/c9195d8417579b9c3c56c5c9f29022e1a0019806/packages/sdk/src/auth): `Certificate`, `MasterCertificate`, `VerifiableCertificate`, `Peer`, and nonce utilities                                  |
| Publication, withdrawal, display resolution, contacts | [SDK identity](https://github.com/bsv-blockchain/ts-stack/tree/c9195d8417579b9c3c56c5c9f29022e1a0019806/packages/sdk/src/identity): `IdentityClient` and `ContactsManager`                                                                            |
| Wallet discovery, issuance, trust, permissions        | [Wallet Toolbox](https://github.com/bsv-blockchain/ts-stack/tree/c9195d8417579b9c3c56c5c9f29022e1a0019806/packages/wallet/wallet-toolbox/src): `Wallet.ts`, `WalletSettingsManager.ts`, `WalletPermissionsManager.ts`, and `utility/identityUtils.ts` |
| Topic validation and indexed lookup                   | [TS Stack identity overlay](https://github.com/bsv-blockchain/ts-stack/tree/c9195d8417579b9c3c56c5c9f29022e1a0019806/packages/overlays/topics/src/identity): topic, validator, lookup service, storage manager                                        |
| Standalone search compatibility profile               | [identity-services at `b5b20f2ad1e63658483969b30ff024159f9bbf7c`](https://github.com/bsv-blockchain/identity-services/tree/b5b20f2ad1e63658483969b30ff024159f9bbf7c): corrected literal/substring matching profile                                    |
| Issuer and concrete social schemas                    | [SocialCert backend at `79de17aa35797b34abcc7587d83cf137a895fa25`](https://github.com/p2ppsr/socialcert-backend/tree/79de17aa35797b34abcc7587d83cf137a895fa25): issuer configuration, certificate handlers, issuance routes                           |
| Issuance/publication application                      | [SocialCert UI at `b662c8a777c2db2915b32062b4532fbbfaf85255`](https://github.com/p2ppsr/socialcert-ui/tree/b662c8a777c2db2915b32062b4532fbbfaf85255)                                                                                                  |
| Application identity components                       | [identity-react at `c42f4a474330c3029c6671306b71828075a6c177`](https://github.com/bsv-blockchain/identity-react/tree/c42f4a474330c3029c6671306b71828075a6c177)                                                                                        |

Metanet Client Desktop and Metanet Explorer provide wallet/application context for these flows. Their names are examples of clients using the underlying wallet ecosystem, not claims that every released UI exposes every operation or passes this specification's conformance requirements. The normative mechanisms are traceable to the public SDK, wallet, and overlay sources above.

### 17.2 Compatibility boundaries that implementers must retain

The following observations are relevant to interoperation; they are not new encodings or claims of universal conformance:

* **Certificate ordering:** the current serializer's `localeCompare` can differ from language-independent lexical ordering. Use documented schemas and preserve signed bytes/interpretations; do not silently normalize existing certificates.
* **Direct master-keyring revealer:** interfaces can name a revealer distinct from the issuer, while pinned acquisition/proving paths assume the stored certifier relationship. A deployment must verify support before using a different revealer, rather than assume all interface-shaped values are implemented.
* **Discovery permissions:** BRC-100's documented optional default and SDK argument defaults differ, and the pinned permissions wrapper does not forward every discovery permission preference. Applications should specify intent explicitly; wallets adopting this profile must honor the documented permission behavior.
* **Pagination:** the pinned wallet discovery path includes caller pagination in cache identity but does not pass it to the overlay question or fully implement it after trust filtering. It also sends the selected issuer list as one filter, despite the host's 32-issuer bound and settings' larger capacity. A request for a larger page is not evidence that more than the host's default collection was examined. Section 10's conformance requirement calls for actual pagination semantics.
* **Search:** TS Stack and corrected standalone storage have the explicitly different `any` and `userName` profiles in Section 9; the pinned wallet's conservative result binding does not erase that difference.
* **Current state:** certificate signature verification, BEEF validity, public revelation withdrawal, and current revocation assessment are separate. The pinned public validator and discovery utilities do not establish every current-state fact merely by returning a certificate.
* **Contacts:** opt-in SDK contact resolution and an injected in-process `ContactSource` have different representations. Synthetic local assertions must not be serialized as valid BRC-52 certificates through an unextended BRC-100 wire format.
* **Presentation:** known-type helpers select fields such as `email`, `userName`, `name`, or `profilePhoto` for display. A heuristic display choice is not a new signed assertion or evidence that all CertMap metadata is automatically interpreted by every application.
* **SocialCert scope:** the pinned configuration enables three social types, retains a disabled telephone example, and uses the disabled-revocation sentinel in its ordinary signing path. It must not be described as implementing a functioning per-certificate UTXO revocation service merely because a revoke route exists in source.

These observations give implementers concrete migration work without requiring a competing certificate system. Conformance claims should identify the release, supported profile, exercised role, and any remaining differences. Changes to normative wallet transport or certificate primitives belong with their authoritative BRCs; an integration document cannot resolve those differences by quietly inventing fields.

## 18. Security, privacy, and operational continuity

Wallets and applications MUST maintain the boundaries between cryptographic evidence, attributed claims, user trust, and permission to act. A valid issuer signature proves who signed, not that the claim is true. A valid subject field signature proves authorization by a key, not informed human consent. Trusted UI, scoped permissions, and understandable publication/disclosure decisions remain necessary.

Public publication makes attributes, issuer relationships, and subject linkage available to observers indefinitely. Withdrawal reduces current discovery, not historical disclosure. Search providers can observe queries; image and documentation servers can observe fetches. Applications SHOULD minimize searches, use local contacts where appropriate, cache with explicit freshness limits, and avoid sending private certificate fields or master keyrings to public services. Metadata, display strings, URLs, and images must be treated as untrusted input and rendered without code execution or unintended local-resource access.

Wallet backups need private-key continuity, held certificates and master keyrings, contact transaction evidence and derivation metadata, and sufficient publication history to manage withdrawals. A public index is not a backup of a private certificate collection or encrypted contacts. Issuers should preserve the evidence and revocation authority necessary for their stated policies; overlay operators should preserve authenticated spend knowledge and test index reconstruction, not just copy current attribute rows.

A failing issuer, registry publisher, wallet vendor, storage host, or overlay need not become a universal identity failure. Users can choose other trust anchors and providers and preserve independently established contacts. The replacement must retain attribution, validate available evidence, and state its limitations. No provider can legitimately transfer another issuer's signing authority merely by hosting its records. When continuity requires a different identity key, explicit re-establishment of trust is necessary.

## References

* [BRC-2: Data Encryption and Decryption](/wallet/0002), for the encryption envelope used by the wallet primitives.
* [BRC-42](/key-derivation/0042) and [BRC-43](/key-derivation/0043), for child-key derivation and protocol/key/counterparty naming.
* [BRC-52: Identity Certificates](/peer-to-peer/0052), [BRC-100: Wallet-to-Application Interface](/wallet/0100), [BRC-103](/peer-to-peer/0103), and [BRC-104](/peer-to-peer/0104).
* [BRC-22](/overlays/0022), [BRC-24](/overlays/0024), [BRC-76](/transactions/0076), and [BRC-183](/overlays/0183), for overlay submission, lookup, synchronization, and deployment models.
* [BRC-184: Optional Metadata Registries and Their Stewardship](/wallet/0184), [registry submissions](https://github.com/bsv-blockchain/BRCs/tree/master/REGISTRY_SUBMISSIONS.md), and [BRC-116](/wallet/0116), for metadata and wallet permission/trust boundaries.
* [BRC-28](/payments/0028), [BRC-29](/payments/0029), and [BRC-169](/peer-to-peer/0169), for related payment and naming systems.
* [Computing with Integrity, Ty Everett (2020)](https://github.com/open-cash-standards/papers/tree/889f38bcbce8773718b248b1704a91e0a19567dd/computing-with-integrity), conceptual background; [BRC-51](/opinions/0051) and [BRC-89](/opinions/0089), application and architecture context.
* [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174), requirement terminology.


# Agent Allowances

Ruth Heasman (Ruth Designs Digital)

**Category:** wallet

## Abstract

This specification defines an *agent allowance*: a sum of money that a wallet owner sets aside for an autonomous agent, which the agent can spend with its own key and the owner can take back at any time in one transaction. An allowance is held in ordinary transaction outputs under a two-branch locking script, so its limit is a property of the money itself rather than a policy enforced by software. The proposal specifies how allowances are created, delivered to the agent, spent, reported and swept; how an agent proves it acts for an owner using a BRC-52 certificate that the sweep also revokes; and how the resulting transactions form a receipt that anyone can check against block headers. It uses existing BRC-100 wallet methods and adds no new ones. It complements BRC-181, which bounds agents that spend through the owner's wallet, by covering agents that hold their own key and run elsewhere.

## Motivation

AI agents increasingly spend money on behalf of people: they call paid model APIs, buy data and pay other services. Today they usually do this with a bearer credential such as an API key. A bearer credential can spend whatever the account behind it can, and revoking it means noticing a problem, finding the right dashboard and rotating the key while the agent keeps spending. The record of what the agent did is held by the party being paid.

BRC-116 lets an application hold a `spendingAuthorization`, a monthly ceiling granted inside the owner's wallet. BRC-181 extends wallet enforcement to unattended agents: the owner's wallet keeps the keys for an isolated agent account and checks every agent request against a signed policy. Both assume the agent spends *through* the owner's wallet, so the wallet must be reachable and running for every payment. Many agents do not work that way. They run on servers or in the cloud, pay counterparties discovered at run time, and need to keep working while the owner's wallet is closed.

An allowance covers that case. The owner moves a fixed amount into outputs that the agent can spend directly, with its own key. The agent cannot spend more than those outputs hold, whatever it does and however many sub-processes it starts, because the money is not there. The owner keeps a second spending path to the same outputs and can sweep whatever is left in a single transaction. Every payment the agent makes is a transaction, so the record of its spending is written by settlement rather than by any party to a later dispute.

The aims are:

1. A worst case fixed before the agent starts: the agent can lose at most the allowance.
2. Revocation that is one owner action, not a race against a credential.
3. Attribution: payees can see that an agent acts for a specific owner, within a specific allowance.
4. A receipt for every payment that the owner holds and anyone can verify from block headers.
5. No new wallet interface methods, so any BRC-100 wallet can implement it.
6. The owner's wallet does not need to be online while the agent spends.

## Relationship to related work

### BRC-116: spending authorisations

A BRC-116 `spendingAuthorization` limits how much an application may spend from the owner's wallet in a period. It governs applications the wallet is talking to. An allowance governs money that has already left the owner's main balance for an agent outside the wallet. The two compose: funding an allowance is spending by the application that requests it, and SHOULD count against that application's BRC-116 authorisation.

### BRC-181: wallet-enforced agent spend policy

BRC-181 bounds an agent that spends through the owner's wallet. The wallet holds the keys for an isolated agent account and checks every `createAction` against a signed policy with per-transaction, period and lifetime caps, a destination allowlist, rate limits and circuit breakers. This specification covers the other topology, where the agent holds its own key and runs elsewhere.

|                                               | BRC-181                                                     | This specification                                                |
| --------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------- |
| Who holds the spending key                    | the owner's wallet                                          | the agent                                                         |
| Where the agent runs                          | where it can reach the owner's wallet                       | anywhere                                                          |
| Owner's wallet online for each payment        | yes                                                         | no                                                                |
| What bounds total loss                        | the isolated account balance, enforced with a signed policy | the value of the allowance outputs                                |
| Per-transaction caps, allowlists, rate limits | yes, enforced by the owner's wallet                         | not by script; see below                                          |
| Revocation                                    | wallet freeze or token revocation                           | one owner transaction, which also revokes the agent's certificate |
| Payee can verify who the agent acts for       | not defined in the current version                          | BRC-52 certificate (section 3.5)                                  |
| Record of spending                            | the wallet's audit log                                      | Atomic BEEF receipts the owner holds and anyone can verify        |

Script cannot express BRC-181's finer limits, so an allowance on its own bounds only the total. The two compose: an agent wallet MAY enforce a BRC-181 policy over its own allowance outputs, giving per-transaction, rate and destination limits inside a total the owner can always take back.

### BRC-172: control without custody

BRC-172 removes custody altogether: the agent holds a credential that advances a covenant whose payments are fixed in advance. Where the payment graph can be committed ahead of time it is the stronger construction. Allowances, like BRC-181, address agents that pay counterparties not known when authority is granted.

### BRC-151: risks of AI agents with human funds

Section 9 of BRC-151 identifies the danger of an AI agent operating from the user's primary wallet, and of single-axis spending limits being drained by repeated small payments. An allowance moves the agent's money out of the primary wallet entirely, so the most it can lose is fixed when it is funded.

## Terminology

* **Owner:** the person or organisation funding the agent. The owner's keys are held in a BRC-100 wallet (the *owner wallet*).
* **Agent:** an autonomous program that spends from the allowance. The agent holds its own BRC-100 wallet (the *agent wallet*) with its own identity key.
* **Allowance:** a set of allowance outputs funded by the owner for one agent and one purpose, identified by an `allowanceId`.
* **Allowance output:** an output locked with the script in section 3.2.
* **Revocation output:** a 1-satoshi output, spendable only by the owner, created with the allowance and referenced by the agent's certificate.
* **Payee:** any party the agent pays.
* **Sweep:** an owner transaction that spends all known allowance outputs and the revocation output back to the owner.
* **Receipt:** an agent transaction that spends allowance outputs, delivered to the owner as Atomic BEEF (BRC-95).

The key words MUST, SHOULD and MAY are used as described in RFC 2119.

## Specification

### 3.1 Keys

Each allowance uses keys derived under BRC-42, with the BRC-43 protocol ID `[2, "agent allowance"]` and a key ID equal to the `allowanceId`. The `allowanceId` MUST be at least 16 random bytes, base64-encoded, and MUST NOT be reused.

Both parties can compute both public keys without further communication:

* **Owner allowance key:** `getPublicKey({ protocolID: [2, "agent allowance"], keyID: allowanceId, counterparty: agentIdentityKey, forSelf: true })`, called on the owner wallet.
* **Agent allowance key:** the same call with `forSelf: false` on the owner wallet, or with `counterparty: ownerIdentityKey, forSelf: true` on the agent wallet.

Deriving fresh keys per allowance keeps one owner's allowances from being linked to each other or to the owner's identity key on-chain.

### 3.2 Allowance output locking script

```
OP_IF
  <agentAllowanceKey> OP_CHECKSIG
OP_ELSE
  <ownerAllowanceKey> OP_CHECKSIG
OP_ENDIF
```

Unlocking scripts:

* Agent spend: `<agentSignature> OP_1`
* Owner sweep: `<ownerSignature> OP_0`

Signatures use `SIGHASH_ALL | SIGHASH_FORKID` unless a payment protocol in use requires otherwise. The branch selector makes the spending party explicit on-chain, so a receipt and a sweep can be told apart without further data.

An allowance MAY be split across several allowance outputs with the same locking script. Several outputs let an agent make payments in parallel without waiting for change from a previous payment.

### 3.3 Revocation output

The funding transaction MUST also create one revocation output of 1 satoshi, locked so that only the owner can spend it:

```
<ownerAllowanceKey> OP_CHECKSIG
```

This outpoint is the `revocationOutpoint` of the agent's certificate (section 3.5). Because the agent cannot spend it, the owner can always revoke the certificate, even if the agent has moved the allowance outputs (section 6.2).

### 3.4 Funding and delivery

The owner wallet creates the allowance with `createAction`, with the allowance outputs and the revocation output in an owner basket (BRC-46) named `agent allowances`, tagged with the `allowanceId`. Funding an allowance is spending by the requesting application, so wallets SHOULD count it towards that application's BRC-116 spending authorisation.

The owner then delivers an *allowance descriptor* to the agent, over any authenticated channel, for example a BRC-103 session or a BRC-33 message relay:

```json
{
  "version": "1.0",
  "allowanceId": "q8c1x3J0pZ0m3bJcVZ8m2w==",
  "purpose": "Competitor research",
  "ownerIdentityKey": "02…",
  "agentIdentityKey": "03…",
  "protocolID": [2, "agent allowance"],
  "outputs": [
    { "outpoint": "<txid>.0", "satoshis": 12500 },
    { "outpoint": "<txid>.1", "satoshis": 12500 }
  ],
  "revocationOutpoint": "<txid>.2",
  "beef": "<base64 Atomic BEEF of the funding transaction>",
  "expiresAt": "2026-10-10T00:00:00Z",
  "permittedPayees": ["02…", "03…"],
  "receiptChannel": { "type": "message-box", "recipient": "02…", "messageBox": "allowance_receipts" },
  "certificate": { }
}
```

The agent wallet MUST verify the funding transaction from the BEEF, check that each listed output carries the locking script of section 3.2 for the derived keys, and internalise the outputs with `internalizeAction` using basket insertion into an agent basket named `allowance`, recording the `allowanceId` in the output's custom instructions.

`permittedPayees` is advisory. Script cannot restrict where an agent pays, so a conforming agent wallet SHOULD refuse payments to identity keys not on the list when it is present.

### 3.5 Agent certificate

The owner issues the agent a BRC-52 certificate, created and revealed as described in BRC-53:

* `certifier`: the owner's identity key
* `subject`: the agent's identity key
* `type`: the base64 SHA-256 of the string `agent allowance`
* `revocationOutpoint`: the revocation output of section 3.3
* fields: `allowanceId`, `purpose`, `expiresAt`, and MAY include a display name for the owner

A payee that authenticates the agent through BRC-103 MAY request this certificate to confirm that the agent acts for the owner and that the allowance has not been revoked. The agent reveals fields selectively under BRC-52, so a payee can learn that a valid allowance exists without learning its purpose.

### 3.6 Payments

An agent pays from the allowance with any payment protocol whose transaction it constructs itself, including BRC-29 payments and the HTTP 402 profiles of BRC-105, BRC-121 and BRC-166.

A conforming agent spending allowance outputs MUST:

1. Spend allowance outputs only through the agent branch.
2. Return any change to a new output with the same allowance locking script.
3. Pay network fees from the allowance.
4. Deliver the spending transaction to the owner as a receipt (section 3.7) as soon as it is broadcast.

Rule 2 keeps unspent funds sweepable. It is a conformance rule rather than a script rule: section 6.2 covers agents that break it.

Because an active agent repeatedly spends its own unconfirmed change, the ancestry carried in each payment's BEEF grows with each payment. Payees using BRC-105 SHOULD support the known-txids mechanism of BRC-105 section 5.3 for allowance payers.

### 3.7 Receipts

For each spending transaction the agent MUST send the owner, over the descriptor's `receiptChannel` (for example a BRC-33 message box), a message containing the `allowanceId` and the transaction as Atomic BEEF (BRC-95). The owner wallet MUST verify the transaction, internalise any new allowance outputs into the `agent allowances` basket by basket insertion, and store the BEEF.

The owner wallet SHOULD show the allowance's remaining balance from the outputs it holds, and SHOULD warn the owner when receipts stop arriving while the allowance still holds funds.

Once mined, each receipt's BEEF can be extended with its Merkle path. A BEEF (BRC-62) containing the funding transaction, every receipt and the sweep is a complete, self-contained record of the allowance that anyone can verify against block headers without contacting the owner, the agent or any payee. Wallets SHOULD offer it as an export.

### 3.8 Sweep

To revoke, the owner wallet constructs one transaction that:

* spends every allowance output it holds for the `allowanceId` through the owner branch, and
* spends the revocation output,

and pays the total, less fees, to the owner. The wallet MUST broadcast it immediately.

Spending the revocation output revokes the agent's certificate under BRC-52 in the same transaction that recovers the funds. After the sweep, the agent's key still exists but controls no allowance outputs and carries no valid certificate.

If the agent broadcasts a payment that spends an allowance output at the same moment as the sweep, whichever transaction the network accepts first wins for that output. The owner loses at most one payment per allowance output.

### 3.9 Expiry

Script does not enforce `expiresAt`. The owner wallet SHOULD sweep automatically at expiry, and payees SHOULD treat a certificate whose `expiresAt` has passed as invalid.

### 3.10 Implementation with existing BRC-100 methods

No new wallet methods are needed:

| Step                                  | BRC-100 methods                                                                                                                                                         |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Derive allowance keys                 | `getPublicKey`                                                                                                                                                          |
| Fund the allowance                    | `createAction` with custom locking scripts and basket                                                                                                                   |
| Issue the certificate                 | the BRC-52 certificate flow (`acquireCertificate` on the agent wallet)                                                                                                  |
| Accept the allowance, accept receipts | `internalizeAction` with basket insertion                                                                                                                               |
| Agent payment or owner sweep          | `createAction` with the allowance inputs, then `createSignature` with `hashToDirectlySign` over the input's signature hash, then `signAction` with the unlocking script |
| Find allowance outputs                | `listOutputs` on the relevant basket                                                                                                                                    |

Wallets that adopt the proposal natively SHOULD present allowances as their own feature (create, view, sweep) rather than leaving them to applications.

## Wallet user experience

A wallet implementing allowances natively SHOULD:

* show, before funding, the purpose, amount, agent identity, expiry and any permitted payees;
* show every active allowance with its remaining balance;
* make the sweep available as one action from the allowance and from any notification about it;
* show a completed sweep with the amount returned;
* offer the receipt export of section 3.7.

## Security considerations

### 6.1 Bounded loss

The most an agent can spend is the value of the allowance outputs. Funding more agents does not raise any agent's limit, and an agent that funds a sub-agent can only pass on part of its own allowance.

### 6.2 Non-conforming agents

An agent holding its allowance key can move the remaining balance anywhere, including to itself, instead of returning change to the allowance script. This is within the bounded loss of section 6.1: it is the worst case the owner accepted when funding. The owner loses the ability to sweep what was moved but can still revoke the certificate, because only the owner can spend the revocation output.

### 6.3 Withheld receipts

An agent that withholds receipts prevents the owner from learning its current allowance outputs and so delays the sweep. It cannot exceed the cap. Owner wallets MAY look up outputs by the allowance locking script where an index is available, and SHOULD revoke the certificate when receipts are overdue.

### 6.4 Concurrent sweep and payment

See section 3.8. Owners who want a smaller worst case during a sweep should split the allowance across fewer, smaller outputs, or fund smaller allowances more often.

### 6.5 Agent key compromise

Compromise of the agent's key is equivalent to a misbehaving agent: loss is bounded by section 6.1 and the owner can sweep and revoke.

### 6.6 Privacy

Allowance keys are derived per allowance and do not reveal the owner's identity key on-chain. Certificate fields are revealed selectively. The descriptor contains the owner's identity key and SHOULD be delivered only over an authenticated, encrypted channel.

### 6.7 Finer limits

An allowance bounds the total an agent can spend, not how fast or to whom. Owners who need those limits SHOULD use an agent wallet that enforces a BRC-181 policy over the allowance outputs, and SHOULD fund smaller allowances more often rather than one large one.

## Out of scope

The following are left to applications and other proposals: proxies that let agents pay providers who do not accept BSV payments; pricing and refunds for usage that is metered after the fact; reconciling a provider's invoice against receipts; and automated judgement of whether an agent stayed on task.

## Implementations

None yet. A reference implementation is planned using `@bsv/sdk`, `@bsv/wallet-toolbox`, the BSV Association HTTP payment middleware and `@bsv/message-box-client`. A reference user interface for allowance creation, monitoring, sweeping and receipt checking is available on request.

## References

* [BRC-29: Simple Authenticated BSV P2PKH Payment Protocol](/payments/0029)
* [BRC-33: PeerServ Message Relay Interface](/peer-to-peer/0033)
* [BRC-42: BSV Key Derivation Scheme (BKDS)](/key-derivation/0042)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-46: Wallet Transaction Output Tracking (Output Baskets)](/wallet/0046)
* [BRC-52: Identity Certificates](/peer-to-peer/0052)
* [BRC-53: Certificate Creation and Revelation](/wallet/0053)
* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062)
* [BRC-95: Atomic BEEF Transactions](/transactions/0095)
* [BRC-96: BEEF V2 Txid Only Extension](/transactions/0096)
* [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100)
* [BRC-103: Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol](/peer-to-peer/0103)
* [BRC-104: HTTP Transport for BRC-103 Mutual Authentication](/peer-to-peer/0104)
* [BRC-105: HTTP Service Monetization Framework](/payments/0105)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)
* [BRC-121: Simple 402 Payments](/payments/0121)
* [BRC-151: BRC-100 Risk Assessment and Best Integration Practices](/opinions/0151)
* [BRC-166: P2PKH Payments for HTTP 402](/payments/0166)
* [BRC-172: Giving an AI Agent Control of a Delivery Vehicle Without Giving It a Wallet](/state-machines/0172)
* [BRC-181: Wallet-Enforced Autonomous-Agent Spend Policy](/wallet/0181)
* RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
* M. Fernandez, *Agent Control Protocol v1.30: Admission Control for Agent Actions*, arXiv:2603.18829 (2026)
* R. Heasman, *The Payment Is the Permission* (2026)

## Appendix: example

An owner funds an agent for a research task with 50,000 satoshis across four allowance outputs of 12,500 satoshis, plus a 1-satoshi revocation output, and delivers the descriptor and certificate.

The agent makes 211 payments to two payees, each returning change to the allowance script, then begins retrying a failing request. Each retry is a payment and a receipt. The owner wallet notices the repeated identical payments in the receipts and the owner sweeps. One transaction spends the remaining allowance outputs and the revocation output back to the owner. One retry that was already broadcast spends its input first and settles; every other output returns to the owner. The agent's certificate is revoked by the same transaction.

The owner exports a BEEF containing the funding transaction, every receipt and the sweep. A third party checks every payment against block headers without asking the agent, the payees or the owner.


# Extensible Wallet Version Strings and Account Labels

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This proposal defines a bounded `vendor-major.minor.patch; key=value;` format for the string returned by `getVersion`, a process for future BRCs to define attributes, and one initial attribute: `account-label`. The label lets an application describe an existing account using the name its user currently recognizes. A wallet might initially return `Pocket Photos Account` and later return `Metanet Account`, without changing the account or the wallet's software version.

This proposal extends BRC-100's version-string format and raises the complete-string limit from 30 to 2048 bytes. It preserves the `getVersion({})` call and `{ version: string }` result shape. The string is optional metadata, not an authentication result, provider identifier, core-method negotiation mechanism, or permission grant. Future BRCs may define advertisements for additive reserved permission schemes while preserving the complete BRC-100 core contract.

## Motivation

A person may first encounter their account through one application. When another application uses the same wallet, “Continue with your Pocket Photos Account” can explain continuity more clearly than introducing an unfamiliar wallet product or ecosystem name. Later, the person may understand it as their “Metanet Account”. The application needs the current term from the wallet serving that person, rather than inferring it from a software vendor or maintaining its own history of other applications.

The wallet decides when a label changes. Renaming after a second, third, or fourth application is an illustrative policy, not an interoperability requirement. A vendor-neutral syntax also allows later specifications to add independently documented metadata without adding fields to the wallet API.

## Scope

The uppercase terms MUST, MUST NOT, SHOULD, SHOULD NOT and MAY have the meanings in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). The Specification and Compatibility sections define requirements for implementations adopting this proposal. Examples and implementation observations are informative.

This proposal defines string syntax, parsing, display-label semantics and extension naming. It does not define a new BRC-100 method, argument, result field, call code, transport, account identifier, authentication mechanism, renaming API, provider-discovery mechanism, or method negotiation. It does not standardize how wallets count applications, choose branding, synchronize labels between devices, or translate account names.

## Specification

### 1. Result and wire grammar

An adopting wallet returns its string in the existing `version` field:

```json
{"version":"example-1.2.3; account-label=Pocket%20Photos%20Account;"}
```

The schematic form is `vendor-<major>.<minor>.<patch>; key=value; key2=value2;`. Angle brackets denote placeholders, not wire characters. The semicolon separates attributes; it is not part of the patch number. This resembles a product identifier with parameters, but does not import HTTP User-Agent grammar or HTTP header semantics.

The following [ABNF](https://www.rfc-editor.org/rfc/rfc5234) operates on the ASCII bytes of the decoded result string, after the existing transport has delivered it. Numeric terminals specify case exactly. `DIGIT` and `SP` come from RFC 5234.

```abnf
version-string = base [ ";" [ SP attribute *( ";" SP attribute ) [ ";" ] ] ]
base           = vendor "-" core-version
vendor         = alnum *( alnum / "." / "_" / "-" )
core-version   = number "." number "." number
number         = "0" / ( %x31-39 *DIGIT )
attribute      = key "=" value
key            = lower *( lower / DIGIT / "-" )
value          = 1*( unreserved / pct-encoded )
unreserved     = alnum / "-" / "." / "_" / "~"
pct-encoded    = "%" HEXUP HEXUP
HEXUP          = DIGIT / %x41-46
alnum          = %x41-5A / lower / DIGIT
lower          = %x61-7A
```

Additional requirements:

* `base` MUST be 7–30 ASCII bytes. The vendor is a case-sensitive software/product token, not the user-facing account label. Hyphens are permitted within the vendor: the **last hyphen in `base`** separates it from the three-part version.
* The version uses the three nonnegative integers of a [SemVer 2.0.0](https://semver.org/spec/v2.0.0.html) release core, without leading zeroes. Prerelease and build suffixes are outside this grammar. It describes the wallet software, not the BRC-100 interface version or account state. Implementations MUST NOT infer method support, permissions or security properties from it.
* The complete string MUST be at most 2048 ASCII bytes, with at most 24 attributes. Each key MUST be 1–64 bytes. Each decoded value MUST be 1–256 UTF-8 bytes. These limits include unknown attributes. A sender MUST omit optional attributes as necessary to stay within the limits, never cut through an encoded value.
* Keys are case-sensitive and lowercase only. Values are case-sensitive unless their defining BRC specifies otherwise. A key MUST appear at most once; repeated keys invalidate the attribute section, even when their values are identical. Attribute order has no meaning.
* There MUST be exactly one ASCII space after every nonterminal semicolon and no spaces around `=`. There is no leading or trailing whitespace, tab, newline, folding, quoted-string form or backslash escape. A final semicolon is optional for readers. Senders MUST include it when attributes are present and MUST emit just `base` when none are present.
* Readers MUST also accept `base;` as an empty attribute section. Consecutive semicolons are invalid.

### 2. Attribute value encoding

All attribute values are Unicode text encoded as UTF-8 under [RFC 3629](https://www.rfc-editor.org/rfc/rfc3629). Encode each byte outside the ASCII `unreserved` set as `%HH`, using uppercase hexadecimal. Unreserved bytes MUST appear literally, not percent-encoded. Thus a space is `%20`, a semicolon `%3B`, an equals sign `%3D`, a percent sign `%25`, and a literal plus sign `%2B`. Plus does not represent a space. For example, `Café Account` becomes `Caf%C3%A9%20Account`.

Readers MUST split the ASCII string into its base and attribute pairs **before** decoding values, then percent-decode each value exactly once. Readers MUST reject malformed escapes, lowercase hex escapes, percent-encoded unreserved bytes, and invalid UTF-8, including overlong sequences and surrogate code points. JSON, HTML, URI, or backslash unescaping MUST NOT be applied to the decoded value. `%2520` therefore represents the three-character text `%20`, not a space. Each attribute's specification MAY further constrain decoded text but MUST NOT change this encoding.

### 3. Parsing, unknown attributes and failure

A consumer adopting this proposal MUST apply the full-string bound before parsing, validate the base, then validate the complete attribute section and every value's encoding and bounds. It MUST NOT use a partially parsed attribute map. Any syntax, encoding, duplicate-key or generic bound failure makes **all attributes unavailable**; an independently valid base MAY still be used as software-version metadata. A string over 2048 bytes, an invalid base, or a non-string result supplies no metadata under this proposal. Parsing failure MUST NOT be converted into an authentication failure or evidence of missing wallet methods.

After generic validation, unknown keys MUST be ignored without changing the meaning of known keys. A known attribute whose decoded text fails its own semantic constraints is treated as absent; other valid attributes remain usable. Consumers MUST NOT display the raw string or unknown values as an account label. Implementations SHOULD use a map with no inherited keys, or equivalent storage that treats every key as data.

Legacy strings that do not match this profile remain opaque legacy version results: consumers MUST NOT guess an account label from them. Failure of this metadata parser alone MUST NOT cause a connected wallet to be rejected, switched, or asked to authenticate. A wallet call or transport failure retains its ordinary error semantics; this parser does not suppress those errors.

### 4. The `account-label` attribute

| Property     | Definition                                                                                                           |
| ------------ | -------------------------------------------------------------------------------------------------------------------- |
| Key          | `account-label`                                                                                                      |
| Meaning      | The current user-recognized name for the account served by the responding wallet in the current application context. |
| Presence     | Optional; at most one current label. Omission means no label is supplied.                                            |
| Decoded form | A complete display noun phrase, such as `Pocket Photos Account` or `Metanet Account`, not a sentence or a command.   |
| Size         | 1–128 UTF-8 bytes after decoding and NFC normalization.                                                              |
| Authority    | The responding wallet's current presentation policy, including any user preferences it honors.                       |

A sender MUST normalize the label to Unicode [NFC](https://www.unicode.org/reports/tr15/) before UTF-8 encoding. A consumer MUST treat a non-NFC label as absent. It MUST also treat a label as absent if it contains a Unicode control character (General Category `Cc`), a separator other than U+0020 SPACE (categories `Zs`, `Zl`, `Zp`), any of U+061C, U+200E–U+200F, U+202A–U+202E or U+2066–U+2069 (bidirectional controls), or a leading or trailing U+0020 SPACE. A label MUST contain at least one character outside General Categories `C`, `M` and `Z` that does not have the `Default_Ignorable_Code_Point` property. These property tests use the [Unicode 17.0.0 Character Database](https://www.unicode.org/Public/17.0.0/ucd/); marks, whitespace and default-ignorable characters alone are insufficient. Ordinary right-to-left text, combining characters and emoji sequences remain permitted when accompanied by such a character. Consumers MUST validate rather than trim, truncate or repair invalid incoming labels into a different name.

The label identifies an **account concept**, not the person's legal name, username, public key, certificate, unique account ID, balance, installed-app list or wallet product. Wallets MUST NOT include those personal identifiers, secrets or usage histories in this attribute. Different people and different wallets MAY return the same label. A wallet MAY omit the attribute to avoid disclosure, when no account is selected, when locked, or when it cannot determine an appropriate label. Omission or presence MUST NOT be treated as proof that an account exists, is unlocked, or is authenticated.

The wallet MUST supply only the current label, never a history or alias list. It MAY change that label without changing `base`. The wallet SHOULD show a new label in its own account UI so that the user can recognize it. An application MUST NOT write a label back to the wallet, migrate an account, or infer account replacement merely because this string changes. Conversely, an unchanged label does not prove that the account stayed the same.

Labels MAY reflect the wallet's existing language preference. This proposal adds no locale argument, locale attribute or translation negotiation. Consumers SHOULD preserve the supplied phrase and localize their surrounding UI. Applications MUST NOT translate or append `Account` on the assumption that the phrase omits it.

### 5. Consumption, freshness and privacy

An application SHOULD request the label through `getVersion({})` on the wallet connection it will actually use, when it presents an account-continuation surface. It MUST NOT query every discoverable wallet for account labels to build an account inventory. A discovery readiness probe MUST NOT cause a wallet to unlock, prompt for new permissions, retrieve an identity key, or establish an account merely to produce metadata. Wallets MUST omit context-sensitive labels when disclosure would require such interaction. Existing originator and permission boundaries remain in force.

The label is a snapshot, not a durable identifier. A consumer MAY retain it in memory for the current connected account context. It MUST discard it on known disconnection, account change, sign-out, or provider switch, and MUST ignore a late response from a retired context. It SHOULD refresh before presenting a new continuation flow; it MUST fall back to generic copy when that refresh fails or supplies no valid label, rather than displaying a label from a previous context. This proposal defines no subscription, polling interval or persistent cross-device cache. Legacy transports might not report account changes: consumers cannot promise immediate freshness in that case.

Suitable copy is `Continue with your` followed by the inert label. If unavailable, use neutral copy such as `Continue with your account`. The fallback MUST NOT assume a particular vendor, application or “Metanet” branding. A consumer MUST NOT require a label to use the wallet or silently choose a provider on its basis.

Consumers MUST render labels as untrusted plain text, with bidirectional isolation from surrounding text, never as HTML, Markdown, a URL, executable code or an instruction. They MAY visually elide a valid long label without changing its stored value. They MUST keep application-owned action text and wallet/connection provenance distinguishable; a label is not verified branding. Unicode normalization and character restrictions do not prevent confusable names or misleading claims.

The label can disclose an earlier application relationship; changing labels or combining multiple attributes can also fingerprint users. Wallets SHOULD choose common, nonunique labels and disclose only the minimum appropriate to the requesting context. Applications MUST NOT send labels or complete version strings to analytics, unrelated servers or logs by default, and MUST NOT use them for cross-application correlation. Encoding is not encryption. Ordinary transport protections and user sharing decisions still apply.

### 6. Future attribute specifications

`account-label` is the sole attribute defined here. Future shared attributes MUST be proposed as new BRCs through the repository's ordinary contribution process. No separate registry service or approval authority is introduced.

To avoid collisions, a future attribute key MUST be `brc` followed by its assigned positive BRC number in decimal without leading zeroes, a hyphen, and a descriptive lowercase suffix; for example, the schematic `brc<number>-<name>`. The entire key still obeys section 1. `account-label` is the exception reserved by this proposal. A BRC may define several distinct keys under its number. Authors MUST check existing proposals and assigned keys, and update provisional keys if their BRC number changes before adoption. Unprefixed names and another BRC's prefix MUST NOT be claimed by an extension.

Each extension MUST specify its exact key, purpose, decoded syntax and semantics, bounds within this profile's limits, optionality and absence behavior, case/normalization rules, update/cache behavior, privacy and display implications, and valid and invalid examples. It MUST reference this base encoding, single-occurrence rule and unknown-key behavior. Incompatible semantics require a new key; an extension MUST NOT reinterpret an existing key or change the outer grammar. Experimental proposals can be reviewed before adoption, but their provisional keys are not stable interoperability commitments.

An attribute MUST remain optional descriptive metadata: consumers unaware of it must remain able to use the wallet normally. Extensions MUST NOT turn these strings into BRC-100 method-support negotiation, authentication proofs, spending authority, permission grants or mandatory security checks. A proposal needing such behavior needs an appropriate separate protocol, not an attribute that old consumers silently ignore.

Future BRCs MAY define attributes advertising support for additive permission schemes under [BRC-98](/wallet/0098) (protocol IDs), [BRC-99](/wallet/0099) (basket IDs), or [BRC-111](/wallet/0111) (action labels). These standards reserve identifiers beginning with `p` followed by a space-free scheme/module ID and the scheme-defined remainder. Their syntax, dispatch rules and identifier bounds remain authoritative. An advertisement's specification MUST distinguish the namespace and exact scheme/module ID, identify the defining specification, and define freshness and omission behavior. A scheme ID is not necessarily a BRC number; the BRC-number prefix on an attribute key identifies the attribute specification, not the advertised scheme's identifier.

Such advertisements are optional hints about an additive extension, not negotiation of the core interface. **All 28 BRC-100 methods remain mandatory**, with their existing argument/result shapes, ordinary non-scheme semantics and security/authorization guarantees, subject only to this proposal's explicit `getVersion` string-content extension. Advertising a scheme MUST NOT make an ordinary operation depend on that scheme or authorize an operation. When an application explicitly invokes a reserved identifier, the wallet MUST still determine actual scheme support, reject unsupported reserved schemes, and apply the governing BRC and that scheme's defined permission/operation rules. For example, BRC-111 permits module-specific label permission checks and query handling; an advertisement neither performs those checks nor overrides their outcome.

Absence of an advertisement MUST NOT be treated as proof of non-support, since metadata is optional and bounded; presence MUST NOT be treated as proof of current authorization, successful execution or safe code. Consumers MUST still handle normal wallet results and errors and MUST NOT silently substitute different operation semantics when an advertised scheme is unavailable. Scheme-support inventories can fingerprint wallets: an extension MUST specify its disclosure/privacy policy and MUST NOT require publishing user-specific grants or usage history. No scheme-advertisement key, list encoding or particular scheme is defined by this proposal; those require future BRCs under the process above.

## Compatibility and related standards

### BRC-100

[BRC-100](/wallet/0100) defines a 7–30-character `[vendor]-[major].[minor].[patch]` version string. This proposal extends that string with optional attributes and replaces its complete-string maximum with 2048 ASCII bytes. The base remains 7–30 bytes. No other result field, method, request argument, call code or transport framing changes.

New consumers MUST accept base-only results without requiring attributes. Wallets MAY return the extended format without a separate opt-in or support-discovery exchange. Consumers adopting this proposal MUST accept strings within the new limit and handle attributes as specified above.

In practice, applications and wallets have generally treated the old 30-character limit loosely, and applications rarely depend on it. These early days are a good time to lift that limit and make room for useful metadata. Older applications, SDKs or wallets that enforce the original length or base-only format may throw when given an extended string; their validators need to accept the new format and limit. This is a compatibility caveat, not a requirement for coordinated adoption or a new negotiation protocol.

### Draft BRC-185

[Draft BRC-185, PR #272](https://github.com/bsv-blockchain/BRCs/pull/272), section 1, explicitly permits separately adopted additive `getVersion` string-content profiles, including this proposal, while retaining the complete mandatory BRC-100 core interface. That exception reconciles its unchanged-result wording with this proposal's syntax and length extension; it does not authorize changes to other method behavior, result shapes or transport framing. Future BRC-defined advertisements for additive BRC-98/99/111 schemes are permitted, but negotiation of a reduced core method set and metadata-based permission grants remain prohibited. BRC-185 support alone does not imply that an implementation's version-string validator has been updated.

Its descriptor's `name` is a product name, `instanceLabel` distinguishes provider instances, and `instanceId`/`continuityKey` support application-scoped provider continuity. None is `account-label`. Consumers MUST NOT replace those fields with this label, merge candidates on matching labels, or use labels as remembered-choice keys. Account labeling belongs to the selected wallet context, not public provider discovery. If BRC-185 is used, its connection epochs and account-change invalidation remain authoritative: a mere display rename is not a change of the bound account, and a real account switch cannot be hidden by retaining the same label.

### Identity and historical versioning

[BRC-56](/wallet/0056) identifies its old `0.3.x` version convention as historical and directs new implementations to BRC-100. This proposal does not restore that convention. [BRC-189](/wallet/0189) addresses identity keys, certificates, public discovery and contacts; this label is not an identity attribute published by that protocol. [BRC-116](/wallet/0116) continues to govern wallet permissions. No label or future metadata attribute bypasses those boundaries.

## Examples and conformance cases

The following strings are complete `version` values, without JSON quoting. `example` is illustrative vendor text.

| Input                                                               | Required result                                                                                                            |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `example-1.2.3`                                                     | Valid base; no label.                                                                                                      |
| `wallet-brc100-1.0.0`                                               | Valid base with vendor `wallet-brc100`; no label.                                                                          |
| `example-1.2.3;`                                                    | Accepted empty attribute section; senders omit this semicolon.                                                             |
| `example-1.2.3; account-label=Pocket%20Photos%20Account;`           | Label `Pocket Photos Account`.                                                                                             |
| `example-1.2.3; account-label=Metanet%20Account`                    | Label `Metanet Account`; reader accepts missing final semicolon.                                                           |
| `example-1.2.3; account-label=Caf%C3%A9%20Account;`                 | Label `Café Account`, in NFC.                                                                                              |
| `example-1.2.3; account-label=Photos%3B%20Family%3DShared;`         | Label `Photos; Family=Shared`; decoded delimiters stay inside the value.                                                   |
| `example-1.2.3; account-label=Photos%2520Account;`                  | Label `Photos%20Account`; no second decoding.                                                                              |
| `example-1.2.3; future-key=value; account-label=Metanet%20Account;` | Ignore the syntactically valid unknown key; use label. `future-key` tests reader behavior, not an allocated extension key. |
| `example-1.2.3; account-label=A; account-label=B;`                  | Duplicate key: all attributes unavailable.                                                                                 |
| `example-1.2.3;account-label=A;`                                    | Missing separator space: all attributes unavailable.                                                                       |
| `example-1.2.3; Account-Label=A;`                                   | Uppercase key: all attributes unavailable.                                                                                 |
| `example-1.2.3; account-label=A;;`                                  | Consecutive semicolons: all attributes unavailable.                                                                        |
| `example-1.2.3; account-label=;`                                    | Empty encoded value: all attributes unavailable.                                                                           |
| `example-1.2.3; account-label=%41;`                                 | Encoded unreserved byte: all attributes unavailable.                                                                       |
| `example-1.2.3; account-label=Caf%c3%a9;`                           | Lowercase escape hex: all attributes unavailable.                                                                          |
| `example-1.2.3; account-label=%FF;`                                 | Invalid UTF-8: all attributes unavailable.                                                                                 |
| `example-1.2.3; account-label=A%0AB;`                               | Generic value parses; label is absent because it contains a control character.                                             |
| `example-1.2.3; account-label=%20Account;`                          | Label is absent because it starts with a space.                                                                            |
| `example-1.2.3; account-label=Cafe%CC%81;`                          | Label is absent because it is not NFC.                                                                                     |
| `example-1.2.3; account-label=%EF%B8%8F;`                           | Label is absent because a variation selector alone is insufficient.                                                        |
| `example-01.2.3; account-label=A;`                                  | Invalid base: no metadata under this profile.                                                                              |
| `0.3.80`                                                            | Opaque legacy result, no account-label inference.                                                                          |

Readers must also cover the stated size limits: exactly 2048 bytes is permitted if all other constraints hold; 2049 is not. Exactly 24 unique attributes and a 256-byte decoded unknown value are permitted; 25 attributes or 257 decoded bytes invalidate the attribute section. A 128-byte valid label is permitted; 129 bytes makes that label absent. A generic failure in an unknown attribute invalidates the whole attribute section just as it would for a known attribute.

Implementations should also check base-only fallback, a label change with unchanged software version, provider/account switching with a delayed old response, omission while locked, localized labels, inert rendering of markup-like text and right-to-left text, and continued operation when metadata parsing fails. These are interoperability cases, not claims that existing wallets already pass.

## Implementation notes

The existing [toolbox Wallet](https://github.com/bsv-blockchain/ts-stack/blob/52fc6465c2e465cb3820c03d896fb6e2ba0dc691/packages/wallet/wallet-toolbox/src/Wallet.ts) returns `wallet-brc100-1.0.0`. The base grammar preserves that form, including hyphens in the vendor.

Some SDK versions enforce the old 7–30-byte bound in [result validation](https://github.com/bsv-blockchain/ts-stack/blob/52fc6465c2e465cb3820c03d896fb6e2ba0dc691/packages/sdk/src/wallet/WalletResultValidation.ts) and the [binary transceiver](https://github.com/bsv-blockchain/ts-stack/blob/52fc6465c2e465cb3820c03d896fb6e2ba0dc691/packages/sdk/src/wallet/substrates/WalletWireTransceiver.ts). Implementations adopting this proposal should update those checks wherever they occur. This PR specifies the format; it does not include SDK or wallet implementation changes.

## References

* [BRC-100: Wallet-to-Application Interface](/wallet/0100), [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116), [BRC-56: Historical Wallet Interface](/wallet/0056), and [BRC-189: Identity, Certificates, Discovery, and Personal Trust](/wallet/0189).
* [Draft BRC-185: Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections](https://github.com/bsv-blockchain/BRCs/pull/272).
* [RFC 5234: ABNF](https://www.rfc-editor.org/rfc/rfc5234), [RFC 3629: UTF-8](https://www.rfc-editor.org/rfc/rfc3629), [Unicode Standard Annex #15: Normalization Forms](https://www.unicode.org/reports/tr15/), and [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).


# 1Color Wallet Module

Ty Everett (<ty@projectbabbage.com>), David Case (<dcase@opl.dev>)

Contributors: John Calhoun, Bridget Doran, Darren Kellenschwiler.

## Abstract

This specification defines the canonical `color` wallet module for fungible assets under [1Color (BRC-162)](/tokens/0162), including the [BRC-161](/tokens/0161) encoding of the same token model. It combines counterparty-derived custody, per-asset baskets, overlay-verified admission, amount-aware spending consent, input selection, change, issuance, authority operations, burns and composable multi-asset swaps. It supports arbitrary foreign scripts and both complete and partially signed transactions through the existing [BRC-100](/wallet/0100) interface.

P2PKH custody and overlay evidence are the baseline. Required processing profiles in the BRC-162 payload can add contract-resource rules or specify alternative custody and evidence procedures under the bounded extension contract below. This BRC defines that contract, not any asset-specific business meaning. [BRC-209](/wallet/0209) defines the explicitly declared minimal resource mode below.

The 1Color name and its exclusive conformance use follow BRC-162. Implementers may use independent product branding; all `color` protocol, basket, label, topic, lookup and capability identifiers below remain unchanged.

MUST, MUST NOT, SHOULD and MAY express requirements for conforming implementations. A wallet without this module MUST reject its reserved identifiers. Module support does not reduce the mandatory core wallet interface or authorize an operation.

## Asset identity, amounts and custody

An `assetId` is the canonical BRC-162 API token ID: a lowercase display-order transaction ID, an underscore, and a decimal deploy uint32 output index without leading zeroes, for example `<64 hex characters>_0`. Its binary prefix representation, including the legacy nonzero-index form, is exactly BRC-162's. The wallet MUST identify assets by this issuance outpoint, never by symbol, icon, decimals or a caller label. BRC-161 JSON and BRC-162 binary outputs with the same ID represent the same asset; BRC-162's binary-prefix precedence applies.

Amounts MUST be processed as exact integers in the domain specified by the applicable token encoding. BRC-162 value outputs carry 1 through `2^64 - 1` units; zero designates authority, not an empty value balance. JSON quantities in this module's metadata MUST be decimal strings without leading zeroes. Sums MUST use exact arithmetic that cannot overflow when adding multiple maximum-sized inputs. Deploy `dec` affects display only; there is no conversion between token units and satoshis, and no floating-point accounting.

Baseline wallet custody requires an admissible BRC-162 prefix, with its optional payload, followed by an ordinary P2PKH remainder controlled by the module-derived wallet key. BRC-161 equivalents MAY be received and spent in the same managed baskets; the wallet MUST recognize and validate both encodings. New wallet-generated issuance, transfer and change outputs MUST use BRC-162. Arbitrary valid scripts remain supported for foreign inputs and destinations; the custody restriction determines which outputs the wallet can automatically manage without a supported contract-resource profile. Such a profile may establish a protected signing role without establishing unrestricted ownership of the output's full amount.

## Required processing profiles

### Discovery and enforcement

Before authorizing a signature, selecting or crediting an output, computing an available balance, or counting foreign consideration, the wallet MUST authenticate the asset's original deploy and read its BRC-162 `req` map. It MUST also read the applicable binary payload of every involved source and destination. BRC-161 JSON carries no processing-profile payload: a legacy deploy has no required profiles, and a JSON output supplies no `ext` state. Extra JSON members cannot change these facts. Requirements inherited from a BRC-162 deploy still apply to later JSON outputs. Binary-prefix precedence applies before choosing an encoding. A cached descriptor is usable only when bound to the verified deploy outpoint and identical bytes. Caller metadata, an overlay's base-admission result, or an omitted output payload cannot substitute for this step. New deployments use the proposed deploy's requirements until their final asset ID is fixed.

An absent or zero-byte payload has no defined attributes. A nonzero payload that is not a strictly valid DAG-CBOR map likewise has no defined attributes under BRC-162; the wallet MUST NOT recover partial `req` or `ext` data from invalid, noncanonical or duplicate-key encodings. An opaque deploy payload declares an empty `req` map. A later opaque output still inherits and must satisfy its authenticated deploy's requirements, treating that output's `ext` as absent. A required profile may reject that absence under its own state rules. In a valid payload map, malformed `req` or `ext`, or a non-deploy `req` that differs from the deploy, MUST fail the affected operation. Advisory keys outside `req` and `ext` remain ignorable. `ext` MUST be a map with valid profile identifiers; its values are interpreted only by the applicable required profiles. These wallet-processing rules do not change BRC-161/162 base validity.

The wallet MUST enforce every required profile's exact version and parameters through trusted implementation code. It MUST validate relevant antecedent state and transition invariants using that profile's authenticated history, checkpoint or attestation rules. Base-indexer admission alone cannot establish those facts. This does not require another wallet's historical local consent records. Unsupported versions, unavailable evidence, incompatible requirements or inability to establish a required fact MUST fail before signing or mutation of credited balances. It MUST NOT partially apply a requirement set, fall back to baseline handling, or use a generic protocol grant to bypass it. A profile identifier does not authorize automatic code installation or establish a trusted service. Support is chosen by the wallet; authority to perform an operation still comes from the user.

Requirements remain attached to the asset across transfers and encoding changes. A required profile MUST specify validation of its `ext` state, including absence, initialization, each transition, and any authorized removal of that state. Removing state bytes cannot remove the asset's immutable requirements. Unknown advisory data cannot demand privileges or weaken required checks. Wallets may display an unsupported asset as unverified information, but MUST NOT represent it as a validated holding, available balance or accepted consideration.

### Profile contract and common guarantees

A required profile MUST define deterministic rules for its exact version and parameters: applicable operations; input/output and state recognition; necessary evidence and its trust assumptions; protected signing roles; permitted state transitions; authorization facts; and failure behavior. It MUST distinguish validated facts from application assertions and unresolved conditions. A profile that adds restrictions applies alongside all other requirements. Conflicting rules fail rather than selecting the most permissive interpretation.

A profile may explicitly replace the baseline **custody recognition** or **overlay evidence and submission** procedure. It MUST identify the replaced procedure and establish the corresponding facts: authenticated source and asset lineage, current spendability under an accepted evidence policy, transaction admission and completion, or the actual control and limitations of a signing role. All other BRC-207 rules continue to apply. In those two cases, references below to P2PKH ownership or overlay lookup/admission/submission mean the profile's specified replacement for the affected operation. Absence of a replacement means the baseline procedure is mandatory. The attested-admission profile below is one explicit replacement; a caller's undeclared "no overlay" or "identity" flag has no meaning. A replacement must address freshness, conflicts, unavailability, disagreement and settlement. Historical admission cannot be presented as current global unspentness: offline acceptance is instead a declared, user-authorized evidence and risk policy. Throughout this BRC, including selection, netting, inventory, balances and internalization, baseline overlay requirements yield only to the exact replacement selected by `req`.

Except for the resource-only handling explicitly defined below, profiles MUST NOT waive BRC-161/162 identity, amount, supply or authority rules. Profiles MUST NOT waive actual consensus script validation, key-namespace isolation, exact signature commitment analysis, user authorization, reservation/exposure tracking, or the processor coordination required below before any non-final signature release. They MUST NOT claim that off-chain policy changes Bitcoin consensus or retracts a released signature. Their guarantees and assumptions must be supplied to wallet policy and display. Where several profiles replace the same procedure, the wallet MUST establish that their procedures and resulting facts agree, or fail.

Additional evidence can use optional `profileData` in an action plan, signature envelope or receipt instruction. It is a JSON object keyed by required profile identifier. Each entry is a map from the involved canonical asset ID (or `ISSUE` for an unresolved deploy) to data whose schema is defined by that asset's exact profile version. Extra profile or asset identifiers, duplicate members or schema violations MUST fail. This scoping supports multiple assets using different versions or parameters of the same profile in one action. The profile MUST bind evidence to the relevant asset, outpoints, state, operation and transaction context; caller data is not itself proof. Wallet-fetched evidence is subject to the same checks. Message and receipt bounds follow the applicable transport rules below. This field does not change the meaning of `tx` or relax the BRC-176 `tokenProofs` transport rules.

### Attested admission and offline token graphs

This section defines the optional required profile `https://brc.dev/207/attested`, version `1`. An authenticated BRC-162 deploy MUST select it before these rules replace baseline current-overlay checks for that asset. Supporting ordinary BRC-207 does not by itself support this profile. An undeclared asset, including a legacy JSON deploy, retains baseline handling. This is a conserved-token profile, not BRC-209's resource-only mode; those two modes MUST NOT be combined in one asset's `req`. A transaction may combine separately declared attested assets, BRC-209 resources and baseline assets; each leg retains its own rules.

Its `params` is exactly an object with these optional members (unknown members fail): `trustMode`, one of `any`, `same`, `all`, `identity` (default `any`); `identityKey`, a valid lowercase compressed secp256k1 key, required exactly in `identity` mode; and `evidenceProfile`, a profile URI (absent selects the default construction below). An empty object selects the defaults. `null` is invalid. This profile defines no `ext` state; its own `ext` entry MUST be absent.

For example, the following DAG-CBOR payload is shown as JSON:

```json
{"req":{"https://brc.dev/207/attested":{"version":1,"params":{"trustMode":"any"}}}}
```

The declaration permits independently authenticated historical admission anchors plus local verification of all intervening token transitions. It replaces live overlay admission/spendability as a prerequisite to signing, receipt acceptance, selection and onward spending for these IDs. It does not assert that an offline anchor or descendant remains globally unspent. Acceptance explicitly assumes the configured authority and offline-conflict risk; Bitcoin sources/scripts, token quantities, supply, authority, required companion profiles, consent and reservations remain mandatory. No claim of confirmed or overlay-settled value may be inferred from this acceptance.

#### Trusted identities and policy snapshots

For list-based modes, take an atomic snapshot of the user's selected [BRC-189](/wallet/0189#11-user-selected-trust-and-identity-interpretation) Trusted Entities List: the distinct valid `identityKey` values of `trustedCertifiers` in that wallet/profile. This profile explicitly assigns those entities authority for this admission purpose when the user enables the profile's offline policy. The wallet MUST disclose that coupling and obtain consent; an application's supplied list, website or certificate does not modify it. BRC-189's certificate weights and `trustLevel` threshold do not apply to this admission rule.

For one asset graph in one decision, let each terminal anchor's signer set be the identities with valid statements for that exact anchor:

| Mode       | Required coverage                                                                                                                                                     |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `any`      | Every anchor has at least one signer in the snapshot; different anchors may use different entities.                                                                   |
| `same`     | At least one identity in the snapshot signs **every** anchor. This is single-authority consistency across the graph, not a requirement that every listed entity sign. |
| `all`      | Every identity in the snapshot signs every anchor. This is all-entities unanimity.                                                                                    |
| `identity` | The deploy's exact `identityKey` signs every anchor, whether or not it is in the local list. No list-membership or weighted-threshold test is made.                   |

An empty snapshot fails all list-based modes; `all` must not succeed vacuously. Do not count signatures from derived child keys as separate entities. For mixed assets, apply each asset's own descriptor and coverage test; authority for one asset is not authority for another. `identity` bypasses only list selection, not signature verification, user consent or other required profiles. The fixed key cannot silently rotate; a future declared profile may specify authenticated rotation semantics. The immutable descriptor cannot be overwritten by metadata.

Capture the list/configuration version with the decision and recheck it before committing or releasing a signature. A concurrent change requires reevaluation. New spends and receipts use the current snapshot; loss of required trust or evidence makes retained outputs unavailable/restricted pending reevaluation. Keep prior decisions and released exposure for audit and settlement; a trust change neither undoes a Bitcoin spend nor authorizes reclamation of `never` payments. Wallet-specific limits may restrict age, amount, chain depth or counterparties further, with explicit errors; they cannot silently weaken the declared coverage or another required profile.

#### Default attestation statement and signature

Use [BRC-175](/payments/0175)'s compact admission message:

```
1color-admit:v1:<topic>:<txid>:<outputs>
```

`topic` is independently derived as `tm_color_<assetId>`. `txid` is the 64-character lowercase display-order TXID. `outputs` is a nonempty, unique, ascending list of uint32 output indices in canonical decimal, joined by commas. There are no spaces or newline. Each named output MUST exist and carry this asset. An example of the exact message shape is:

```
1color-admit:v1:tm_color_c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0:05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931:0,2
```

A JSON attestation is exactly `{txid, outputs, signer, signature}`: `outputs` is an array of integer indices in that same order, `signer` is the authority's lowercase compressed identity key, and `signature` is lowercase strict-DER ECDSA hex with no transaction sighash byte. Both valid low-S and high-S forms MUST be accepted; duplicate signatures do not add coverage or authority.

The signing protocol is exactly `[2, "color admission attestation"]`, `keyID` exactly `"1"`, and counterparty `"anyone"`, using the authority's ordinary BRC-42/43 identity. Sign SHA-256 of the UTF-8 message **once**, as BRC-3 `createSignature({data, protocolID, keyID, counterparty:"anyone"})` does. The invoice is `2-color admission attestation-1`. A verifier publicly derives the authority's child using the anyone private scalar 1 and that invoice, or uses BRC-3 `verifySignature` with this tuple, `counterparty:signer` and `forSelf:false` in an anyone verifier. It MUST NOT use its own ordinary child, the bare identity key or a caller-supplied child. This is an application attestation protocol, not a `p color` or BRC-29 transaction-signing key. Signers SHOULD use RFC 6979 nonces. Unknown/duplicate JSON fields, noncanonical identifiers, invalid points or signatures fail.

The authority asserts that the exact named outputs were validly admitted to this asset's topic under BRC-161/162 quantity, lineage and authority rules. It MUST validate those facts before signing. The asset ID commits to its original deploy and immutable `req` through the deploy TXID; the attested TXID commits to the outputs. Verify both sets of bytes. This statement checkpoints token validity through those outputs, not Bitcoin inclusion, current unspentness, future acceptance, recipient identity or companion-profile state unless that profile explicitly accepts this evidence. An omitted output is not attested; the list is not an assertion that every other output was rejected.

The default message has no timestamp or expiry. It makes a historical claim without an age guarantee. A wallet may limit retention or offline exposure, but MUST NOT describe a locally recorded first-seen time as a signed issuance time. A profile needing authenticated age, network-domain separation or revocation MUST declare a replacement with those semantics. Bitcoin evidence always has to match the wallet's actual chain; a signature alone establishes no chain membership. Invalid or known contradictory evidence cannot be ignored to manufacture coverage.

BRC-175 carries these signatures in bundles and defines the optional BRC-22 response fields. Other carriers or lookup query forms MAY carry them too; the BRC-24 `output-list` shape is unchanged. The signer need not be the contacted host. No endpoint or supplied signer identity imports trust or executable code.

#### Evidence carrier and complete token frontier

For each resolved ID, default evidence is supplied under `profileData["https://brc.dev/207/attested"][assetId]` as exactly: `{declarationTx, transactions, attestations}` and optional `submissions` and `lifecycles`. `declarationTx` is lowercase raw transaction hex matching the asset ID and original binary deploy at output zero; decode and authenticate its `req` before selecting policy. `transactions` is a topologically ordered array of distinct lowercase raw transaction hex strings containing the token history/source bodies needed below. `attestations` is an array of the objects defined above. Different signers may attest the same TXID; deduplicate coverage by identity and exact outpoint, not TXID alone. Hex must decode to exactly one complete transaction. No URLs, executable material or TXID-only substitutes are allowed in these arrays. The ordinary action/receipt `tx` remains BRC-95 Atomic BEEF.

`submissions`, if present, is an array of exact `{txid, offChainValues}` objects with unique canonical TXIDs and lowercase even-length hex for the opaque BRC-22 off-chain bytes for **this asset topic**. A BRC-175 adapter maps its submission entries here. Authenticate required contents through the applicable profiles and bind them to their transactions; this field cannot grant trust. The wallet MUST possess and persist all data needed to submit the tip and every unattested ancestor without the original sender. For the default profile alone, empty off-chain bytes suffice and may be synthesized. Companion profiles may require nonempty caller-supplied data; absence then fails acceptance. Newly constructed transactions acquire their submission entry after their TXID is fixed. `lifecycles` is an array of BRC-177 portable lifecycle objects for every unresolved protected root in the delivered graph. Verify and persist them under BRC-177; forward them unchanged when constructing descendants. Unknown non-protected upstream disposition is a disclosed risk, not a non-reclamation promise.

Verify the complete token graph for **each involved asset**, separately from Bitcoin/SPV validation:

1. Start with every token input whose validity the action needs, or each requested receipt output. Reconstruct TXIDs, outpoints, token ID/role, amounts and scripts from actual source bytes. An attestation cannot invent an output or rename an asset. Fully signed parent bytes are required before signing a child.
2. At an output with qualifying attestation coverage, check its source bytes, declaration binding and role; it is a terminal validity anchor. Otherwise expand its producing transaction and recursively validate **all** same-asset token inputs, including authority inputs. Apply the full token transition rules to the transaction, not just the output selected by the caller.
3. Every same-asset path MUST reach a qualifying attested anchor. A mined token parent alone is not a token-validity anchor. A missing parent, unanchored deploy, invalid transition, duplicate spend or conflicting representation fails. No same-asset branch may be omitted merely because another input supplies enough nominal quantity. Anchors may occur at different depths. For `same`/`all`, evaluate coverage across the entire resulting frontier, not per branch alone. Memoize shared ancestors: revisiting one transaction in a diamond-shaped DAG is valid. Two distinct graph transactions consuming the same outpoint are not.
4. Authenticate and enforce all other required profiles at every relevant state boundary. An attestation is not evidence of their state transitions unless their exact contracts say so. A known incompatible spend outside the validated graph, established contradictory claim, invalid Bitcoin source or failed companion rule defeats acceptance. Consuming an anchor in this very graph is expected and is not such a conflict. Picking a favorable signer is not conflict resolution. No universal online check is required by this default profile, but known contradictory facts cannot be ignored.
5. Independently validate ordinary Bitcoin sources, scripts, satoshis, fees and [BRC-67](/transactions/0067) / BEEF evidence for **all Bitcoin inputs**. The analogous recursion stops at accepted chain proofs for Bitcoin and at accepted authority signatures for token validity. A token attestation is not a Merkle proof. Non-token funding inputs need Bitcoin evidence, not token lineage. Reject cycles, inconsistent duplicate bodies and unsupported bounds.

For a new `ISSUE` deployment, its action-plan leaf is exactly `{transactions:[],attestations:[]}`: the proposed deploy supplies its declaration. Issuance may be constructed and signed under ordinary issuance authorization; it is not credited or used as a token-validity anchor until the completed deploy has the required attestation coverage. ISSUE receipt instructions preserve the original key tuple, but their profile evidence uses the resolved asset ID and full declaration bytes. A transfer cannot exploit ISSUE to omit ancestry.

Wallet-held evidence may be reused only after the same source, coverage and trust checks; request data must identify the required profile and asset. The caller may supply complete evidence through this carrier in action plans, signature envelopes and receipts. This is **not** a shortened BRC-176 packet: BRC-176 retains its own completeness rules. Ordinary `tokenProofs` cannot waive current admission for undeclared assets. Optional transport compression or content-addressed storage is implementation-specific and cannot alter verified bytes.

This profile's `customInstructions` strings use the same 16 MiB per-string and aggregate limit as BRC-209, measured in actual UTF-8 bytes after JSON/hex encoding. Bound before allocation, authenticate the declaration before applying the exception, and return explicit errors for lower host limits, missing evidence or excessive work. No partial balances or silently truncated graph may result.

#### Offline acceptance, balances and settlement

After complete consensus-final script validation, authenticated control, graph validation and user-authorized offline policy, the wallet MAY internalize and credit a receipt before broadcasting or overlay admission. It MAY select and sign its descendants online or offline under the same rules. `createAction` may chain validated staged parent outputs without first calling internalization for its own output. Cross-wallet receipt independently validates and durably records the received graph; one wallet's policy/grant is not another's consent. Incomplete or consensus-non-final subjects remain pending, never accepted receipts.

An unreserved accepted output uses `spendable:true`, `color_state_available` and the additional wallet-authored tag `color_offline` until actual current overlay admission is verified. Its units contribute to the existing available balance under this asset's declared policy. The wallet MUST distinguish deferred and settled portions to the user; a scalar balance means locally authorized spending capacity, not guaranteed settlement. Inventory without tags and balance queries alone do not attest settlement. A reserved parent and its child MUST never both count as available. Reserved, restricted and pending tags retain precedence; caller tags cannot create or erase `color_offline`.

Acceptance requires authorization for the complete unresolved dependency and settlement risk, including applicable expiry/reclaim policies. Wallets may use continuing grants and risk limits. A finite ancestor can invalidate downstream payments; missing lifecycle evidence cannot be described as `never`. Known external conflicts or lost trust make affected holdings unavailable, while preserving released signatures and all unaffected holdings. Local accounting correction cannot retract payments or reverse delivered goods.

Internalization records acceptance; it does not itself broadcast. The user's policy may authorize subsequent settlement by the wallet or receiving application. On an explicit online processing request, reconcile and process all missing ancestors in topological order and submit required asset topics under each applicable profile. Before publishing a new child, establish actual acceptance of its required ancestors; a dependency-aware processor may accept a package, but package submission alone is not acceptance. Failure of an ancestor prevents new child publication. Already published members stay published; retain per-TXID and per-asset pending/error state, retry exact bytes idempotently and remove `color_offline` only after the actual admission check succeeds. A profile may define another settlement procedure through the replacement contract below.

Normal BRC-177 finite or `never` batch lifecycles, DSAP, exact signature coverage, arbitrary-script checks and non-final processor coordination still apply. Satoshi prefunding must already be available when disconnected; neither an attestation nor a batch workspace grants access to an unreachable storage provider. Authoritative graph/reservation/exposure state MUST be durable before signed handoff; a planning lease is not the lifetime of a released payment.

#### Explicit future replacement

If `params.evidenceProfile` is present, the same deploy `req` MUST also contain that exact different identifier with its exact supported descriptor. That profile explicitly supplies the evidence schema, signature/authority selection, anchoring/graph validation, freshness, admission, balance and settlement semantics in place of this section's default construction. It MUST name each replaced procedure and define every affected fact and failure outcome; unspecified procedures keep their defaults. Trust-mode and identity parameters above MUST be absent with `evidenceProfile`, avoiding two competing authority selectors. Use the replacement's own asset-scoped `profileData` carrier; this profile's leaf is absent in that case. Unsupported replacement, cycles of delegation, incompatible companion requirements or ambiguous precedence fail closed.

This explicit declaration allows later specifications to use other signatures, quorums, trust roots, checkpoints or offline settlement models without changing the generic wallet methods or default construction. It does not authorize code installation, arbitrary runtime override, or weakening common Bitcoin, amount, authority, consent, exposure and non-final coordination guarantees. Other profiles may independently replace baseline procedures through the common contract; using this default profile is not mandatory for future designs.

### Minimal resource mode

[BRC-209](/wallet/0209) is the explicit exception for assets whose authenticated original declaration selects its single minimal resource mode. Only for those IDs, it replaces baseline token lineage, overlay, amount/supply/authority validation and accounting with declaration authentication and resource-scoped handling. It defines the `resource` plan operation, restricted custody and larger receipt-evidence limit. Throughout this BRC, baseline admission, selection, netting, token-operation and receipt requirements are subject to those exact replacements. This does not change BRC-161/162 base token validity or permit the same exceptions for any other ID.

All other required profiles remain enforceable. Common Bitcoin evidence and script checks, satoshi authorization, key isolation, signature commitments, resource consent, reservations and non-final processor coordination remain mandatory. Supporting this module does not require supporting BRC-209; an unsupported required profile fails. Ordinary profiles cannot use this exception without the authenticated BRC-209 declaration.

### Contract resources and authorization

A supported profile may recognize a module-derived key in an arbitrary locking script and identify the resource and signing role it protects. It MUST verify that role against authenticated script bytes and the actual execution context; the presence of a public key or caller-provided role name alone is insufficient. Existing source keys retain their original derivation tuples; successor keys follow their own authenticated remittance and profile rules. The profile MUST describe competing, joint, conditional and time-limited control where present. A signing role does not by itself establish that the wallet owns, can freely spend, or will receive the entire token amount.

For every use of a protected role, the wallet MUST obtain authorization for the validated resource effects, including exercise, state updates, delegation, encumbrance or loss of control, even when token quantities and satoshi net debit do not change. The facts include the asset and affected outpoints, exact input and output states/scripts, role being exercised, and any conditions on the result. Profile-specific operation names and claims require profile validation; the base plan's amount/authority operations remain required independently, except for the declared BRC-209 resource operation.

Wallets MAY implement continuing grants for arbitrary application-directed updates to named resources or roles. Narrower grants MUST be checked against facts the wallet can establish. Unsupported distinctions cannot be promised by a UI or inferred from application descriptions. A grant to change arbitrary resource state does not silently authorize token debits, burns, authority changes or satoshi spending outside the granted scopes. Generic application keys outside this namespace retain their ordinary protocol-permission semantics. Neither profile support nor disclosure alone constitutes user consent.

Contract resources belong in the existing per-asset value or authority basket, with their verified control and state recorded separately. An admitted, unreserved resource not eligible for baseline automatic selection MUST use `spendable:false` and the wallet-authored tag `color_state_restricted` in inventory. Reserved or pending status takes precedence. It contributes no available balance and MUST NOT be selected, merged, split or converted into ordinary change unless the profile establishes eligibility and the applicable authorization allows it. Explicitly selecting a restricted resource requires the profile's operation checks; it does not make the resource foreign. A profile may establish full, unconditional control and amount fungibility for eligible outputs, which can then use normal balance and selection rules. Mere key participation cannot do so.

Wallet-generated successors MUST preserve required profile state or apply a validated, authorized transition. Input/output position, covenant state and signature constraints remain binding. Missing unlocking material may remain pending under the existing two-phase flow only when the wallet can already validate the requested signing context and authorized effects. Neither this extension contract nor a profile grants access to another module's private keys.

## Namespace and counterparty derivation

The logical derivation identifier is:

```
p color <assetId | ISSUE> <derivationPrefix> <derivationSuffix>
```

The prefix distinguishes a payment or issuance and the suffix distinguishes each output, as in [BRC-29](/payments/0029). Exact remittance strings MUST be preserved, even when they contain base64. They MUST be nonempty strict UTF-8, without whitespace, and SHOULD be freshly generated. An application origin, transaction, amount, basket or permission grant MUST NOT silently alter keys.

For interoperability with [BRC-43](/key-derivation/0043)'s lowercase alphanumeric protocol grammar, the wire representation is defined as follows:

* `assetTag` is the asset ID's 64 lowercase txid hex characters, `v`, and its canonical decimal output index, or the literal `issue` for `ISSUE`.
* `prefixTag` and `suffixTag` are lowercase hex encodings of the **UTF-8 bytes of the exact remittance strings**, not hex encodings of decoded base64 data.
* `protocolID` is `[2, "p color " + assetTag + " " + prefixTag + " " + suffixTag]`.
* `keyID` is the fixed string `color`.
* The BRC-42 invoice is `2-<complete protocol name>-color`.

The complete protocol name MUST satisfy BRC-43's 280-character bound. Invalid or oversized identifiers MUST fail before key derivation; truncation, case folding of remittance strings and alternate wire aliases are prohibited. For example, `ISSUE`, prefix `AQ==`, suffix `Ag==` encodes as protocol name `p color issue 41513d3d 41673d3d`. The logical form above is not a second wire spelling. This encoding introduces no exception to BRC-43 normalization.

Derivation follows BRC-42's same sender/recipient counterparty relationship used by the TypeScript Wallet Toolbox's [BRC-29 script template](https://github.com/bsv-blockchain/wallet-toolbox/blob/master/src/utility/ScriptTemplateBRC29.ts): the sender derives the recipient's child public key using the recipient identity, and the recipient derives its child private key using the sender identity. `getPublicKey.forSelf` MUST select the intended owner's child. `self`, `anyone` and compressed public-key counterparties retain their BRC-100 meanings. Receipt metadata MUST represent `anyone` by the compressed public key for private key 1. Ordinary and privileged root selection retain their meanings and authorization; public derivation from an ordinary identity cannot reproduce a privileged key.

A deploy output uses `ISSUE` because its own transaction ID does not yet exist. That derivation remains its spend key after issuance: finalizing the asset ID MUST NOT rederive the deploy's key. Subsequent outputs use the known asset ID. A wallet MUST store the exact original tuple for every managed output, including legacy-encoding inputs and authority outputs.

### Baskets and dispatch

The logical baskets are `p color <assetId>` and `p color <assetId>-AUTHORITY`. Their BRC-100-compatible wire names are:

```
p color <assetTag>
p color <assetTag> authority
```

There is one value basket and one authority basket per asset, never a global Colored Tokens balance basket. `issue` is not a stored basket. After a deploy transaction has its fixed, fully signed transaction ID, the wallet MUST assign its derived asset basket, or authority basket when its amount is zero. Provisional issuance records MUST remain pending until that ID is final. Authority output counts and capabilities MUST NOT be presented as fungible value amounts.

The `color` scheme is defined in [BRC-98](/wallet/0098) protocol, [BRC-99](/wallet/0099) basket, and [BRC-111](/wallet/0111) action-label namespaces. Labels carry the action plan below; they do not change derivation. The wallet MUST dispatch these identifiers to trusted module code before generic permission defaults. Application code cannot install its own handler or bypass custody by renaming baskets, deleting metadata, choosing an originator, or obtaining a generic signing grant. Reads and organizational operations use module-aware permissions; relinquishing tracking does not release protected keys.

`getPublicKey` and `verifySignature` retain their normal public semantics. `createSignature` accepts only the transaction request below. Encryption, decryption, HMAC and linkage operations in this protocol MUST be rejected. Protected keys and root linkages MUST NOT be exposed through auxiliary methods. BRC-29 satoshi keys remain separate and are signed only by the normal wallet transaction path.

### Asset discovery and balance queries

The module MUST support these read-only `listOutputs` views:

| Basket selector                        | Meaning                                                                                                   |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `p color assets`                       | Inventory across this root's per-asset value and authority baskets and the pending records defined below. |
| `p color balance <assetTag>`           | Available token units in the asset's value basket.                                                        |
| `p color balance <assetTag> authority` | Number of available authority outputs in the asset's authority basket.                                    |

These selectors are queries, not storage baskets. They MUST NOT be accepted as output destinations, internalization baskets or relinquishment targets. Coins remain in their per-asset baskets. The `assets` view follows the aggregate-query pattern used by [BTMS asset discovery](https://github.com/bsv-blockchain/ts-stack/blob/main/packages/wallet/btms/src/BTMS.ts), without requiring a shared storage basket or application-maintained asset list.

#### Inventory

`p color assets` returns ordinary BRC-100 output records for actual outpoints, not fabricated asset-summary UTXOs. `totalOutputs` counts matching visible output records, not distinct assets or token units. With `include: "locking scripts"`, applications can decode BRC-161/162 scripts, resolve deploy IDs from their outpoints, and deduplicate by canonical asset ID. Authority-only assets MUST appear; zero authority amount is not a reason to discard them. Identity and quantities come from validated records and scripts, not application tags or symbols. There is no one-satoshi filter.

The inventory includes admitted, unspent managed value and authority outputs, including reserved outputs. It also includes wallet-created, fully signed outputs with fixed transaction IDs that the wallet has verified it controls but has not yet credited because consensus finality, submission or overlay admission is pending. These pending records remain prospective, outside spendable balances. Supported contract resources are included with their restricted state as defined above. Unsigned provisional IDs, unverified incoming messages, spent outputs, unmanaged foreign scripts and legacy burn records MUST NOT be exposed as held value or authority. A fully signed pending transaction that consumes a listed source leaves that source reserved until completion or conflict is established; source and prospective change MUST NOT both count toward available balance.

For this explicitly defined module inventory view, BRC-100 `spendable` is a boolean: true only for locally available, admitted, unreserved outputs under the wallet's accepted state policy; false for reserved, pending or restricted records. When `includeTags:true`, every record MUST carry exactly one wallet-authored state tag: `color_state_available`, `color_state_reserved`, `color_state_pending`, or `color_state_restricted`. Applications cannot assign or overwrite these state tags. A `spendable:false` record is discovery information, never permission to spend. Normal authorization still refreshes source and overlay checks. Inventory is a wallet-state snapshot, not a promise that an outpoint will remain unspent.

The view MUST honor ordinary tag filters, `include` options, `limit`, `offset` and their bounds. Filtered state tags are evaluated against the authoritative state. Records are ordered by canonical txid and then numeric output index, ascending for nonnegative offsets and descending for negative offsets under BRC-100's offset convention. Each call's count and page use one coherent snapshot. Separate calls need not share a snapshot; callers refreshing a changing inventory must reconcile outpoints rather than count repeated records as additional value. Implementations MUST report an explicit error instead of silently truncating a requested result at a private scan limit.

Access requires module-aware read authorization for the inventory scope. Only records covered by that authorization may contribute to either the page or its count; neither may leak other holdings. Restricted authorized views are scoped inventories, not claims about the wallet's complete holdings. `seekPermission` retains its ordinary meaning. Discovery neither grants signing permission nor exposes inventory through `getVersion`. Permission UI and grant organization remain wallet-specific.

#### Exact balances

The balance selectors adopt [BRC-112](/wallet/0112), including its exact module balance representation. A value query sums token units, never carrier satoshis; an authority query counts capabilities, never their zero-valued token fields. The target asset and role are determined by the selector. Only admitted, unspent, unreserved outputs available under the wallet's accepted state policy contribute. Pending receipts and reserved inputs are excluded. The same ownership, read-permission and tag-filter rules apply as to the corresponding basket. An existing, accessible basket with no matching available outputs returns zero; a missing or inaccessible basket uses the normal BRC-100 error behavior.

Responses contain `outputs:[]`, no `BEEF`, and the exact aggregate in `totalOutputs` as canonical decimal text, including `"0"` for zero. BRC-112's P-basket string encoding applies to the API, JSON and binary substrates. `limit` and `offset` do not affect the sum. Each value output remains uint64, but the sum has no uint64 ceiling. Ordinary inventory counts retain their numeric format.

An unsupported codec or validator, exceeded resource limit, or inability to establish the requested balance MUST produce an explicit error. No rounding, wrapping, saturation or partial sum is permitted. `balance p color ...` is prohibited; `p color balance assets` is invalid because amounts of different assets must not be combined.

## Overlay spendability contract

For each asset, the topic and lookup service are deterministically:

```
tm_color_<assetId>
ls_color_<assetId>
```

These names use the canonical underscore asset ID, not the key-derivation `assetTag`. They cover both token encodings, value and authority. For **every** input, whether caller-supplied or wallet-selected, the wallet MUST authenticate the source output, apply BRC-162's binary-prefix precedence, determine its token asset and role or bare-satoshi status, and derive any topic and lookup service it needs. The caller supplies no input-topic hint. An application label, basket, description or other annotation cannot conceal a token, designate another topic, or select a service URL. A source that cannot be resolved and authenticated fails; the wallet MUST NOT assume it is bare satoshis.

Under baseline overlay handling, the wallet MUST have a user-accepted configuration for which providers and evidence it trusts for that asset and how it establishes a sufficiently current answer. It may use owner-configured or owner-initialized [BRC-88](/overlays/0088) SLAP trackers to discover providers. Discovery alone does not make every advertised host a required or trusted provider. The wallet MUST authenticate the providers and response provenance required by its configuration and apply that configuration's freshness, availability and disagreement rules before releasing authorization or accepting custody. If it cannot establish a sufficient answer under those rules, it MUST fail closed. A historical proof or cached permission does not replace the required current check. A caller-supplied endpoint or unauthenticated declaration is not a trust anchor. An immutable, authenticated deploy selecting the attested profile may instead select its fixed-identity mode, subject to that profile and user authorization.

This BRC specifies neither one provider set nor a universal quorum, timeout, chain-tip tolerance, facilitator, [BRC-101](/overlays/0101)-style negotiation, or asset-network participation authorization scheme. Those choices belong to the wallet, asset issuer, configured trackers, application, users and their wider system. Wallets can consequently disagree about admission or spendability while following this BRC in good faith. An issuer's and counterparties' conventions for an asset may be necessary for a transaction to be accepted by its intended recipients. A wallet MUST account for that interoperability risk in its own authorization and submission policy; a response from a provider trusted by one wallet is not automatically sufficient for another wallet.

A conforming service MUST support this [BRC-24](/overlays/0024) query:

```json
{
  "service": "ls_color_<assetId>",
  "query": {
    "type": "color-spendability",
    "version": 1,
    "assetId": "<assetId>",
    "topic": "tm_color_<assetId>",
    "outpoints": [
      "<txid>.<vout>"
    ]
  }
}
```

Query outpoints use canonical [BRC-36](/outpoints/0036) dot form. The response MUST be BRC-24 `output-list`, containing exactly the queried outputs that are currently admitted and unspent in that asset's topic. Each item MUST include its `outputIndex` and BEEF sufficient to authenticate its source output. An empty list is a valid negative answer. Any missing queried token outpoint fails its spendability check; unrelated, duplicate or conflicting returned outputs are invalid responses. The wallet MUST reconstruct each returned outpoint and match its bytes to the supplied source transaction, asset ID, role, amount and script.

Services MUST additionally support the same query shape with `type: "color-admission"`. This returns exactly the queried outputs that were validly admitted to the topic, including outputs now spent. It provides historical membership for receipt validation, not current spendability. Missing historical outputs fail unless the wallet independently establishes their admitted lineage through a complete BRC-176 proof and verifies the received output's current membership. Both queries use the same source-byte matching and response rules.

These queries are a minimum interoperability profile, not a restriction on additional service queries, return-value forms, facilitator capabilities or token-specific functions at higher layers. Services MUST apply BRC-161/162 admission and authority rules, recognize both encodings including canonical 36-byte IDs for legacy nonzero-index deploys, and admit valid token outputs regardless of their carrier satoshi value. The optional one-satoshi service policy in BRC-161/162 is not an alternative validity rule for a conforming `tm_color_<assetId>` or `ls_color_<assetId>` service. A provider unable to meet these rules MUST NOT advertise or answer as that asset's conforming service; the wallet MUST reject known nonconforming answers. Script prefix presence alone does not prove membership. A successful response is a claim about that provider's current service state under the wallet's trust policy, not a miner-enforced token rule, a proof that every other provider agrees, or a future guarantee that an output remains unspent. Pending actions require fresh validation on completion.

### Deployment discovery

The shared topic `tm_color` MUST be available before any particular issuance. Its role is deployment discovery, not per-asset spendability. A provider serving it MUST authenticate the complete deployment transaction and chain evidence and validate the BRC-161/162 deploy position, encoding and amount/authority role without applying a one-satoshi carrier filter. It MUST recognize legacy BRC-161 deployments at nonzero output indices. It admits qualifying deploy outputs; it MUST NOT accept an arbitrary asset ID or caller metadata as a deployment. Its admission does not admit subsequent token transfers or authenticate other assets in the same transaction.

After its signed bytes and transaction ID are fixed, a wallet-created deploy MUST be submitted to `tm_color` during normal processing. Configured providers can observe that deployment and seed `tm_color_<assetId>` and `ls_color_<assetId>` from the validated deploy and its canonical issuance outpoint. Each resulting service MUST apply the ordinary per-asset rules and advertise through the wallet's configured discovery system. No per-asset provider is required to exist before the deployment transaction is constructed or signed.

The wallet MUST then discover an accepted provider for the derived service and obtain the actual per-asset admission/spendability result before crediting its deploy output. A discovery-topic acknowledgement alone is insufficient. If no accepted provider is available or asset admission fails, retain the exact signed transaction and prospective basket as pending, report the failure, and retry discovery/submission idempotently. Do not invent a new issuance or rederive the ISSUE key to work around that failure. Existing assets consumed or created by the deployment transaction still require their own checks and submissions.

This bootstrap path applies to wallet-created deployments; incoming `internalizeAction` still requires existing per-asset admission and MUST NOT silently submit to either topic. Partial or consensus-non-final deployments remain pending as specified below.

## Transaction evidence and admission

The caller MUST provide the complete transaction context, authenticated source outputs and required BEEF/SPV evidence. A bare preimage, digest, amount declaration or list of hashes is insufficient. Txid-only entries may be resolved from authoritative wallet knowledge; unresolved sources fail. [BRC-176](/tokens/0176) packets MAY supply token-lineage evidence but MUST be validated under that profile rather than stripped as ordinary Atomic BEEF. They do not replace baseline current overlay spendability. Only the declared attested or another explicit evidence profile supplies its stated replacement. Neither `trustSelf` nor `knownTxids` waives token admission or currentness checks.

The wallet MUST validate all source output identities, scripts, satoshi amounts, proofs and token roles; reject duplicate inputs and conflicting representations; and, before releasing a spend signature, check every token input against its asset overlay's current spendability. For a completed receipt, consumed inputs instead require authenticated historical admission or valid BRC-176 lineage; checking them as currently unspent would incorrectly reject ordinary receipts. Foreign inputs contribute amounts only after this authentication and topical-membership check. The wallet MUST evaluate the resulting transaction per issuance ID under BRC-161/162:

* A deploy is admitted only at the position and under the rules of its encoding.
* Apply the encoding-specific BRC-161/162 balance and authority rules exactly, aggregating by the same issuance ID across encodings. Without authority, all non-mint value and explicit-burn output amounts together require value input coverage; any uncovered output invalidates the transition.
* BRC-161 `mint` outputs require valid authority. Its `transfer` and `burn` totals require value-input coverage even when authority is present. Legacy `burn` outputs have no spendable token value and MUST NOT enter custody or incoming-value accounting; explicit and implicit burns both require consent.
* With valid authority, BRC-162 minting and authority split, combine, transfer and termination follow its rules. Authority contributes zero to value totals.
* Apply each encoding's admission rules to its own output groups. In particular, an invalid BRC-161 transfer/burn group does not by itself invalidate a separately authorized legacy mint group. The wallet MUST reject an action whose intended outputs are not all admitted; it MUST NOT treat destruction of an input by an over-output transaction as a successful transfer.
* Metadata and decimals never create supply, confer authority, or waive checks.

For each existing asset, the following exact arithmetic determines both authorization facts and the required plan operations. Let `V` be the sum of admitted value inputs (never authority or legacy burn inputs), `T` the sum of BRC-161 `transfer` outputs, `E` the sum of BRC-161 explicit `burn` outputs, `M` the sum of BRC-161 `mint` outputs, and `B` the sum of BRC-162 value outputs. All sums use an integer domain wide enough for multiple uint64 amounts. A legacy `mint` requires an admitted authority input. A legacy `transfer` or `burn` requires `T + E <= V` even with authority. Put `R = V - T - E`; a negative `R` fails. Without authority, `M` and new authority outputs are invalid and `B <= R` is required. With authority, `B` may exceed `R` under BRC-162. Define:

```
fundedBinary = min(B, R)
minted       = M + (B - fundedBinary)       // M requires authority
burned       = E + (R - fundedBinary)
transferred  = T + fundedBinary
```

These are canonical **authorization and supply-accounting** quantities, not an assertion that individual indistinguishable binary output units have an on-chain provenance. In particular, the convention allocates remaining value inputs to binary outputs before counting binary minting, avoiding arbitrary simultaneous mint and burn claims. It preserves `minted - burned = M + T + B - V`. Legacy `mint` is always newly created supply; legacy `burn` is always destroyed supply. A positive `burned` amount requires explicit burn consent and a `burn` operation, even when authority is present and even if a separate mint makes the net supply change zero. A positive `minted` amount requires mint consent and a `mint` operation. A positive `transferred` amount requires `transfer`. Zero quantities require none of those respective operations. Deploys use only `deploy-fixed` or `deploy-authority` for their new asset and do not enter this existing-asset calculation. This calculation does not waive the per-encoding admission rules.

For an existing asset with `A_in > 0` admitted authority inputs, count all authority outputs for that same asset as `A_out`. Exactly one authority operation is required: `authority-end` if `A_out = 0`, `authority-combine` if `0 < A_out < A_in`, `authority-transfer` if `A_out = A_in`, or `authority-split` if `A_out > A_in`. These names classify the transaction-wide capability count; they do not imply that all authorities remain under one owner or that a same-count replacement is harmless. Consent MUST separately cover every resulting authority's script, controller where verifiable, and capability, including delegation and loss of control. Without an authority input, any new authority output is invalid. Operations for a given asset MUST be exactly the set implied by these predicates, with no missing or extra entries. This applies equally to foreign inputs and outputs.

The wallet MUST perform independent satoshi conservation and fee validation. There is no module-wide one-satoshi rule: conforming `color` services do not filter token validity by carrier value. Actual script consensus and ordinary wallet satoshi and fee policy still apply. Nonzero bare-satoshi payments, token-carrier satoshis and fees remain fully accounted for.

Every completed transaction MUST pass consensus-valid script execution for all inputs, including arbitrary foreign scripts. An incomplete subject may omit unlocking scripts only in an explicitly pending action; its available context, source evidence, overlay state and token transition must still be checked. Invalid provided scripts MUST fail. The wallet MUST NOT represent a pending transaction as fully validated, admitted, mined or immediately spendable.

## Structured authorization facts

Authorization MUST be based on trusted, validated facts for the entire action:

* authenticated originator, network and selected wallet/root context;
* every asset's issuance ID, encoding, value and authority inputs and outputs, scripts, outpoints, overlay topic and current check results;
* per-asset wallet input total, wallet-owned output total, incoming amount, outgoing amount, net debit or credit, burn, mint and authority changes;
* satoshi inputs, wallet receipts, outgoing payments, carrier values and fees;
* recipients as verified control identities where known, otherwise script-based destinations with the limits of that identification stated;
* wallet-selected inputs, generated change, caller-fixed positions, sequences, locktime, transaction version, signature modes and exact commitment coverage;
* completeness, pending status and terms that remain conditional or mutable;
* required profile versions and parameters, protected contract roles and state transitions, and additional validated authorization facts those profiles require.

Netting MUST be computed separately for each asset from authenticated inputs and wallet-controlled outputs. Foreign funding can offset a debit only to the extent it validly funds wallet-owned outputs of that **same** asset. Recognizing an output's key requires the stored or supplied remittance tuple and comparison against its actual P2PKH hash, or the required profile's verified control rules; an application annotation cannot establish wallet ownership. A conditional or joint signing role cannot offset a debit as an unrestricted receipt without the profile establishing the corresponding control and authorization facts. Satoshis are accounted separately under normal spending permissions, including [BRC-116](/wallet/0116) DSAP where implemented. Token-carrier satoshis MUST appear in total satoshi accounting without also being counted as a bare-satoshi payment. Internal transfers and change are not external spending.

When value of one asset leaves and another enters, the wallet MUST present the action as a swap with all legs. Bare satoshis are an asset for this presentation; a swap may have any number of token and satoshi legs, including three or more assets. Different assets MUST NOT cancel each other's debits or disappear from the presentation. Burns and authority delegation, duplication or destruction require clear consent even when the fungible net debit is zero. Display metadata is untrusted descriptive information; the issuance ID remains available.

The user MUST consent to the actual authorized consequences. Rendering and permission organization are wallet-specific: wallets MAY use per-action approval, origin-scoped limits, monthly allowances, thresholds, manifest-informed rules or other user-authorized policies. This specification mandates no particular quota, permission-token encoding, UI framework, or frequency of prompts. A generic cryptographic protocol grant does not substitute for asset spending consent. Concurrent operations and retries MUST NOT exceed a wallet's granted permission or consume an allowance twice for the same authorized action.

## Metadata types and validation

Every envelope, plan, remittance, operation and constraint is a JSON object with exactly the named members. Duplicate JSON members and unknown members MUST fail at every nesting level; `profileData` enforces its profile/asset map structure and its leaf values use the required profile's explicitly defined schema; duplicate rejection remains mandatory. Optional `profileData` is permitted in signature envelopes, action plans and receipt instructions only. The optional `tokenProofs` member is permitted only in signature envelopes and action plans; omission means an empty array. Receipt custom instructions MUST reject that member, even when its value is empty. Each item is exactly `{assetId, subjects, beef}`: a canonical asset ID, a nonempty array of unique canonical subject outpoints, and a nonempty numeric byte array encoding one BRC-176 packet. The packet MUST be validated for exactly those subjects and that asset; contradictory or duplicate proof scopes fail. Packet presence does not assert current admission or spendability. These bytes count toward the containing request's resource bound. Wallet-fetched proofs may be used only after the same validation. The `tx` field separately uses BRC-95 subject framing and ancestor inclusion; incomplete subject script execution is deferred until completion, and MUST NOT be described as fully validated Atomic BEEF.

Arrays MUST have the types specified here; `null` is allowed only where explicitly stated. Strings MUST be strict UTF-8. Numeric indices and versions MUST be integers, not fractions, booleans or strings. `version` is exactly `1`. An index is in `0..2^32-1` and MUST select an actual input/output or a satisfiable planned position. `sighashType` is an integer in `0..255`, further subject to consensus rules. `tx` is a nonempty numeric array of integer bytes in `0..255`, not base64 or a numeric-key object.

All metadata `outpoint` fields use canonical lowercase BRC-36 dot form with a uint32 decimal index without leading zeroes. `assetId` and `ISSUE` use the exact identity rules above; ISSUE is prohibited for existing inputs. Topics are exact strings derived from the asset ID. `senderIdentityKey` is a valid compressed secp256k1 public key. Prefix/suffix strings and resulting derivation must satisfy the namespace rules. `ownedOutputs` contains at most one remittance per output index. Source-output classification and topic derivation are entirely the wallet's responsibility. `inputTopics` and its former hint objects are not members of any version-1 envelope, plan or receipt instruction and MUST fail as unknown metadata. Every remittance MUST match authenticated transaction bytes.

Plan `assets` is an array of unique strings and MUST exactly enumerate token assets found in the planned sources and outputs, including ISSUE only when creating a deploy. `operations` is an array of unique `{assetId, operation}` pairs; `operation` is one of the table's enumerated strings; `resource` is valid only for an authenticated BRC-209 identifier. Each constraint array contains objects of exactly the stated shape. Duplicate outpoints, asset change constraints or target positions within an array MUST fail. Cross-array conflicts and positions incompatible with requested output order MUST fail. Override outpoints MUST identify managed inputs actually consumed. A selected input without an override uses the default signature mode. Any malformed metadata MUST fail without signing, mutation of balances or receipt acceptance.

## Signature requests and composability

An application requests one module-key signature using existing fields:

```
createSignature({ protocolID, keyID: "color", counterparty,
  data: UTF8(JSON({version:1, tx:[...Atomic BEEF bytes...], inputIndex,
    sighashType, ownedOutputs:[...remittances...]})),
  seekPermission }, originator)
```

The five JSON members shown are required; `ownedOutputs` may be empty. Optional `tokenProofs` carries lineage packets and optional `profileData` carries required-profile evidence as defined above. A remittance is `{outputIndex, assetId, derivationPrefix, derivationSuffix, senderIdentityKey}`; `assetId` may be `ISSUE` only for a deploy output. Root selection for newly managed outputs is the action's selected root; mixed-root custody MUST NOT be inferred. Additional JSON members, duplicate members, invalid UTF-8, invalid byte arrays and invalid indices MUST fail. `hashToDirectlySign` MUST be absent. The envelope is limited to 16 MiB before allocation; a documented lower host limit MUST fail explicitly rather than truncate. All remittances are checked, not trusted.

The wallet MUST verify that the protocol's asset selector identifies the selected source asset; ISSUE is valid only for the preserved original deploy tuple. It MUST derive the selected key, match it to the selected managed P2PKH source (or a recognized module-key signing role in a foreign script), construct the exact consensus preimage from authenticated source data and authorize its coverage. A foreign source requiring another module's or a BRC-29 key is not an alias into this signer. Executed `OP_CODESEPARATOR`, when applicable, MUST be handled using its actual executed script path and consensus script code; a caller-provided code separator offset or signature slot is not evidence of that path. If partial foreign execution needs missing material to determine the preimage, the request fails until that material is supplied. This does not limit foreign scripts that the module need not sign.

The result is BRC-100 `{signature: Byte[]}`: DER low-S ECDSA over the wallet-computed transaction digest, without the script sighash byte. Verification uses `hashToDirectlyVerify` and the same tuple, not the JSON envelope as `data`. The wallet MUST reserve managed inputs and record consent before releasing a signature. A signature request may spend module-managed balances; it MUST NOT silently sign default-basket funding inputs.

### All signature modes

The module MUST support all consensus-valid sighash **families**, including ALL, NONE, SINGLE and ANYONECANPAY combinations and the post-Chronicle digest variants. It MUST NOT impose the earlier four-mode restriction. It MUST, however, refuse to create or release a SINGLE signature unless `inputIndex < transaction.outputs.length`, even where consensus would accept the missing-output case. This safety condition applies to `createSignature`, `createAction` and `signAction`, including signatures prepared for a pending action. In the original transaction digest algorithm, a missing matching output makes SINGLE sign the constant digest `1`, so the signature is reusable without an outpoint or output commitment. Refusing that degenerate context loses no distinct useful transaction commitment; NONE remains available where zero output coverage is intended. Other invalid consensus modes or contexts fail through consensus validation, not a token-specific mode whitelist. Standard wallet-generated managed-input signatures SHOULD use ALL|FORKID without ANYONECANPAY unless the authorized agreement needs another mode.

The wallet MUST select the actual consensus digest algorithm before calculating coverage. FORKID without CHRONICLE uses the BIP143-style preimage: `hashPrevouts` commits all input outpoints only when ANYONECANPAY is absent, and ANYONECANPAY zeros that component. CHRONICLE selects the original transaction digest algorithm even when the FORKID bit is also set; it does not have a `hashPrevouts` field. The wallet MUST apply that algorithm's script code, sequence, input and output serialization rules, including executed `OP_CODESEPARATOR`, before signing or describing commitments. ALL commits all outputs, a valid SINGLE commits its corresponding output, and NONE commits no outputs. These components MUST NOT be described as interchangeable. A named, unspent counterparty input committed by a signature prevents a completed transaction from consuming it after a conflicting spend; that fact alone does not commit the requested outputs or guarantee token delivery.

A wallet MAY release a partially signed offer before receiving the counterparty's unlocking scripts. For a transaction-level guaranteed swap, the named incoming inputs and promised wallet-owned outputs MUST be committed by the released signature, validated covenant conditions, or other independently verified commitments that remain binding in every valid completion. The wallet MUST identify uncommitted terms and all possible authorized consequences, including burn, missing consideration and authority delegation. Disclosure or broad consent alone MUST NOT turn an unsafe conditional exchange into a guaranteed swap. A weaker signature mode MAY be used when the user's authorized disposition is unconditional at signature release, so every possible completion is within that disposition and a later untrusted actor cannot worsen the user's agreed position, or when the wallet has verified an applicable, system-specific safeguard or remedy for the conditional exchange. The safeguard or remedy may be in script, at law, or elsewhere in the asset ecosystem; its design is outside this BRC. If neither condition holds, the wallet MUST decline the signature. It MUST NOT present conditional terms as guaranteed receipts or authorize only the displayed favorable completion.

Revalidation before wallet completion is mandatory but cannot restrict an external party using an already released signature. A pending reference, UI cancellation or expired allowance does not retract that capability. Composable agreements remain possible with arbitrary scripts and independently supplied signatures; the wallet enforces accurate authorization rather than a fixed swap transaction template.

Transaction-level commitments do not themselves establish overlay admission. Before signature release or broadcast, the wallet MUST apply its configured asset and counterparty acceptance policy to the intended transaction. It MUST perform determinable token and profile checks, available admission preflight under its configured provider and known participant policies, and any applicable system-specific settlement safeguard checks. The caller cannot suppress a check by omitting an assertion or hint.

If those checks establish that a token output the wallet would count as payment, change or consideration cannot be admitted by its topic under the acceptance policy required for that leg, the wallet MUST NOT release any signature it controls for the action. This includes a swap in which another leg can settle while that output is rejected. The prohibition covers signatures for both token and ordinary satoshi funding inputs. It applies to direct and managed signing, partial actions, revisions and `noSend`. Consent to unconditional disposition, a broad grant or an alternate remedy cannot override a known failed output. Explicit BRC-161 burn outputs are not presented as admitted receipts.

An unavailable or inconclusive preflight is handled by the existing fail-closed and conditional-exchange rules, not treated as a known rejection. A wallet MUST NOT claim that an output is a guaranteed admitted token receipt before the required topic accepts it. Because later provider failure, policy differences or another party's inaction can defeat a prospective credit, designers of a conditional exchange MUST bind the required consideration or provide a clearly specified alternate remedy under that system's assumptions and risk profile. Otherwise a participant who releases an inadequately constrained signature may lose **that participant's** assets. One participant's signature grants no power over another participant's keys: each independently contributed input requires its controller's own valid authorization, and recovery or overlay disagreement MUST NOT treat unrelated users' assets as spendable or invalid. This BRC does not prescribe scripts, certificates, on- or off-chain identity linkability, legal terms, or an asset-specific remedy.

## Action planning, selection and change

Ordinary BRC-100 calls keep their existing argument and result shapes. The module action plan is strict UTF-8 JSON, hex encoded and split into consecutive chunks of at most 200 hex characters. It is carried in existing `labels`:

```
p color plan <partIndex> <partCount> <hexChunk>
```

Hex chunks MUST contain an even, nonzero number of lowercase hex digits. Indices are zero-based; index and count are decimal without leading zeroes and bounded to uint32. Concatenate chunks by index, decode hex once, then decode strict UTF-8 JSON with duplicate-member rejection. Nonfinal chunks MUST contain exactly 200 hex characters; the final chunk contains at most 200. Reject missing parts, inconsistent counts, invalid encodings or bounds before large allocation. There MUST be exactly one complete plan, with each part present once and a consistent positive count. Parts may occur among ordinary action labels; their index determines reconstruction. A plan is bounded to 16 MiB decoded JSON and existing per-label limits. A host with lower action limits MUST report an error. Plan labels are module metadata and MUST NOT be interpreted as consent or as [BRC-177](/wallet/0177) expiry labels. `listActions` queries using these exact labels retain ordinary matching semantics with module-aware read permissions.

The plan has these required members and optional `tokenProofs` and `profileData` members defined above, with no other members:

| Member            | Meaning                                                                                                                                                                                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`         | Integer `1`.                                                                                                                                                                                                                                                                    |
| `assets`          | Unique canonical asset IDs involved, plus `ISSUE` for a deploy.                                                                                                                                                                                                                 |
| `ownedOutputs`    | Remittances for requested outputs to be managed; same format as the signature envelope.                                                                                                                                                                                         |
| `operations`      | Per-asset intent: `transfer`, `burn`, `deploy-fixed`, `deploy-authority`, `mint`, `authority-split`, `authority-combine`, `authority-transfer`, or `authority-end`; BRC-209 IDs instead use `resource`. Declare every applicable operation and validate against actual scripts. |
| `inputPositions`  | Array of `{outpoint, index}` constraints on explicit inputs; empty when unconstrained.                                                                                                                                                                                          |
| `changePositions` | Array of `{assetId, index}` constraints for generated token change, or `{assetId:null,index}` for ordinary satoshi change; empty when unconstrained.                                                                                                                            |
| `sighashTypes`    | Array of `{outpoint, sighashType}` overrides for managed inputs; empty uses the default above.                                                                                                                                                                                  |

An operation is `{assetId, operation}`. Authority operations and burns must be explicit; inferred transfer intent never authorizes them. A plan's `ISSUE` identifies only its deploy, not any existing asset. The transaction's prefix and validated sources determine quantities and operations; the plan cannot override them. A deploy plan MUST have exactly one deploy. After finalization, its record is indexed under the resulting ID without changing the signed plan or derivation.

`createAction` MUST recognize plan labels, token-encoded requested outputs, relevant module baskets, and explicit inputs already managed by the module. Missing required plans or source evidence fail. The module MUST authenticate foreign inputs, select sufficient unreserved value inputs separately per asset, and make fresh per-asset change outputs under the custody profile. Required profile and contract-resource checks apply to caller inputs and generated successors as well. Restricted contract resources are not ordinary selectable funding or change. Valid foreign amounts are netted before wallet selection. Authority operations select actual authority inputs, not a numeric balance. Applications MAY supply required inputs or input/output positions. Setting `options.randomizeOutputs:false` preserves requested output order; explicit position constraints and generated change MUST be satisfied without inserting, shuffling or replacing covenant-sensitive outputs. An impossible layout, insufficient asset or satoshi balance, or absent overlay membership MUST fail before signing. Newly generated keys and authoritative ownership facts belong to the wallet; application selection does not make an input foreign.

Only declared burns may suppress value change; mint and issuance outputs follow their declared supply rules. Satoshi funding, outputs and change use the ordinary wallet action machinery alongside the token plan. The wallet MUST authorize the final funded layout, not an earlier estimate. `createAction` MAY automatically sign managed P2PKH inputs, or profile-supported contract inputs for which it can construct and verify the complete unlock, when that final action is valid and authorized, including colored-coin payments and multi-asset swaps. It MUST NOT invent missing contract material. Missing foreign unlocking scripts or `signAndProcess:false` returns the existing `signableTransaction` reference. Applications may exchange the resulting partial transaction and signatures.

`signAction` MUST preserve the pending plan, reservations, constraints and previously committed fields; validate supplied unlocking scripts; refresh overlay spendability and permission validity; and validate the final transaction before returning it as complete or processing it. A changed context needs revalidation and consent covering the change, with new signatures where required. A module signature and an action reference for the same inputs MUST share one authoritative reservation and authorization record rather than conflict or charge twice.

Reservations are atomic across all assets and satoshi funding. Failed checks before signature release roll back provisional reservations. After signature release, aborting an action may cancel processing but MUST retain the relevant exposure record; inputs MUST NOT be offered to a conflicting action until the wallet establishes that the capability can no longer consume them, or explicitly authorizes a linked revision or conflicting spend while retaining all remaining exposure. Authorization alone does not invalidate the earlier offer. Unspentness checks, observed external completion and conflicts update this record. Wallets MUST distinguish pending exposure from settled spending. Quota implementation is wallet-specific, but released capabilities cannot be hidden by resetting a pending action. `noSend`, `sendWith` and ordinary two-phase flows remain supported; returning a noSend transaction does not establish overlay admission.

### Non-final transactions, revisions and auctions

Wallets MUST support time- or height-constrained Colored Token actions through the existing BRC-100 `lockTime`, input `sequenceNumber`, `signableTransaction`, `signAction` and `noSend` fields. This applies to multi-asset auctions, token-for- satoshi bids and arbitrary foreign contract scripts as well as ordinary sends. The application supplies auction terms; the wallet validates their enforceable consequences and owns the processor coordination required below. This defines no new action method or Bitcoin consensus rule.

Track these facts separately: whether all unlocking scripts are valid and present; whether the transaction is consensus-final in the evaluated chain context; whether processing/broadcast is requested and currently eligible; and whether every required overlay has admitted it. Fully signed bytes have a transaction ID even while non-final. An incomplete transaction's current ID is provisional. Neither type of pending action supplies spendable received value.

Apply Bitcoin's [transaction finality rules](https://protocol.bsvblockchain.org/specification/consensus-rules#finality-nlocktime--nsequence) to the complete input set. `lockTime` is an absolute height below 500000000 and an absolute timestamp otherwise. A zero lock time, a lock time strictly below the applicable chain comparison value, or all input sequences equal to 4294967295 makes the transaction final. A lower sequence alone does not create a delay; all-final sequences bypass even a future lock time. Implementations MUST use the applicable chain rules, not the user's wall clock or a Bitcoin relative-locktime assumption. An auction deadline is not enforced merely by putting a timestamp in metadata or using a nonmaximum sequence.

Before releasing a signature, establish and present the effective time/height constraint, all asset and satoshi consequences, output positions, signature commitments, and any still-executable earlier revisions. A wallet MAY authorize an explicitly pending non-final action once all available scripts, sources, current overlay state and token transitions pass validation. It MUST NOT claim immediate settlement or ordinary completed processing while non-final. For a fully signed non-final action, `noSend:true` may return its transaction bytes; the wallet MUST retain its pending status. A processing request that cannot yet be fulfilled MUST return an explicit error and preserve released exposure. With `noSend:false` and `acceptDelayedBroadcast:true`, a complete non-final action MAY instead succeed as processor-accepted pending processing, but only after the coordination acceptance checks below. With delayed processing disallowed, it MUST return an error while non-final. Processor acceptance is not settlement or spendable overlay admission.

Each revision is an exact transaction context. `signAction.spends[i].sequenceNumber` may update that input only subject to BRC-100's saved-action contract; it cannot silently alter other inputs, outputs or lock time. A changed bid price, recipient, lock time or layout needs a new `createAction` context (or the existing complete signature-request envelope), linked by the wallet to the shared input exposure. Every revision MUST recheck all assets, sources, conservation, permissions, positions and current overlay state, and recompute every affected preimage. Retain a signature only if it verifies for the revised context; regenerate or obtain every invalidated signature before returning a complete transaction. This includes foreign signatures: changing a sequence does not give the wallet authority or ability to replace another party's signature. Preserve covenant output ordering and generated-change constraints, or fail.

#### Wallet-managed processor coordination

A conforming wallet MUST establish and maintain coordination with transaction processors that ensures **every valid higher-sequence replacement is accepted** while the registered transaction family remains replaceable. This is the wallet's responsibility, including processor selection, authentication, submission, propagation, acknowledgement, recovery and ongoing coverage. An application need not implement processor-specific replacement logic. A wallet MUST NOT shift this obligation to the application, substitute a risk disclosure for it, or silently downgrade a coordinated revision to best-effort broadcast.

Before first releasing a non-final transaction signature, including through `createSignature`, partial actions and `noSend`, the wallet MUST establish an authenticated processor arrangement covering the family and its intended lifetime. The arrangement MUST identify the participating processors, their replacement and retention obligations, the evidence of acceptance, and their capacity, fee and availability conditions. The wallet MUST arrange sufficient coverage for the promised replacement and settlement behavior; merely choosing an arbitrary reachable node is insufficient. It MUST check these conditions before authorizing a revision, so a valid authorized replacement cannot later be treated as an optional policy exception. Without sufficient coordination, the wallet MUST fail before releasing a new signature. This BRC standardizes the obligation and validated facts, not a vendor, fixed processor count or transport.

A **transaction family** is identified by the network and the exact ordered input outpoint list, held in the wallet's shared reservation record. Relative to the latest accepted revision, a higher-sequence replacement MUST preserve that list, decrease no input's uint32 sequence and increase at least one. Identical bytes are an idempotent resubmission, not a new revision. Different bytes with the same sequence vector, decreasing or incomparable vectors MUST NOT supersede an accepted revision. The wallet MUST serialize competing updates against the latest accepted state rather than compare each with a stale base. Changing the input set or order is a new family, and requires a separately validated coordination handoff covering any shared-input exposure; it is not made a valid replacement by increasing an unrelated sequence. A handoff MUST bind both families, their latest accepted transaction IDs and shared outpoints; all required processors must acknowledge the new family as successor and prevent the earlier family from resuming preferred processing before success is reported. If these obligations cannot be established, the handoff fails without silently freeing the old family's inputs.

A **valid replacement** also has the required valid unlocking scripts, current source availability and token evidence, exact asset and satoshi accounting, authorized terms, and preserved positional/covenant constraints. Partial bytes may be prepared under the arrangement but cannot be reported as accepted complete replacements. Higher sequence is not permission to change the bid price or any other term. The wallet MUST regenerate or obtain affected signatures as above. Replacement eligibility closes when an input is consumed by an established conflicting spend, the latest accepted revision becomes consensus-final in the evaluated chain context, or the processors atomically accept an expressly authorized finalizing revision. An arbitrary local timeout or a processor refusal is not finalization. The arrangement MUST cover the non-final lifetime; a processor may not unilaterally shorten it. A valid higher-sequence finalizing revision is accepted before closing the family to further updates. A sequence increase cannot undo completed settlement. The wallet MUST manage propagation and finalization timing so it does not promise acceptance of an update whose acceptance window it cannot satisfy.

When processing is requested, the wallet MUST obtain authenticated evidence that the exact transaction is accepted as the latest revision under the arrangement, superseding earlier revisions for relay and eventual processing. Evidence MUST bind the network, ordered input outpoints, previous and replacement transaction IDs (no previous ID for initial acceptance), sequence vector, processor identities and the arrangement's validity scope. Required processors MUST agree on the accepted revision, retain or recover that state across restarts, and prevent stale or equal-sequence conflicting revisions from becoming preferred again. An HTTP success, one ordinary mempool observation, an overlay answer, or an application's assertion is not sufficient evidence of those obligations. The wallet MUST monitor their continued fulfillment until finalization, and coordinate replacement processors and overlays as necessary.

With `noSend:true` the wallet MUST honor the prohibition on transaction submission: it may establish the arrangement without submitting the signed transaction, but returns **prepared**, not processor-accepted, bytes. The same distinction applies to `signableTransaction`. When the caller later requests processing through `signAction` or a `sendWith` flow, the wallet performs the acceptance step. It MUST NOT claim replacement from the mere return of a signature, transaction ID or pending reference.

Processor refusal, timeout, conflicting acknowledgements or lost coverage MUST produce an explicit failure of the replacement operation, not a successful latest-revision result. The wallet MUST preserve all transmitted or released exposure, reconcile uncertain outcomes and retry idempotently where possible. A valid update's refusal is a failure to fulfill the coordination obligation, not permission to settle an older revision silently. The wallet MUST surface that failure and MUST NOT finalize a stale revision or credit speculative outputs while the accepted state is unresolved. Changing providers MUST preserve the latest accepted state and obligations rather than reset the sequence history.

This guarantee is supplied by the wallet's processor arrangement. It does not make an earlier transaction's signatures cryptographically invalid, change Bitcoin's finality rules, or establish that a higher sequence is a higher price. The wallet MUST retain earlier released capabilities as historical exposure, including after coordinated supersession, until a verified conflict or an independently verified consensus/script condition prevents their use. The user and application can rely on the wallet to enforce replacement within its arrangement; they are not responsible for recreating that coordination. A highest-bid-wins promise additionally requires validation of the auction's bid ordering and authorization rules.

`lockTime` remains an earliest-inclusion constraint, not signature expiry or a last-valid auction time. Neither abort nor noSend expiry cancels the processors' obligations or erases released capabilities. Cancellation MUST reconcile the coordinated state, preserve exposure and report any cancellation that cannot be established; it MUST NOT pretend that local deletion revoked the transaction.

On finalization, recheck chain context, all scripts, permissions, all asset states and the processors' agreed latest revision, then follow normal processing and overlay admission. Do not silently set sequences to 4294967295, reduce lock time, drop fixed outputs or reuse stale authorization to make a transaction final. An unspentness check can fail because an earlier revision or another spender consumed an input; report that conflict and reconcile reservations without crediting competing prospective outputs. These rules apply equally to internalization: a fully signed but non-final receipt MUST fail, and overlay acknowledgement cannot waive consensus finality.

## Internalization

`internalizeAction` uses the existing `basket insertion` protocol with the module's per-asset basket and `insertionRemittance.customInstructions` containing strict JSON `{version:1, assetId, derivationPrefix, derivationSuffix, senderIdentityKey}` and optional `profileData`, with no additional members. A deploy receipt uses `assetId: "ISSUE"` for key derivation and derives its actual asset ID and basket from the supplied completed transaction. The root is the wallet's selected root. Unknown or duplicate members and missing required fields MUST fail. An incorrect basket MUST fail rather than be silently reclassified; an issuance initiated by this wallet uses its automatic final-ID assignment. `paymentRemittance` for ordinary satoshis remains BRC-29.

The complete `customInstructions` string MUST fit BRC-100's 1000 UTF-8 byte limit, except for the larger limit explicitly defined by a supported BRC-209 or attested-admission receipt. Module-aware routing and validation MUST apply that exception before ordinary string-length rejection; merely naming a basket does not establish eligibility. Initially bound all `p color` receipt instruction strings to 16 MiB each and 16 MiB in aggregate per call, then validate the authenticated profile and enforce its applicable limit before accepting any output. A lower documented host limit fails explicitly. Other basket limits and binary framing remain unchanged. `tokenProofs`, proof URLs and proof-reference fields are not receipt members; they MUST be rejected rather than ignored. Receipts obtain historical admission through the defined lookup, or independently verified BRC-176 lineage already held by the wallet or retrieved under its configured provider and evidence policy. Fetched or cached packets MUST meet the same exact asset, subject, completeness and chain-evidence checks as caller-supplied packets in action plans or signature envelopes. Cache presence alone is not validation. If sufficient evidence is unavailable, internalization fails. Current membership of the received output is required by the baseline; the declared attested profile instead applies its complete-frontier and offline-acceptance rules.

Baseline receipts define no new caller proof-upload transport. Declared profiles may use their specified `profileData` carrier, without changing `tx`. The ordinary `tx` field retains its BRC-95 meaning: a BRC-176 packet MUST NOT be smuggled through it or stripped as ordinary Atomic BEEF. Removing receipt-embedded proofs changes their delivery, not the wallet's validation obligations.

Subject to the exact declared evidence replacement above, before accepting a receipt the wallet MUST validate complete transaction scripts, source evidence, foreign-input authentication, historical topical admission of consumed inputs, per-asset amount and authority rules, and its control of each requested custody output. It MUST NOT demand current unspentness of inputs already consumed by that receipt. The same encoding, lineage, amount and ownership checks apply to foreign inputs used by `createAction` and prospective wallet-owned outputs used in signing accounting. A candidate output is not yet admitted: its prospective credit is conditional on transaction completion and overlay acceptance, and MUST NOT be added to spendable local balance before receipt admission. The received outpoint itself MUST already appear as admitted and unspent in its asset's spendability lookup. Internalization MUST NOT silently submit an absent transaction to make this test pass. Missing overlay admission MUST reject internalization. The sender or higher-level flow is responsible for submission before requesting receipt.

For wallet-created completed, consensus-final actions, normal processing MUST submit the transaction to every involved token topic and record each topic's actual admission result. A `noSend` or partial action defers submission. Coordinated processing of a complete non-final action may submit it to transaction processors as specified above, but defers ordinary final processing and spendable overlay credit. Deployments first follow the discovery-topic bootstrap above. Admission into one asset's overlay does not imply admission into another's; local receipt and spendable balances MUST only be credited after all required admission checks. If Bitcoin completion occurs but an overlay operation fails, record the actual pending/error state without claiming reversal of the on-chain spend. Retries MUST be idempotent. Partial or consensus-non-final transactions cannot be internalized as completed receipts.

## Discovery, compatibility and security boundaries

This specification defines the optional [BRC-205](/wallet/0205) attribute `brc207-color`. Its decoded value for this revision is `4`, meaning the wallet implements this revision's required-profile contract, scoped receipt limits and `color` protocol, basket and action-plan rules. Earlier values do not establish support for this revision. BRC-209 and attested admission remain optional required profiles. A wallet implementing the default attested profile, including all four trust modes, MAY additionally advertise `brc207-color-attested=1`. Future evidence replacements are checked separately; this attribute does not claim support for arbitrary replacements or alter BRC-175 transport discovery. The JSON envelope and plan `version` remains `1`; the capability revision is not their serialization version. Example:

```
example-1.2.3; brc207-color=4;
```

Encoding, single occurrence and outer bounds follow BRC-205. Unknown values MUST be ignored as unsupported metadata. A wallet MAY omit the attribute for privacy, while locked, or to meet bounds; omission is not proof of non-support. Presence is a snapshot, not authorization, proof of safe code or core-method negotiation. It MUST contain no user-specific balances, grants, asset inventory or history. It does not enumerate supported required profiles: support for the exact requirements is checked on every operation, and unsupported profiles fail. Consumers SHOULD refresh on the selected connection before starting a workflow, and MUST discard cached values on provider/account changes or disconnection. Every actual call still checks current support and permissions. BRC-205's extended-string compatibility configuration is required before emission; legacy paths continue to return base-only versions. BRC-185 discovery is independent. Implementations MUST independently adopt BRC-205's profile before using this attribute; this document does not assert deployment or adoption of that profile.

The module supports the BRC-161/162 token model; it does not assume compatibility with earlier `p mandala` drafts, BTMS, generic application keys or other basket profiles. Migration and recovery are outside this specification. An application MUST NOT strip the reserved prefix or fall back to generic signing when this module is unavailable. No software implementation, universal deployment, or live-wallet conformance is asserted here.

Overlay trust is explicit and independent of SPV and Bitcoin consensus. A current spendability answer cannot promise future availability; consensus input commitments and script conditions establish completion constraints. Metadata, permissions and partial transaction references cannot change those constraints. Trusted module code retains private keys and validates the facts supplied to wallet policy and presentation. Arbitrary foreign scripts retain composability without receiving access to internal wallet signing capabilities.

## Conformance cases

Unless a case explicitly selects a required evidence profile, these cases and Appendices A–B exercise baseline overlay admission. Appendix C covers the attested replacement; its exceptions never apply to undeclared assets.

| Case                                                                           | Required behavior                                                                                                                                                              |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Same identities and remittance tuple                                           | Sender-derived recipient public key matches recipient-derived private key; exact encoding and owner selection agree.                                                           |
| Deploy finalized                                                               | Asset ID and value/authority basket assigned from final outpoint; ISSUE-derived key preserved.                                                                                 |
| BRC-161 and BRC-162 inputs for one ID                                          | One asset account, encoding-specific validation, BRC-162 generated outputs.                                                                                                    |
| Unresolved source, service substitution, missing overlay output                | Fail without signing or crediting.                                                                                                                                             |
| Opaque non-map BRC-162 payload                                                 | No defined attributes; enforce the authenticated deploy requirements and profile rules without interpreting partial payload data.                                              |
| Two-satoshi carrier or canonical 36-byte legacy ID                             | A conforming `color` service applies the same base token rules without an optional carrier filter or legacy-ID omission.                                                       |
| Known rejection of a token payment, change or consideration by one asset topic | Refuse every wallet-controlled token or satoshi signature before release, even if another leg can settle or an alternate remedy is offered.                                    |
| Foreign input provenance                                                       | Reconstruct and verify source; matching current topic admission required before amount netting.                                                                                |
| Split, merge, burn, mint and all authority operations                          | Exact per-asset rules and consent; no authority-as-value arithmetic.                                                                                                           |
| Three-asset swap or token-for-satoshis swap                                    | Every leg and satoshi fee represented independently.                                                                                                                           |
| Wallet selection and change with fixed positions                               | Sufficient eligible inputs and fresh change; preserve constraints or fail.                                                                                                     |
| Partial offer before foreign unlocks                                           | Pending status, verified sources, accurate commitments and authorization of possible outcomes.                                                                                 |
| ALL, NONE, SINGLE and ANYONECANPAY                                             | Consensus preimage correctness; no fictitious hashPrevouts or output coverage.                                                                                                 |
| SINGLE with no corresponding output                                            | Reject for both BIP143 and Chronicle digest variants; never release the original-algorithm constant-digest signature.                                                          |
| Mixed legacy/binary value with authority                                       | Apply the exact input-allocation arithmetic; declare and consent to every positive mint, burn and transfer, including offsetting mint and burn.                                |
| Completed foreign-script spend                                                 | Full consensus script validation before use or processing.                                                                                                                     |
| Required payload profile                                                       | Authenticate the deploy; enforce every exact version and parameter set or fail, across encodings.                                                                              |
| Contract resource with unchanged amount                                        | Validate and authorize role/state effects; no automatic funding or unrestricted receipt credit.                                                                                |
| Internalization absent from overlay                                            | Reject; do not silently submit.                                                                                                                                                |
| Retry, concurrent requests, abort and noSend                                   | Shared reservations and accurate exposure; no duplicate credits or quota charges.                                                                                              |
| Inventory and exact balances                                                   | Discover value, authority and pending/reserved records without treating them all as available; preserve exact aggregates or error.                                             |
| Coordinated sequence replacement                                               | Wallet arranges processor coverage before signature release; a valid higher revision must be accepted when processing is requested, or an explicit failure preserves exposure. |
| Receipt metadata bounds                                                        | No embedded tokenProofs; enforce 1000 UTF-8 bytes except the scoped BRC-209/attested-profile limit.                                                                            |
| Capability omitted or stale                                                    | Handle actual call results; no generic-signing fallback.                                                                                                                       |

## References

* [BRC-29](/payments/0029), [BRC-42](/key-derivation/0042), [BRC-43](/key-derivation/0043), [BRC-44](/key-derivation/0044): payment keys and derivation boundaries.
* [BRC-98](/wallet/0098), [BRC-99](/wallet/0099), [BRC-111](/wallet/0111), [BRC-100](/wallet/0100), [BRC-112](/wallet/0112), [BRC-116](/wallet/0116): namespaces, action API, balance queries and permissions.
* [BRC-161](/tokens/0161), [BRC-162](/tokens/0162), [BRC-176](/tokens/0176): token encoding, supply and lineage validity.
* [BRC-22](/overlays/0022), [BRC-24](/overlays/0024), [BRC-88](/overlays/0088): overlay admission, lookup and service discovery.
* [BRC-62](/transactions/0062), [BRC-95](/transactions/0095), [BRC-96](/transactions/0096): transaction evidence and framing.
* [BRC-177](/wallet/0177), [BRC-185](/wallet/0185), [BRC-205](/wallet/0205): noSend expiry, discovery and version metadata.
* [BRC-175](/payments/0175), [BRC-189](/wallet/0189), [BRC-67](/transactions/0067): offline bundles, personal trust and SPV ancestry.

## Appendix A — Conformance Examples

### A.0. Reading and running the examples

These independent examples use the BRC-100 CWI cross-document JSON substrate. Each request and response below is the complete JSON message at that boundary. `type`, `isInvocation`, `id`, `call`, `args`, `status` and `result` are transport fields, not new wallet-method parameters. The authenticated browser message source and origin supply the caller identity outside this JSON; examples use `https://shop.example`. A caller-supplied originator cannot impersonate another application. A wallet adapter for another BRC-100 substrate carries the same method arguments and results with that substrate's framing.

Error messages and numerical codes shown here are illustrative CWI mappings: 1 is a general failure, 2 an unsupported action, 6 an invalid parameter, and 7 insufficient funds. Conformance requires the stated failure and lack of unauthorized side effects, not identical English wording or a new module error registry. UI text, approval frequency, transaction fee choice, signature nonce, and reference generation remain wallet choices. Exact result bytes shown are one valid outcome under the stated fixtures. The user-visible facts and validation outcomes are required. All amounts below are integer token units unless explicitly described as satoshis or display values.

Every case starts with a fresh copy of the stated fixture state. A reference to an earlier case imports its described state or bytes, not changes made by executing all preceding examples in order. Do not submit these synthetic transactions to a live network or interpret their identifiers as claims about existing assets. This is an offline reference fixture: its initial source transactions are prevalidated, coinbase anchors at distinct heights starting at 100000, matured at a fixture tip of 100200 in a controlled chain tracker and authoritative wallet store, and the configured test SLAP providers attest the stated topic states. A test harness must supply that trusted anchor state; the raw transactions alone are not SPV proofs. A production wallet must obtain and verify real chain evidence. Txid-only BEEF entries below resolve only through this authoritative fixture store; cases that remove that knowledge fail. BRC-176 packets additionally retain the complete token-source bodies.

For reproducible public keys the synthetic ordinary, Bob and privileged root private integers are respectively 11, 22 and 33. They are public test material, never production credentials. The anyone counterparty is private integer 1. Each key-controlled source deploy uses its original ISSUE tuple, keyID `color`, the nonce prefix listed below and suffix `Ag==`. Subsequent token outputs use their actual asset selector. A newly selected change nonce is fresh within that case. Default satoshi outputs use BRC-29 protocol \[2, "3241645161d8"], keyID "AQ== AA==", counterparty anyone and the respective owner root, independent of token keys.

Fixture identities:

```json
{
  "alice": "03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb",
  "bob": "03421f5fc9a21065445c96fdb91c0c1e2f2431741c72713b4b99ddcb316f31e9fc",
  "privileged": "021697ffa6fd9de627c077e3d2fe541084ce13300b0bec1146f95ae57f0d0bd6a5",
  "anyone": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}
```

| Name | Asset ID                                                             | Initial role and owner                                            | Prefix | Satoshis |
| ---- | -------------------------------------------------------------------- | ----------------------------------------------------------------- | ------ | -------- |
| A    | `e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0` | 1000 units, Alice                                                 | `AQ==` | 1000     |
| B    | `b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0` | 2000 units, Bob                                                   | `Aw==` | 1000     |
| C    | `91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47_0` | 3000 units, Alice                                                 | `BA==` | 1000     |
| H    | `ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c_0` | authority, Alice                                                  | `BQ==` | 1000     |
| F    | `5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474_0` | 2000 units, supplied by Bob; foreign OP\_2 OP\_EQUAL              | `Bg==` | 1000     |
| L    | `32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351_0` | 1000 units, Alice; legacy BRC-161                                 | `DQ==` | 1        |
| K    | `8d849b7a3070c7128635ba2696bd90804d2a352dee05ee07f2c1fc034d48ddd1_0` | 1000 units, Alice; foreign module-key script, not managed custody | `EA==` | 1000     |
| Q    | `da4d0facb10834ab595f43b1a6b6ca123f60d35ebf627aa6aede882b54747b39_0` | 1000 units, Alice; foreign module-key script, not managed custody | `EQ==` | 1000     |

The following complete source bodies fix the transaction IDs, scripts and amounts used in the messages. Each deploy is output 0. The two ordinary funding outputs each carry 10000 satoshis. All source bodies are resolved from the prevalidated store described above; labels A, B and so on are reading aids, never wire asset identifiers.

```json
[
  {
    "name": "A",
    "txid": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a0860107636f6c6f722d41ffffffff01e8030000000000001e0002e8036d76a9146cbd20c18911529dd4a707cbe7abcd296a097fe488ac00000000"
  },
  {
    "name": "B",
    "txid": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a1860107636f6c6f722d42ffffffff01e8030000000000001e0002d0076d76a914b06d328176e2fe89101c7701e8290c3ec7e41af288ac00000000"
  },
  {
    "name": "C",
    "txid": "91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a2860107636f6c6f722d43ffffffff01e8030000000000001e0002b80b6d76a91482a6d141057b6bd1d85839bd83b58f6e657c0a9088ac00000000"
  },
  {
    "name": "H",
    "txid": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a3860107636f6c6f722d48ffffffff01e8030000000000001c00006d76a91475a06c850b20c3c7e7e9d607f2cc64ba885daaed88ac00000000"
  },
  {
    "name": "F",
    "txid": "5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a4860107636f6c6f722d46ffffffff01e803000000000000070002d0076d528700000000"
  },
  {
    "name": "L",
    "txid": "32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0b03a78601066c6567616379ffffffff0101000000000000007b0063036f726451126170706c69636174696f6e2f6273762d323000457b2270223a226273762d3230222c226f70223a226465706c6f792b6d696e74222c22616d74223a2231303030222c22646563223a2232222c2273796d223a22474f4c44227d6876a9145e30b3732f30c22da5e91cf9556f2362258c07f288ac00000000"
  },
  {
    "name": "K",
    "txid": "8d849b7a3070c7128635ba2696bd90804d2a352dee05ee07f2c1fc034d48ddd1",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a8860107636f6c6f722d4bffffffff01e8030000000000001f0002e8036dab76a9143785bf49db52e85837fa1936ab927ef204d7bfcd88ac00000000"
  },
  {
    "name": "Q",
    "txid": "da4d0facb10834ab595f43b1a6b6ca123f60d35ebf627aa6aede882b54747b39",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0c03a9860107636f6c6f722d51ffffffff01e803000000000000230002e8036d63ab67ab6876a91483d002882c8286eaea767a517c52d5a8f246addf88ac00000000"
  },
  {
    "name": "funds",
    "txid": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0a03a586010566756e6473ffffffff0110270000000000001976a91496f7c6aff5874d2c57f8ca75466d3620fffb9bc488ac00000000"
  },
  {
    "name": "bobFunds",
    "txid": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e",
    "rawTransaction": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a6860108626f6266756e6473ffffffff0110270000000000001976a91430e2861546dcf665575f3c280857e6d2c4d944da88ac00000000"
  }
]
```

Complete explicit-input requests include already supplied fixture unlocking scripts, not unlockingScriptLength. These are signatures from the input owners’ normal transaction authorization paths; the receiving wallet still checks its own permissions and the entire final action. They do not demonstrate application access to protected private keys. The final ordinary change output is generated by the wallet at the specified changePositions index; applications never select the reserved default basket. Automatic selection/signing and deferred input-length flows are shown separately.

For successful createAction traces the fixture wallet uses version 1, locktime 0, final input sequences, the shown output order and a 10-satoshi fee, unless stated otherwise. It uses deterministic low-S signatures for the reproduced transaction IDs. Normal processing succeeds on Bitcoin and every involved overlay unless the case states a different result. A successful method result never by itself asserts mining finality. The fixture scripts and signature results are concrete encodings; wallet-specific fee/layout choices are allowed when the application has not fixed them, but must be reflected in consent.

### A.01. Capability discovery

The connection has explicitly adopted a BRC-205-compatible version profile. The application originates this request.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDAx",
  "call": "getVersion",
  "args": {}
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDAx",
  "status": "success",
  "result": {
    "version": "example-1.2.3; brc207-color=4;"
  }
}
```

The application recognizes capability revision 4, including required-profile enforcement and scoped receipt limits, across protocol, basket and label namespaces. This is capability information, not permission to spend or evidence of a user balance.

### A.02. Omitted capability

This wallet supports Colored Tokens but chooses not to advertise it. The connection uses the ordinary core version profile.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDAy",
  "call": "getVersion",
  "args": {}
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDAy",
  "status": "success",
  "result": {
    "version": "example-1.2.3"
  }
}
```

Omission does not establish lack of support. An application can attempt the intended module call and handle its result. It never substitutes generic signing. Account, provider or connection changes discard the previous capability cache; discovery is repeated.

### A.03. Unrecognized metadata

The compatible version profile is enabled.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDAz",
  "call": "getVersion",
  "args": {}
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDAz",
  "status": "success",
  "result": {
    "version": "example-1.2.3; brc207-color=5;"
  }
}
```

The application ignores the unsupported attribute value. It does not interpret revision 5 as revision 4, enumerate holdings from this response, or use cached authorization after a provider change.

### A.04. Original deploy key

Alice asks for the public key of her preserved ISSUE tuple, using prefix AQ== and suffix Ag==.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA0",
  "call": "getPublicKey",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "forSelf": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA0",
  "status": "success",
  "result": {
    "publicKey": "03a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
  }
}
```

The protocol encodes the UTF-8 strings AQ== and Ag== as 41513d3d and 41673d3d. It does not decode their base64 contents. This remains the key of the original deploy after its asset ID becomes known.

### A.05. Counterparty ownership

Alice requests her own child public key with Bob as counterparty. Bob independently derives the recipient key from Alice’s public identity with forSelf:false.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA1",
  "call": "getPublicKey",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "03421f5fc9a21065445c96fdb91c0c1e2f2431741c72713b4b99ddcb316f31e9fc",
    "forSelf": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA1",
  "status": "success",
  "result": {
    "publicKey": "03a5a62ba6fe30cda7de3c6f705389ba20dcde671a86a43810aaa14dfcc7c12c1a"
  }
}
```

Bob obtains exactly 03a5a62ba6fe30cda7de3c6f705389ba20dcde671a86a43810aaa14dfcc7c12c1a. The recipient can derive the matching private key. Changing the application origin or basket does not alter this derivation; permission remains origin-aware. A sender uses the recipient identity and forSelf:false, rather than accidentally deriving its own child.

### A.06. Privileged root selection

Alice explicitly authorizes access to the separate privileged fixture root.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA2",
  "call": "getPublicKey",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "forSelf": true,
    "privileged": true,
    "privilegedReason": "Access separate token identity"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA2",
  "status": "success",
  "result": {
    "publicKey": "0222da9b9ede01dc1ae6f1a038d9be2199db5ada2fec3690bca14b0b511ad16dc7"
  }
}
```

This key differs from the ordinary-root key. An output addressed to the ordinary identity is not credited to the privileged root merely because its remittance strings match. The selected root remains consistent throughout one action; mixed-root custody is not inferred.

### A.07. Reject alternate identifier spellings

The application uses the human-readable logical identifier instead of its defined wire encoding.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA3",
  "call": "getPublicKey",
  "args": {
    "protocolID": [
      2,
      "p color ISSUE AQ== Ag=="
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "forSelf": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA3",
  "status": "error",
  "description": "Invalid Colored Tokens protocol identifier",
  "code": 6
}
```

No key is derived. Uppercase selectors, underscore wire asset tags, whitespace-bearing or empty decoded remittances, and over-280-character protocol names likewise fail; the wallet neither truncates nor silently re-encodes them.

### A.08. Wallet without the module

This case uses a core BRC-100 wallet that does not implement Colored Tokens.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA4",
  "call": "getPublicKey",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "forSelf": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA4",
  "status": "error",
  "description": "Colored Tokens module is not supported",
  "code": 2
}
```

The reserved identifier is rejected before generic permissions. A prior advertisement from another account or provider does not change this result; the application must handle the failure without removing p color.

### A.09. Per-asset inventory

Alice has the admitted, unreserved A deploy output in its assigned value basket.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDA5",
  "call": "listOutputs",
  "args": {
    "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0",
    "include": "locking scripts"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDA5",
  "status": "success",
  "result": {
    "totalOutputs": 1,
    "outputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "satoshis": 1000,
        "lockingScript": "0002e8036d76a9146cbd20c18911529dd4a707cbe7abcd296a097fe488ac",
        "spendable": true
      }
    ]
  }
}
```

The script carries 1000 token units and the UTXO carries 1000 satoshis: these are different quantities. Other assets are not included. The ISSUE derivation remains attached to this output in the wallet’s authoritative records.

### A.10. Authority inventory

Alice holds the admitted H deploy authority, independently of any H value balance.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDEw",
  "call": "listOutputs",
  "args": {
    "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority",
    "include": "locking scripts"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDEw",
  "status": "success",
  "result": {
    "totalOutputs": 1,
    "outputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "satoshis": 1000,
        "lockingScript": "00006d76a91475a06c850b20c3c7e7e9d607f2cc64ba885daaed88ac",
        "spendable": true
      }
    ]
  }
}
```

The wallet presents one minting authority and its control implications, not a fungible balance of zero units available for payment. It does not combine this inventory with the H value basket.

### A.11. Relinquishing tracking preserves key protection

Alice permits the application to stop tracking A in this basket.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDEx",
  "call": "relinquishOutput",
  "args": {
    "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0",
    "output": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDEx",
  "status": "success",
  "result": {
    "relinquished": true
  }
}
```

The organizational operation does not spend the outpoint or grant cryptographic access. A later module signature still requires full transaction validation and asset consent, even if the application supplies a different basket name or deletes its own metadata.

### A.12. Auxiliary cryptography is unavailable

A generic cryptographic permission was previously granted to this origin.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDEy",
  "call": "createHmac",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [1, 2, 3]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDEy",
  "status": "error",
  "description": "HMAC is unavailable for Colored Tokens keys",
  "code": 2
}
```

The reserved protocol dispatch wins over that grant. The same prohibition applies to encryption, decryption, HMAC verification and key-linkage disclosure; a public getPublicKey or verifySignature call does not grant any of these capabilities.

### A.13. A digest is not spend authorization

The application asks the protected key to sign a 32-byte digest directly.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDEz",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "seekPermission": true,
    "hashToDirectlySign": [17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDEz",
  "status": "error",
  "description": "Colored Tokens signing requires a transaction envelope",
  "code": 6
}
```

No signature is returned, regardless of a generic protocol grant. Supplying a bare serialized preimage as data is also insufficient.

### A.14. Automatic selection, change and signing

Alice owns only A for this asset, and no input is reserved. Her existing policy allows this 300-unit payment and the final satoshi consequences. The wallet’s next fresh change tuple is prefix CA==, suffix Ag==.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE0",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The decoded plan carried by the labels is:

```json
{
  "version": 1,
  "assets": [
    "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0"
  ],
  "ownedOutputs": [],
  "operations": [
    {
      "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
      "operation": "transfer"
    }
  ],
  "inputPositions": [],
  "changePositions": [
    {
      "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
      "index": 1
    },
    {
      "assetId": null,
      "index": 2
    }
  ],
  "sighashTypes": []
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE0",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

The wallet selects e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0, derives its topic from the verified source, and checks current admission. It pays 300 A units to Bob, creates 700 A units of change at output 1, and ordinary satoshi change of 988 at output 2. Total satoshi input is 1000; output carriers are 1 and 1, ordinary change is 988, and the fee is 10. Alice’s token debit is 300 and satoshi debit is 11, of which 1 is Bob’s carrier and 10 is fee. Her own change carrier is not counted again as a bare-satoshi payment. After consensus validation and successful processing on the A topic, the wallet can expose the admitted change as spendable. No cryptographic call by the application is needed.

### A.15. Explicit managed input and remittance

Alice supplies the token outputs and original managed deploy input, with ordinary change constrained to output 2. Its authoritative source transaction is already in the wallet’s verified store.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE1",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The decoded plan is:

```json
{
  "version": 1,
  "assets": [
    "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0"
  ],
  "ownedOutputs": [
    {
      "outputIndex": 1,
      "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
      "derivationPrefix": "CA==",
      "derivationSuffix": "Ag==",
      "senderIdentityKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
    }
  ],
  "operations": [
    {
      "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
      "operation": "transfer"
    }
  ],
  "inputPositions": [],
  "changePositions": [
    {
      "assetId": null,
      "index": 2
    }
  ],
  "sighashTypes": []
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE1",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

Txid-only BEEF entries are resolved against the verified store, then checked against current overlay responses. Explicitly naming the input does not make it foreign or bypass module permissions. The supplied input signature is complete; no unlockingScriptLength requests deferred signing here. The output-1 remittance must derive the actual P2PKH hash before its 700 units count as Alice’s change. The original ISSUE tuple was used to sign the input; the newly created change uses the established asset ID.

### A.16. Current overlay answer and provider trust

The same payment is requested with Alice’s policy already satisfied. The configured SLAP trackers resolve a trusted provider for this issuance ID.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE2",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

Before signing, the wallet makes this BRC-24 lookup (it is an overlay request, not a CWI invocation):

```json
{
  "service": "ls_color_e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
  "query": {
    "type": "color-spendability",
    "version": 1,
    "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
    "topic": "tm_color_e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
    "outpoints": [
      "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0"
    ]
  }
}
```

The provider’s authenticated answer is:

```json
{
  "type": "output-list",
  "outputs": [
    {
      "outputIndex": 0,
      "beef": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229]
    }
  ]
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE2",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

In this fixture the txid-only entry resolves to the prevalidated A source body in the wallet’s store. The reconstructed outpoint, prefix, amount, role and script match. The service and topic are derived independently from the asset ID; the provider’s authenticated response supplies current membership. The lookup service remains free to implement unrelated queries as well.

### A.17. Absent current overlay membership

Alice requests the ordinary 300-unit payment, but the trusted lookup now returns the following valid negative answer:

```json
{
  "type": "output-list",
  "outputs": []
}
```

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE3",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE3",
  "status": "error",
  "description": "Selected token input is not currently admitted and unspent",
  "code": 1
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.18. Conflicting trusted providers

Two providers accepted under the wallet’s configured SLAP trust policy disagree: one returns A as admitted and unspent, the other returns an empty output-list. The wallet cannot establish current state.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE4",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE4",
  "status": "error",
  "description": "Conflicting overlay spendability answers",
  "code": 1
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.19. Unavailable overlay

The configured provider cannot be reached and the only cached answer is stale. An otherwise valid source proof and an existing spending allowance are available.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDE5",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDE5",
  "status": "error",
  "description": "Current overlay state is unavailable",
  "code": 1
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.20. Explicit input needs no topic hint

The explicit A source is authenticated and parsed by the wallet. The plan supplies no input-topic hints because version 1 has no such field.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDIw",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDIw",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

The wallet derives A and its topic from the source, validates current membership and processes the same authorized transfer as A.15. No caller hint is needed or accepted.

### A.21. A plan cannot select a lookup service URL

The application adds a lookupURL field to an otherwise valid plan, attempting to choose the provider for A. Provider selection belongs to the wallet configuration.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDIx",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d2c226c6f6f6b757055524c223a2268747470733a2f2f6f7665726c61792e6578616d706c65227d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDIx",
  "status": "error",
  "description": "Unknown Colored Tokens plan member lookupURL",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.22. Known transactions cannot hide tokens

The application omits A from the declared asset set and operations, while the authenticated input and both requested outputs still encode A. It also asks to trust known transaction IDs.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDIy",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b5d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a2265353434356231363133363034343834643735356164376331363966336533",
      "p color plan 1 4 3766363361363836633864316231306533626165303737366538646233616130345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e",
      "p color plan 2 4 746974794b6579223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b5d2c22696e70",
      "p color plan 3 4 7574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false,
      "trustSelf": "known",
      "knownTxids": [
        "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04"
      ]
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDIy",
  "status": "error",
  "description": "Declared assets omit authenticated token A",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.23. Unresolved txid-only source

Unlike the other fixture cases, the wallet has no authoritative source body or SPV evidence for the txid-only inputBEEF entry. The caller’s stated amount cannot supply that evidence.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDIz",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDIz",
  "status": "error",
  "description": "Unable to authenticate input source",
  "code": 1
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.24. Insufficient eligible token balance

A has already been spent in the fixture chain. Alice has no other admitted, unreserved A value inputs; her ordinary satoshi balance is ample.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI0",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI0",
  "status": "error",
  "description": "Insufficient available Colored Tokens asset balance",
  "code": 7
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.25. Insufficient satoshi funding

The requested outputs total 1002 satoshis before fees; the only authorized funding input has 1000 and no additional eligible satoshi input is available.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI1",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 1000,
        "outputDescription": "Additional ordinary payment"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI1",
  "status": "error",
  "description": "Insufficient satoshis for outputs and fee",
  "code": 7
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.26. User declines the final action

No standing policy covers the ordinary 300-unit payment. The wallet shows the A issuance ID, recipient, 700-unit change, 1-satoshi recipient carrier and 10-satoshi fee. Alice chooses Cancel.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI2",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI2",
  "status": "error",
  "description": "User declined the Colored Tokens spend",
  "code": 1
}
```

No signatures are released. The provisional reservation is released, no allowance is consumed, and no action is submitted. A wallet may use a different UI or policy organization while reaching this outcome.

### A.27. No silent permission escalation

No applicable asset-spending permission exists, and the application forbids an interactive permission request.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI3",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": false
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI3",
  "status": "error",
  "description": "Colored Tokens spending permission is required",
  "code": 1
}
```

The wallet neither prompts nor signs. A monthly allowance, per-action approval or manifest-informed policy would be an implementation choice, but a generic key-use grant alone cannot satisfy the missing consent.

### A.28. Fixed-supply issuance

Alice authorizes a new fixed supply of 1000000 units. The deploy is output 0 and uses ISSUE with prefix CQ==.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI4",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens issuance reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100c5d805453982b10c19f9bde6916d180bbd61d6a0415d8690d53009fe135a43d2022019a4dab39a1fb736771a8e201ab265fa3a9e561c190a8b4eb8e97590e6bd2a50412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "000340420f6d76a914cfebd325666fc742644d84fa85c1128d9cfa05ef88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2243513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d6669786564227d5d2c22696e707574506f73697469",
      "p color plan 3 4 6f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI4",
  "status": "success",
  "result": {
    "txid": "b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98"
  }
}
```

The final asset ID is b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98\_0. The wallet assigns basket p color b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98v0 after finalization, preserves the ISSUE tuple, and credits only after current output admission.

### A.29. Authority issuance

Alice authorizes creation of a minting authority rather than a value supply. The output-0 amount is zero and the ISSUE prefix is Cg==.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDI5",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens authIssue reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "4730440220420e5dbf7f424af17daa81a6f3c544c067709fcb657fb78f3ac3d7006e2c29b00220726a1e8b32e68528ed797d858ee0ccef0004da360db61365d975674460b8fd50412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "00006d76a914b56375cded5b215c8c800e540ab328f49de71bd688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2243673d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d617574686f72697479227d5d2c22696e707574506f",
      "p color plan 3 4 736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDI5",
  "status": "success",
  "result": {
    "txid": "9512264e2e558ac823b36f5b1ffa9583fb9f7017f5b730f0313403b69256384e"
  }
}
```

The final asset ID is 9512264e2e558ac823b36f5b1ffa9583fb9f7017f5b730f0313403b69256384e\_0 and the automatic basket is p color 9512264e2e558ac823b36f5b1ffa9583fb9f7017f5b730f0313403b69256384ev0 authority. No zero-value fungible coin or global issuance basket is created.

### A.30. Mint and retain authority

Alice holds the admitted H authority and consents to minting 2500 H units while retaining a replacement authority.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDMw",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens mint reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "473044022074196ce70c25df70ee0ab86893faac2345067e998e56a065ebb732324b7810d8022077264f5fd7367d6e5125ec73db093cb9b15431016b752a8f94cc4d3c20d541914121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec02c4096d76a914ce01fe373d9791886679196eb9ac72a4c946acd488ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0"
      },
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a9142f3b4539d738c6eb2e2cd273b6b292bb190791bf88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      }
    ],
    "labels": [
      "p color plan 0 9 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 9 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 9 635f30222c2264657269766174696f6e507265666978223a2242773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 9 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d2c7b226f7574707574496e646578223a312c2261737365744964223a2265636530343831393235373234356166333239303337303232",
      "p color plan 4 9 3564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c2264657269766174696f6e507265666978223a2243773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e64",
      "p color plan 5 9 65724964656e746974794b6579223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b",
      "p color plan 6 9 7b2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a226d696e74227d2c",
      "p color plan 7 9 7b2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a22617574686f7269",
      "p color plan 8 9 74792d7472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDMw",
  "status": "success",
  "result": {
    "txid": "1d7b44a06c61bb36e7d7dfdd014f9a47dcbc43bd484e9fed48d4f2531a50d687"
  }
}
```

The value and authority outputs go to separate H baskets. Authority contributes zero to input value, but authorizes the new binary value output. The plan explicitly covers the replacement authority as well as minting.

### A.31. Split authority

Alice consents to replacing one admitted H authority with two independently spendable authorities.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDMx",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens splitAuth reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402200a8af39a7ea2b0405271b309f9fdefe8253ddb904e92b68f264807c88436699902202356ab9b53205fcd3d7937dcd99854ca219f39298cd9b2c32a9ff9c8b34ead304121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a914ce01fe373d9791886679196eb9ac72a4c946acd488ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      },
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a9142f3b4539d738c6eb2e2cd273b6b292bb190791bf88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      }
    ],
    "labels": [
      "p color plan 0 8 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 8 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 8 635f30222c2264657269766174696f6e507265666978223a2242773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 8 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d2c7b226f7574707574496e646578223a312c2261737365744964223a2265636530343831393235373234356166333239303337303232",
      "p color plan 4 8 3564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c2264657269766174696f6e507265666978223a2243773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e64",
      "p color plan 5 8 65724964656e746974794b6579223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b",
      "p color plan 6 8 7b2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a22617574686f7269",
      "p color plan 7 8 74792d73706c6974227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDMx",
  "status": "success",
  "result": {
    "txid": "3db38d5f451b9f7050b12edc42bb22f44ed66f4361ea4727b7bc06d2512b64fa"
  }
}
```

Both zero-amount outputs enter the H authority basket after admission. The wallet explains the additional independent minting capability; zero net fungible debit does not remove consent.

### A.32. Combine authority inputs

The two outputs from the split-authority fixture have already been completed and admitted. Alice consents to consuming both and retaining one authority; the ordinary funding source is also available.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDMy",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens combine reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 250, 100, 43, 81, 210, 6, 188, 183, 39, 71, 234, 97, 67, 111, 214, 78, 244, 34, 187, 66, 220, 46, 177, 80, 112, 159, 27, 69, 95, 141, 179, 61, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "3db38d5f451b9f7050b12edc42bb22f44ed66f4361ea4727b7bc06d2512b64fa.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "4730440220310a6b452e5e965f904e779e35429ab88514d25b1a58046dbe9cca7be0ce49e502205d7f7f92e1916f16d7f53e95a88d4d3d2f804177305d59c41dc53efca95d84d541210296888d486bec6ca6236f09ca9742ef30349760228d6b0e0a6df2bb454492727c"
      },
      {
        "outpoint": "3db38d5f451b9f7050b12edc42bb22f44ed66f4361ea4727b7bc06d2512b64fa.1",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "473044022100b22d494f1e0caec6e9719fb48d0b7ebb64dfc39285d758713ff73d72579dca7e021f66633a05f019fee515840eca0333c011c2fda71cdb46d198bc99442b82663b4121020a393a5d067f0d3c32f03035020f61a7e6c04ef27d785bddc7fed39711deacfd"
      },
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 2",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100b7c63eeac4cafe632ee6e7902d36fda3613badb89fb49c8cdeba22e19d8f09b502207dff464edf21bcdf83006a0127f0cc3660232099c03aaaba50369cff19e5d377412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a9149d5932644941eb104b29ea290cd15b6b80c8ce2a88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 6 635f30222c2264657269766174696f6e507265666978223a2244413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226563653034383139323537323435616633323930333730323235",
      "p color plan 4 6 64623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a22617574686f726974792d636f6d62696e65227d5d2c22696e707574506f736974696f6e73223a5b5d2c22636861",
      "p color plan 5 6 6e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDMy",
  "status": "success",
  "result": {
    "txid": "bb0530f14fe9fc2c73be026275f0043fa9f4258be7fcb59dfe621d3124d9106c"
  }
}
```

Both consumed authority outpoints, including vout 1, receive their own current lookup. Their key tuples and signatures remain distinct. One new authority is tracked; the fee is paid from satoshis, never from a numeric authority balance.

### A.33. Transfer authority to another owner

Alice consents to transferring the H minting capability to Bob, whose destination key is not hers.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDMz",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens moveAuth reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100af5b110db4882aefd956997fbc50e84f07daf535ac7c23a9afa80555c2156da202207015fabec98584709653e5ced0ab94c527ebea6372b7d71ddef039c7d06c57ce4121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a914370babff5d3708da069e72279fea99962f0124cf88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 4 635f30222c226f7065726174696f6e223a22617574686f726974792d7472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e6465",
      "p color plan 3 4 78223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDMz",
  "status": "success",
  "result": {
    "txid": "1cbc904840983aa6a5c1f9ac2aa9062e7465a7100a96416e4ca6de3b97bbe201"
  }
}
```

No new H authority is credited to Alice. The UI identifies the delegation to Bob rather than presenting it as a zero-unit payment.

### A.34. End an authority

Alice consents to destroying this H authority without a replacement. The wallet knows it is the only remaining admitted H authority in this fixture.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM0",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens endAuth reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100812fe665adbacc10cfcddfe6c7bd7151c471dca7671702ab8630690c7636bc9e0220651ae53497e24db1b88cb6fa4194443e97fbfd3299e76cbbf75f86734e636f974121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "006a",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 4 635f30222c226f7065726174696f6e223a22617574686f726974792d656e64227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d",
      "p color plan 3 4 5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM0",
  "status": "success",
  "result": {
    "txid": "5bcf30ba536401451d3d844d7e93e29f3c39c48d15bb7bb0fadf28303f8def36"
  }
}
```

There is no replacement authority. In this stated fixture minting ends. Ending one authority would not establish that minting had ended if other authorities remained.

### A.35. Explicit value burn

Alice consents to destroying all 1000 A units. The non-token data output receives one satoshi; 989 ordinary satoshis return to Alice and the fee is 10.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM1",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens burn reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "473044022028a1fb33a715e205898650e72470b248547f86f861bc6705dd97d2b694f23684022064c1da2c6f701f4784808c37218f64231626bda466d89a47e6b3e9681653e299412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "006a",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a226275726e227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173",
      "p color plan 3 4 685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM1",
  "status": "success",
  "result": {
    "txid": "622e19b68192bd9bee2625e4e01a6a2971d92b0425c84bdf68dc900c53452440"
  }
}
```

The burn is a separately consented consequence, not a fee or a payment to another asset. No replacement A balance is created.

### A.36. A transfer declaration cannot authorize a burn

The transaction spends A but creates no A outputs. The plan only declares transfer.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM2",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens burn reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "473044022028a1fb33a715e205898650e72470b248547f86f861bc6705dd97d2b694f23684022064c1da2c6f701f4784808c37218f64231626bda466d89a47e6b3e9681653e299412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "006a",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM2",
  "status": "error",
  "description": "Undeclared Colored Tokens burn",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.37. Over-output value transition

The first output now encodes 301 units while the change still encodes 700. The input contains exactly 1000 and no authority. Losing one unit of coverage would invalidate all value outputs of this asset under BRC-162.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM3",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022d016d76a914d70bcb7e47c238c22be672040bd7565c5f003bc688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM3",
  "status": "error",
  "description": "Token outputs exceed authenticated input balance",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.38. Exact uint64 amount and display decimals

Alice authorizes a fixed-supply deploy at the maximum BRC-162 amount. The payload is a DAG-CBOR map with dec:18 and sym:G.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM4",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens maxIssue reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "4830450221009717cf34d124cb19d5a84d4e31113af0fb0c8e9366da4959cba6d58fc9f6b3db0220545b765525f2ee34d26e227f438d12de6d7a027bf48de2c4d930b444cb546871412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "0009ffffffffffffffff006d0ca263646563126373796d61477576a9149850e952220eede3e0e72e72f8978ff03a92caa988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2244773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d6669786564227d5d2c22696e707574506f73697469",
      "p color plan 3 4 6f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM4",
  "status": "success",
  "result": {
    "txid": "520f26d99fd56c7720ee96497b17a04d2b7c845eb6bce3b4682eec06bf36a5f1"
  }
}
```

The script amount is exactly 18446744073709551615 units; the display may be 18.446744073709551615 G. Neither JavaScript floating-point rounding nor display decimals may change the integer. The maximum uses a nine-byte minimal positive script number. Aggregates of multiple such inputs use wider exact arithmetic even though each output remains within uint64. Unknown or malformed display fields in a valid map are ignored for display, not used to create supply. A nonempty payload that is not a valid DAG-CBOR map has no defined attributes; inherited deploy requirements still apply.

### A.39. Deploy at an invalid output position

The caller requests a BRC-162 deploy at output 1 and preserves output order.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDM5",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens issuance reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScriptLength": 106
      }
    ],
    "outputs": [
      {
        "lockingScript": "006a",
        "satoshis": 1,
        "outputDescription": "Leading non-token data output"
      },
      {
        "lockingScript": "000340420f6d76a914cfebd325666fc742644d84fa85c1128d9cfa05ef88ac",
        "satoshis": 1,
        "outputDescription": "Invalid deploy at output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2243513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d6669786564227d5d2c22696e707574506f73697469",
      "p color plan 3 4 6f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDM5",
  "status": "error",
  "description": "BRC-162 deploy must be output zero",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.40. Receive legacy supply and spend in binary form

The L fixture is an admitted BRC-161 deploy+mint held by Alice with the preserved ISSUE tuple and one satoshi. The ordinary funding input supplies the remaining satoshis.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQw",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens legacyTransfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 81, 163, 105, 87, 99, 203, 143, 187, 119, 20, 243, 224, 157, 22, 131, 97, 82, 145, 212, 105, 40, 31, 180, 156, 189, 32, 243, 94, 64, 184, 201, 50, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402203864e78b420639c6e92d66fc968639cffcc3d7de9fcfecf79b2b0b4ea71bd7fb02202539881148d137c05f3023541b4f95553ab8026d8948bc88d9955de4e368307441210216a78007b6f035e189d8395f8b41b75244b5eb07874245d1181c0267c06fa1d9"
      },
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100a16e78ecedefe7b654f7a12b17b4ba073a47fa8271dc5f43403951090b57e132022064508eaab424448683d29d3b2f206bb57b65b64d2cc688eb68475367eaadf609412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2051a3695763cb8fbb7714f3e09d1683615291d469281fb49cbd20f35e40b8c9320258026d76a914ed54bf58ddad122587bd922380ca1e49ce1b63e088ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2051a3695763cb8fbb7714f3e09d1683615291d469281fb49cbd20f35e40b8c9320290016d76a914e1c69924d8e7dcd97d02caaf5721fcf08be6105288ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color 32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22333263396238343035656633323062643963623431663238363964343931353236313833313639646530663331343737626238666362363335373639613335315f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22333263396238343035656633323062643963623431663238363964343931353236313833313639646530663331343737626238666362363335373639613335",
      "p color plan 2 6 315f30222c2264657269766174696f6e507265666978223a2244673d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a223332633962383430356566333230626439636234316632383639",
      "p color plan 4 6 64343931353236313833313639646530663331343737626238666362363335373639613335315f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQw",
  "status": "success",
  "result": {
    "txid": "b543564e9223703056e248167f962659b4c559a6e803ea3befc5551b2fea4a5d"
  }
}
```

The legacy source carries 1000 units with display dec:"2". The new outputs use BRC-162, sending 600 units to Bob and 400 to Alice. They retain the same L issuance ID and basket identity across encodings. The wallet validates the old encoding; it does not assume that any unrelated BSV-20 basket or preexisting module is interoperable.

### A.41. Two-asset swap

Alice provides A and Bob provides B. Bob has supplied a valid unlocking script for the exact final transaction. Alice consents to exchanging all 1000 A units for 2000 B units.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQx",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens swap reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402204e2757151e54a28d80e672b83cf430d3ecd6b2c6de7ce47023760cb695c922870220594931a3004c2e1f08b3834bb1ddf37b269f1481d1c3576ab54836e21d865bf7412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "20a17ef53d1575a70bb727fd09ae1727270e81ba78af0d015890e67a6fde6563b702d0076d76a91444990bee442fb159a9caaa597b98992d2d8d698688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 8 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2262373633363564",
      "p color plan 1 8 6536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 8 744964223a22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 8 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 8 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 8 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a226237363336356465366637616536393035383031306461663738626138313065323732373137616530396664323762373062613737",
      "p color plan 6 8 35313533646635376561315f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e6465",
      "p color plan 7 8 78223a337d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQx",
  "status": "success",
  "result": {
    "txid": "a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196"
  }
}
```

Both current overlays are checked. The wallet shows A debit 1000 and B credit 2000 as separate legs, with a 10-satoshi net cost to Alice. Bob’s input is authenticated before it offsets any funding requirement. Its admission is not inferred from the A overlay.

### A.42. Three-asset composable swap

Alice contributes A and C while Bob contributes B, with the requested output order fixed.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQy",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens three reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 71, 125, 237, 175, 241, 55, 176, 194, 91, 25, 15, 186, 60, 120, 14, 58, 52, 158, 229, 193, 145, 71, 151, 51, 186, 214, 251, 133, 231, 251, 251, 145],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100959896e1cd8d49db936a61d8894ac2c52efa315bd93be039d226f122c2957be8022053311c7e7e9ba79fc6dd07da579670ff286b00a35c9bad10cd7f0a21a489028a412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402201ce51f3acff8617c93c88ffd6732a192a6d235d402663d314b5ea78691b5d465022051724c860e5ad19b60a61e52133ebca5871e41674a512b5f8a9509b734840f9a4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      },
      {
        "outpoint": "91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47.0",
        "inputDescription": "Authenticated input 2",
        "sequenceNumber": 4294967295,
        "unlockingScript": "4830450221008603807473b5cd00c78fc71f92ea59fb6069bb201da2080656a04fc250ecabf7022059f7a60c76d7b313a08af096a3cf4f9b501684c44638a8ead7cb72e414c7504e4121034fc95ac842ac9d727b76b982dd4b4dc7b6a82a8c2cd49f5609c86bdbe986fc8a"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "20a17ef53d1575a70bb727fd09ae1727270e81ba78af0d015890e67a6fde6563b702d0076d76a91444990bee442fb159a9caaa597b98992d2d8d698688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0"
      },
      {
        "lockingScript": "20477dedaff137b0c25b190fba3c780e3a349ee5c191479733bad6fb85e7fbfb9102b80b6d76a914d85945b6c0fb8d8e9985024ff702f38d5d5f86f388ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 2"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 998,
        "outputDescription": "Agreed output 3"
      }
    ],
    "labels": [
      "p color plan 0 9 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2262373633363564",
      "p color plan 1 9 6536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c223931666266626537383566626436626133333937343739316331653539653334336130653738",
      "p color plan 2 9 33636261306631393562633262303337663161666564376434375f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a2262373633363564653666376165363930353830313064616637",
      "p color plan 3 9 3862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c2264657269766174696f6e507265666978223a2242773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e64",
      "p color plan 4 9 65724964656e746974794b6579223a22303237396265363637656639646362626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b",
      "p color plan 5 9 7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e736665",
      "p color plan 6 9 72227d2c7b2261737365744964223a22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c226f7065726174696f6e223a22747261",
      "p color plan 7 9 6e73666572227d2c7b2261737365744964223a22393166626662653738356662643662613333393734373931633165353965333433613065373833636261306631393562633262303337663161666564376434375f30222c226f7065726174696f6e223a",
      "p color plan 8 9 227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a347d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQy",
  "status": "success",
  "result": {
    "txid": "31a5175d4b65aaaeddadc04e46fb348c0ab462d3dadf698d5667ecd09b16b2a0"
  }
}
```

The wallet presents debits of 1000 A and 3000 C, credit of 2000 B, and Alice’s 10-satoshi cost. None of the asset amounts cancel across issuance IDs. All three overlays, three inputs and five outputs are validated before automatic signing.

### A.43. Sell tokens for ordinary satoshis

Alice exchanges 1000 A units with Bob for a 5000-satoshi contribution. The source token carrier also contains 1000 satoshis.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQz",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens sell reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402207af54f25df445bdb6cd172a57ba5216af2277b0e73b77b3f97d658f5590c73ee022067f8255717f7bbe3d91e64fd241fd3fbb9c075c9bf1090bb971a38583d0de4f1412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100a0ddc115675c11d6ad4ef023d7df6cd2272fe4b1440d38086ce9990453c3e38f02202e7ed5cf7c956a83d69f2da2568ca943f47ef140291349f19e2dd30645d8fe4b412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 4999,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQz",
  "status": "success",
  "result": {
    "txid": "b598ba8de38ed34ce74dad2637bfdf73d68ed0a13ebc9f7f5e2b86ad6333ff65"
  }
}
```

Alice receives 5990 ordinary satoshis in the final layout: her released 1000 carrier satoshis plus 5000 from Bob minus a 10-satoshi fee. Her net satoshi increase is 4990. Bob receives the token in a 1-satoshi carrier and 4999 ordinary change. The 5000 contribution is a swap leg; carrier and fee accounting remain explicit.

### A.44. Buy tokens with ordinary satoshis

Alice contributes the 10000-satoshi ordinary funding input and buys Bob’s 2000 B units.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ0",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens buy reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100da18490509afbcb2e1e2d1154b6364f2cfccebd3a8cee6865b3e13ab7b81fbcd022007d6615ab66e16f8662b6f26ec24393883e5884d6475b6e39798589ec9b5d9a74121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      },
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "4830450221008b73a5d16532772dee999c3185e524e6017d172a364363177a28e25094a19f8a02203218bf0554d9eb44f7671ce0f06b60d9df969774ee10503bd7b8e1b62433f9db412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "20a17ef53d1575a70bb727fd09ae1727270e81ba78af0d015890e67a6fde6563b702d0076d76a91444990bee442fb159a9caaa597b98992d2d8d698688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0",
        "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 5990,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561",
      "p color plan 2 6 315f30222c2264657269766174696f6e507265666978223a2242773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226237363336356465366637616536393035383031306461663738",
      "p color plan 4 6 62613831306532373237313761653039666432376237306261373735313533646635376561315f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ0",
  "status": "success",
  "result": {
    "txid": "45f6002f4de3b25501dea6c12a75b6a6de79b859a95a48a6ec7df0eef2ba298d"
  }
}
```

Alice receives 2000 B units on a 1-satoshi output and 4999 ordinary change: a net satoshi debit of 5000 including her new carrier. Bob receives 5990 ordinary satoshis after paying the 10-satoshi fee from the combined layout. Normal satoshi policy, including DSAP if implemented, applies in addition to token policy.

### A.45. Foreign script input with valid execution

Alice exchanges A for F. The admitted F deploy uses OP\_2 OP\_EQUAL after its token prefix, and the caller supplies unlocking script 52.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ1",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens foreign reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 116, 244, 113, 121, 41, 186, 221, 99, 164, 186, 213, 203, 120, 134, 20, 154, 145, 205, 234, 135, 247, 200, 9, 210, 209, 144, 73, 20, 226, 226, 211, 94],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402207e2da7e5cb5ce2c14985c2e069002c5c0434b3b1e7983d85262b8cb7ba70ee7102202b2e72386c3e15fcb05d27d8b3bb1a6a4a9b198c7f586a6d14d853619220be54412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "52"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2074f4717929badd63a4bad5cb7886149a91cdea87f7c809d2d1904914e2e2d35e02d0076d76a91486f9dbe298d4239ea2fc02b77a95db6e74e13eca88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color 5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 8 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2235656433653265",
      "p color plan 1 8 3231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 8 744964223a22356564336532653231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 8 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 8 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 8 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a223565643365326532313434393930643164323039633866373837656163643931396131343836373863626435626161343633646462",
      "p color plan 6 8 61323937393731663437345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e6465",
      "p color plan 7 8 78223a337d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ1",
  "status": "success",
  "result": {
    "txid": "a8c26eee7da4265bc1af05df20cc8130f5095aa1aeeab70c6c459f23d3f66a70"
  }
}
```

The wallet authenticates F’s source and overlay membership before crediting its incoming amount. It executes the foreign script successfully, checks conservation for A and F, and manages only the newly created F P2PKH output addressed to Alice. The foreign source was never assumed to be a wallet-managed P2PKH coin.

### A.46. Invalid provided foreign unlocking script

All source and overlay checks succeed, but the provided foreign unlocking script fails OP\_2 OP\_EQUAL.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ2",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens foreign reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 116, 244, 113, 121, 41, 186, 221, 99, 164, 186, 213, 203, 120, 134, 20, 154, 145, 205, 234, 135, 247, 200, 9, 210, 209, 144, 73, 20, 226, 226, 211, 94],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402207e2da7e5cb5ce2c14985c2e069002c5c0434b3b1e7983d85262b8cb7ba70ee7102202b2e72386c3e15fcb05d27d8b3bb1a6a4a9b198c7f586a6d14d853619220be54412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "51"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2074f4717929badd63a4bad5cb7886149a91cdea87f7c809d2d1904914e2e2d35e02d0076d76a91486f9dbe298d4239ea2fc02b77a95db6e74e13eca88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color 5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 8 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2235656433653265",
      "p color plan 1 8 3231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 8 744964223a22356564336532653231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 8 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 8 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 8 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a223565643365326532313434393930643164323039633866373837656163643931396131343836373863626435626161343633646462",
      "p color plan 6 8 61323937393731663437345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e6465",
      "p color plan 7 8 78223a337d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ2",
  "status": "error",
  "description": "Foreign input script validation failed",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.47. Create an offer before the other party signs

Alice proposes the same two-asset swap but Bob has not provided his unlocking script. Alice authorizes a pending offer whose ALL|FORKID signature binds both named inputs and all outputs. Her wallet has applied its configured A/B provider and counterparty-acceptance policy and verified the settlement safeguards assumed by this fixture.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ3",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens swap reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScriptLength": 106
      },
      {
        "outpoint": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScriptLength": 107
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "20a17ef53d1575a70bb727fd09ae1727270e81ba78af0d015890e67a6fde6563b702d0076d76a91444990bee442fb159a9caaa597b98992d2d8d698688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 8 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2262373633363564",
      "p color plan 1 8 6536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 8 744964223a22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 8 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 8 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 8 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a226237363336356465366637616536393035383031306461663738626138313065323732373137616530396664323762373062613737",
      "p color plan 6 8 35313533646635376561315f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e6465",
      "p color plan 7 8 78223a337d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ3",
  "status": "success",
  "result": {
    "signableTransaction": {
      "tx": [1, 1, 1, 1, 152, 218, 212, 126, 194, 31, 61, 68, 153, 28, 91, 7, 54, 249, 60, 118, 54, 236, 39, 135, 147, 145, 30, 39, 36, 162, 194, 47, 126, 130, 222, 63, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 0, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
      "reference": "bWFuZGFsYS1zd2FwLTAwMDE="
    }
  }
}
```

The wallet returns a pending reference and a partially signed subject: Alice’s input is signed, Bob’s is empty. Sources, current overlays and token amounts have been validated, but the transaction cannot yet complete. The wallet reserves A and records the released capability; it does not credit B or claim overlay admission. The signature binds transaction terms, while admission still depends on both topics. Bob’s future signature can be exchanged separately. This response also illustrates signAndProcess:false without a new core API.

### A.48. Complete the pending swap

Start with the independently established pending offer described in A.47. Both named token sources remain admitted and unspent, and the permission still covers the offer. Bob supplies his exact final script.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ4",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE=",
    "spends": {
      "1": {
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      }
    },
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ4",
  "status": "success",
  "result": {
    "txid": "a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196"
  }
}
```

The wallet preserves the pending plan and previously committed fields, refreshes every involved overlay, validates both complete input scripts, and processes the transaction on both asset topics. It consumes the existing authorization record once. It records actual admission results before making incoming B spendable.

### A.49. Counterparty input spent before completion

Start with the pending offer in A.47, but Bob’s named B outpoint has now been consumed by a different valid transaction.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDQ5",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE=",
    "spends": {
      "1": {
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      }
    },
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDQ5",
  "status": "error",
  "description": "Counterparty token input is no longer spendable",
  "code": 1
}
```

The wallet fails completion and does not replace the named input or reuse Alice’s signature on changed terms. Because Alice’s released signature commits that outpoint, the old offer cannot complete after the conflict. The wallet updates exposure once the conflict is authoritatively established.

### A.50. Reject an invalid completion script

Start with the pending offer in A.47. The caller supplies an invalid P2PKH unlocking script for Bob.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDUw",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE=",
    "spends": {
      "1": {
        "unlockingScript": "51"
      }
    },
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDUw",
  "status": "error",
  "description": "Input script validation failed",
  "code": 6
}
```

The action remains pending; no completed transaction is returned or processed. Alice’s released capability and reservation are retained. Missing foreign scripts may be deferred in a partial offer, but invalid supplied scripts cannot be accepted as a completed spend.

### A.51. Previously committed fields cannot change silently

Start with the pending offer in A.47. The caller changes Bob’s sequence while supplying the old unlocking script.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDUx",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE=",
    "spends": {
      "1": {
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb",
        "sequenceNumber": 4294967294
      }
    },
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDUx",
  "status": "error",
  "description": "Completion changes previously committed transaction fields",
  "code": 6
}
```

The existing ALL|FORKID authorization and signatures do not cover this change. The wallet rejects this completion. A separately revalidated action with consent and new signatures may use a different sequence; an old reference does not waive those requirements.

### A.52. ALL transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. The signature commits every input outpoint and every output. The transfer terms and 700-unit change are bound.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDUy",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The decoded data envelope is:

```json
{
  "version": 1,
  "tx": [1, 1, 1, 1, 76, 19, 219, 198, 231, 103, 161, 231, 249, 22, 41, 70, 110, 10, 145, 157, 171, 187, 103, 146, 172, 73, 29, 9, 203, 92, 140, 231, 159, 8, 20, 92, 2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 1, 0, 0, 0, 1, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 0, 255, 255, 255, 255, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 44, 1, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 188, 2, 109, 118, 169, 20, 197, 248, 172, 11, 49, 150, 233, 155, 255, 17, 106, 15, 201, 180, 26, 129, 151, 72, 26, 9, 136, 172, 220, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
  "inputIndex": 0,
  "sighashType": 65,
  "ownedOutputs": [
    {
      "outputIndex": 1,
      "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
      "derivationPrefix": "CA==",
      "derivationSuffix": "Ag==",
      "senderIdentityKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
    }
  ]
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDUy",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 230, 211, 128, 197, 139, 58, 41, 235, 150, 216, 47, 69, 115, 158, 17, 71, 10, 16, 219, 11, 105, 179, 121, 227, 179, 113, 239, 18, 99, 254, 51, 167, 2, 32, 115, 136, 26, 81, 11, 237, 158, 175, 95, 45, 166, 12, 25, 3, 147, 82, 225, 55, 152, 6, 78, 221, 152, 206, 168, 20, 249, 193, 219, 184, 26, 206]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.53. NONE transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. The signature commits input outpoints but no outputs. Alice has instead authorized an irrevocable, unconditional disposition of the entire 1000-unit input and its carrier satoshis at signature release. The illustrated 300-unit payment and change are not promised consideration; any valid completion remains within that already authorized disposition.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDUz",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 54, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDUz",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 214, 127, 185, 71, 19, 39, 240, 162, 114, 215, 40, 50, 70, 129, 73, 251, 95, 25, 166, 77, 161, 232, 149, 82, 169, 53, 212, 215, 39, 181, 73, 106, 2, 32, 65, 121, 201, 253, 140, 129, 78, 217, 40, 254, 189, 68, 120, 81, 206, 43, 77, 34, 85, 37, 106, 46, 196, 18, 138, 196, 14, 200, 15, 19, 131, 44]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.54. SINGLE transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. Input 0 commits only output 0: Bob’s 300-unit output. Alice has authorized the remaining 700 units and affected satoshis as an unconditional disposition at signature release. Output 0 is binding, while the illustrated change is not promised consideration; no later completion can worsen the disposition she authorized.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU0",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 55, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU0",
  "status": "success",
  "result": {
    "signature": [48, 68, 2, 32, 127, 224, 178, 143, 47, 4, 162, 212, 129, 230, 226, 120, 61, 225, 181, 200, 180, 198, 60, 221, 161, 118, 30, 53, 166, 69, 250, 144, 164, 83, 206, 115, 2, 32, 40, 26, 233, 201, 17, 14, 2, 249, 247, 204, 52, 61, 253, 58, 129, 163, 221, 204, 14, 250, 47, 208, 13, 7, 22, 12, 224, 222, 98, 211, 244, 167]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.55. ALL with ANYONECANPAY transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. All outputs are committed, but other input outpoints are not. The wallet does not claim hashPrevouts binds a named counterparty UTXO. In this one-input reference transfer the input itself and its fixed outputs remain bound.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU1",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 49, 57, 51, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU1",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 190, 234, 39, 47, 52, 246, 56, 210, 28, 12, 243, 49, 23, 79, 126, 235, 217, 144, 107, 39, 34, 193, 63, 229, 97, 123, 137, 102, 248, 62, 169, 30, 2, 32, 23, 134, 32, 168, 195, 156, 237, 197, 147, 114, 98, 19, 2, 37, 230, 70, 218, 41, 60, 18, 207, 205, 138, 178, 112, 224, 59, 16, 183, 37, 33, 98]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.56. NONE with ANYONECANPAY transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. Neither other inputs nor any outputs are committed. Alice authorizes the entire 1000 units and their carrier satoshis as an irrevocable, unconditional disposition at signature release. No receipt, change or counterparty action is a condition of that disposition.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU2",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 49, 57, 52, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU2",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 134, 23, 254, 177, 20, 235, 146, 239, 189, 255, 81, 74, 213, 111, 243, 9, 18, 240, 1, 54, 9, 45, 164, 25, 40, 72, 179, 78, 67, 76, 232, 200, 2, 32, 74, 155, 154, 152, 159, 130, 250, 10, 1, 9, 74, 21, 193, 213, 98, 78, 85, 22, 153, 157, 115, 112, 17, 191, 77, 69, 80, 44, 104, 104, 76, 75]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.57. SINGLE with ANYONECANPAY transaction signature

The source is A, using its original ISSUE tuple. The application requests a signature for the unsigned transfer subject; the wallet validates its source, topic and prospective output ownership. Only the signing input and its corresponding output are committed. Alice authorizes the remaining value and affected satoshis as an unconditional disposition at signature release. The wallet does not present a named foreign input or change as guaranteed consideration.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU3",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 49, 57, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU3",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 184, 185, 25, 63, 188, 190, 5, 39, 151, 185, 205, 156, 85, 204, 244, 164, 155, 152, 107, 150, 132, 223, 147, 183, 220, 72, 212, 218, 130, 106, 177, 0, 2, 32, 40, 215, 180, 97, 196, 149, 126, 216, 79, 222, 70, 145, 214, 115, 215, 250, 188, 128, 70, 182, 20, 59, 87, 119, 70, 231, 163, 210, 77, 229, 62, 99]
  }
}
```

The wallet computes the consensus preimage itself, obtains consent covering the actual commitment, atomically reserves the input and returns DER low-S signature bytes without a sighash byte. The application appends the requested flag when constructing the unlocking script. A different consensus-valid signature nonce may produce different DER bytes; the digest and successful verification are the conformance criteria. Revalidation on later wallet completion cannot constrain an external party holding this signature.

### A.58. Verify against the transaction digest

The signature from A.52 is checked using the same derivation tuple and the actual transaction digest.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU4",
  "call": "verifySignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "forSelf": true,
    "signature": [48, 69, 2, 33, 0, 230, 211, 128, 197, 139, 58, 41, 235, 150, 216, 47, 69, 115, 158, 17, 71, 10, 16, 219, 11, 105, 179, 121, 227, 179, 113, 239, 18, 99, 254, 51, 167, 2, 32, 115, 136, 26, 81, 11, 237, 158, 175, 95, 45, 166, 12, 25, 3, 147, 82, 225, 55, 152, 6, 78, 221, 152, 206, 168, 20, 249, 193, 219, 184, 26, 206],
    "hashToDirectlyVerify": [241, 164, 35, 205, 74, 113, 147, 82, 48, 19, 29, 22, 246, 126, 236, 56, 155, 172, 249, 102, 21, 5, 230, 44, 181, 173, 199, 209, 213, 198, 171, 210]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU4",
  "status": "success",
  "result": {
    "valid": true
  }
}
```

The envelope’s JSON bytes are not the signed message. Verification does not itself authorize spending or reserve an input. Appending a script sighash byte to the DER-only API signature would also change the API input and is not required here.

### A.59. Invalid consensus signature context

The source is an ordinary P2PKH spend, but the request supplies sighashType 0.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDU5",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 48, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDU5",
  "status": "error",
  "description": "Invalid signature mode for this consensus context",
  "code": 6
}
```

The wallet rejects through consensus-mode validation. Support for ALL, NONE, SINGLE and ANYONECANPAY does not authorize invalid modes or permit a token-specific whitelist that excludes valid ones.

### A.60. Derivation selector must match the source

The transaction spends A, but the request names B’s asset in the key protocol.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDYw",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDYw",
  "status": "error",
  "description": "Colored Tokens source asset does not match protocol selector",
  "code": 6
}
```

The wallet does not select a convenient alternative key, sign a default-basket input, or accept the requested selector as evidence. ISSUE is permitted for A only because its authoritative record preserves that original deploy tuple.

### A.61. Preserve exact composable positions

Alice and Bob require input positions 0 and 1, the three requested outputs in their given order, and generated ordinary change at output 3. All fee funding is already accounted for.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDYx",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens swap reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402204e2757151e54a28d80e672b83cf430d3ecd6b2c6de7ce47023760cb695c922870220594931a3004c2e1f08b3834bb1ddf37b269f1481d1c3576ab54836e21d865bf7412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "20a17ef53d1575a70bb727fd09ae1727270e81ba78af0d015890e67a6fde6563b702d0076d76a91444990bee442fb159a9caaa597b98992d2d8d698688ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 10 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2262373633363564",
      "p color plan 1 10 6536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 10 744964223a22623736333635646536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 10 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 10 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 10 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a226237363336356465366637616536393035383031306461663738626138313065323732373137616530396664323762373062613737",
      "p color plan 6 10 35313533646635376561315f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b7b226f7574706f696e74223a226535343435623136313336303434383464373535616437633136396633",
      "p color plan 7 10 65333766363361363836633864316231306533626165303737366538646233616130342e30222c22696e646578223a307d2c7b226f7574706f696e74223a2262373633363564653666376165363930353830313064616637386261383130653237323731",
      "p color plan 8 10 3761653039666432376237306261373735313533646635376561312e30222c22696e646578223a317d5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a337d5d2c22736967686173685479",
      "p color plan 9 10 706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDYx",
  "status": "success",
  "result": {
    "txid": "a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196"
  }
}
```

The wallet honors inputPositions and randomizeOutputs:false. No extra funding or change is inserted into these agreed positions. The same rule protects a validated covenant’s positional requirements; if a covenant has additional conditions they are also executed, not inferred from these constraints.

### A.62. Unsatisfiable output positions

The requested recipient output occupies index 0, and generated A change is also constrained to index 0 while output order is fixed.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDYy",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a307d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDYy",
  "status": "error",
  "description": "Conflicting requested and change positions",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.63. Plan chunks interleaved and out of order

The ordinary payment plan arrives with chunks in reverse array order and an unrelated action label between them.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDYz",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 4 5 73223a5b5d7d",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "color example",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDYz",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

The wallet reconstructs exactly one plan by the encoded partIndex, not array order. The ordinary label remains organizational metadata. Plan labels neither grant permission nor become BRC-177 expiry labels.

### A.64. Duplicate plan chunk

The first plan part appears twice. Its bytes happen to be identical.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY0",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d",
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY0",
  "status": "error",
  "description": "Duplicate Colored Tokens plan part",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.65. Missing plan chunk

The declared part count still names a final part that never arrives.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY1",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY1",
  "status": "error",
  "description": "Incomplete Colored Tokens action plan",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.66. Duplicate JSON members

The data bytes contain two version members. The duplication is malformed even though the values agree.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY2",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY2",
  "status": "error",
  "description": "Duplicate member in Colored Tokens metadata",
  "code": 6
}
```

The parser detects duplication before treating the JSON object as a map. The same rule applies at every nested level, including remittances, operations and constraints. Missing required members also prevent signing.

### A.67. Caller-supplied service URL is not an envelope field

The otherwise complete signature envelope adds an unknown lookupURL member. The wallet derives A’s service and uses its own configured providers.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY3",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 44, 34, 108, 111, 111, 107, 117, 112, 85, 82, 76, 34, 58, 34, 104, 116, 116, 112, 115, 58, 47, 47, 111, 118, 101, 114, 108, 97, 121, 46, 101, 120, 97, 109, 112, 108, 101, 34, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY3",
  "status": "error",
  "description": "Unknown Colored Tokens envelope member lookupURL",
  "code": 6
}
```

The wallet rejects the closed-object violation and makes no request to that URL. It independently derives the lookup name and uses its configured SLAP trust policy.

### A.68. Reject invalid byte-array elements

The tx member includes 256, which is not a byte.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY4",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 50, 53, 54, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY4",
  "status": "error",
  "description": "Invalid transaction byte array",
  "code": 6
}
```

Strings, base64, numeric-key objects, booleans, fractions and empty tx arrays likewise cannot stand in for the required nonempty byte array. Bounds and encoding are checked before parsing or allocation; no truncation or modulo conversion is permitted.

### A.69. Reject malformed UTF-8

The data bytes are not valid UTF-8 and therefore cannot encode a request envelope.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDY5",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [195, 40],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDY5",
  "status": "error",
  "description": "Colored Tokens envelope is not strict UTF-8",
  "code": 6
}
```

No replacement-character decoding is allowed. Plan hex must likewise be lowercase, even-length and complete, with 200 hex characters in every nonfinal chunk.

### A.70. Reject count overflow before allocation

The declared plan part count exceeds uint32. The wallet must not allocate based on this untrusted count.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDcw",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 4294967296 aa"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDcw",
  "status": "error",
  "description": "Colored Tokens plan count is outside uint32 bounds",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.71. Metadata integers are not booleans

The plan version is JSON true rather than integer 1.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDcx",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a747275652c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e",
      "p color plan 1 5 65644f757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233",
      "p color plan 2 5 616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834",
      "p color plan 3 5 6437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854",
      "p color plan 4 5 79706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDcx",
  "status": "error",
  "description": "Invalid Colored Tokens metadata version",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.72. Complete locally without sending

The wallet validates and signs the transfer but the application asks for noSend.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDcy",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false,
      "noSend": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDcy",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6",
    "noSendChange": [
      "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6.2"
    ]
  }
}
```

The returned transaction ID and ordinary change outpoint identify a locally completed action, not an admitted token receipt. No token topic is submitted yet. The signed capability and managed inputs remain recorded, and the prospective 700-unit token change is not made spendable solely because Bitcoin serialization and signatures are complete.

### A.73. Send a previously prepared action

The transfer from A.72 is retained by the wallet and has not conflicted. This request uses the ordinary batch-only createAction flow.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDcz",
  "call": "createAction",
  "args": {
    "description": "Send prepared Colored Tokens transfer",
    "options": {
      "sendWith": [
        "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
      ],
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDcz",
  "status": "success",
  "result": {
    "sendWithResults": [
      {
        "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6",
        "status": "sending"
      }
    ]
  }
}
```

The wallet resolves the saved plan and authorization, rechecks current token state, and processes the action on the relevant token topic along with Bitcoin submission. The result uses the existing sendWithResults shape; sending does not itself claim mining or topic admission. The wallet records subsequent admission before crediting change. It neither requires invented new inputs/outputs nor charges the same authorized transfer again.

### A.74. Abort after releasing a signature

Start with the pending offer from A.47, including Alice’s already released signature. The wallet cancels its own pending processing.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc0",
  "call": "abortAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE="
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc0",
  "status": "success",
  "result": {
    "aborted": true
  }
}
```

Cancellation does not retract Alice’s signature. The wallet retains the exposure record and does not immediately offer A to another conflicting action. It can free that exposure after an authenticated completion or conflict makes the capability unusable, or after explicit consent to a conflicting spend that invalidates the old offer. BRC-177 expiry likewise cannot revoke bytes another party holds.

### A.75. Concurrent action cannot reuse reserved input

The pending offer from A.47 already reserves Alice’s only A input. A second application asks for the normal 300-unit payment, without authorizing a conflict with that offer.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc1",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc1",
  "status": "error",
  "description": "No unreserved Colored Tokens balance is available",
  "code": 7
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.76. Retry a completed pending reference

The wallet has already successfully completed the exact reference and spends in A.48. Its response was lost; it recognizes this retry and returns its recorded result.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc2",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1zd2FwLTAwMDE=",
    "spends": {
      "1": {
        "unlockingScript": "483045022100f3e0efe667230ecd9f750e5c1c3348ee89dadd25cff11ad70ed90a0e131d7ab402200119a0d91d3d766a29fbc23f1ac07a20d6fb01f05306ec1870f59f79256d403c4121035426266ad18afddbd6997b724b619249f334d259430e9733b8fa92f8a6153ecb"
      }
    },
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc2",
  "status": "success",
  "result": {
    "txid": "a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196"
  }
}
```

This is the same authorization record, not a second swap. The wallet does not deduct another allowance, create another credit, or attempt to select another A input. The CWI message ID is only transport correlation; the saved action reference and wallet action state identify this retry.

### A.77. Internalize an admitted swap receipt

The complete swap has already been submitted by its creator and admitted on both topics. Alice has verified sources and the stored remittance context but has not yet credited output 1.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc3",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The decoded customInstructions string is:

```json
{
  "version": 1,
  "assetId": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0",
  "derivationPrefix": "Bw==",
  "derivationSuffix": "Ag==",
  "senderIdentityKey": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc3",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

The wallet validates all complete scripts and both assets’ conservation. The consumed A and B inputs require historical admission, even though they are now spent. The received outpoint a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196.1 must currently be admitted and unspent on B’s topic. Alice’s supplied remittance derives its P2PKH hash; only then are 2000 B units accepted into the B basket. This does not treat Bob’s original source as currently unspent or silently submit a missing receipt.

### A.78. Historical admission differs from current spendability

The completed swap receipt is ready for internalization. Its A input was validly admitted before being consumed.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc4",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

For the consumed A input the wallet issues this historical query:

```json
{
  "service": "ls_color_e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
  "query": {
    "type": "color-admission",
    "version": 1,
    "assetId": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
    "topic": "tm_color_e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0",
    "outpoints": [
      "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0"
    ]
  }
}
```

Its authenticated response includes the source output:

```json
{
  "type": "output-list",
  "outputs": [
    {
      "outputIndex": 0,
      "beef": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229]
    }
  ]
}
```

An ordinary current-spendability query for that same consumed input now returns:

```json
{
  "type": "output-list",
  "outputs": []
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc4",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

That empty current-input answer does not invalidate this receipt. The wallet performs the analogous historical B-input check, then a separate positive current check for the received B output. Historical membership is not reused as evidence that the receipt output remains unspent.

### A.79. Internalization never silently submits

The complete swap is consensus-valid but its B receipt output is absent from the B topic. Its current-spendability query returns an empty output-list.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDc5",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDc5",
  "status": "error",
  "description": "Received token output is not currently admitted and unspent",
  "code": 1
}
```

The wallet rejects receipt acceptance and performs no overlay submission to repair this condition. The creator or higher-level flow must arrange admission before trying again. Existing source proofs and a successful A-topic result cannot substitute for B admission.

### A.80. Wrong receipt basket

The B output and its B remittance are correct, but the caller requests insertion into A’s basket.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDgw",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDgw",
  "status": "error",
  "description": "Receipt basket does not match token asset and role",
  "code": 6
}
```

The wallet rejects rather than silently reclassifying the output. Authority-versus-value basket mismatches, global p color baskets and stored issue baskets fail by the same identity rule.

### A.81. Wrong receipt derivation

The purported ownership metadata has a different suffix from the actual B output key.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDgx",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"AA==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDgx",
  "status": "error",
  "description": "Receipt P2PKH does not match the derived wallet key",
  "code": 6
}
```

The wallet does not count the output as its own merely because it is listed in ownedOutputs or a requested basket. Wrong counterparty, sender key, ordinary/privileged root or derivation prefix has the same ownership consequence.

### A.82. Internalize a completed deploy authority

The authority issuance fixture has a final transaction ID and its output 0 is already admitted and unspent. The wallet has not yet credited it.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDgy",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 78, 56, 86, 146, 182, 3, 52, 49, 240, 48, 183, 245, 23, 112, 159, 251, 131, 149, 250, 31, 91, 111, 179, 35, 200, 138, 85, 46, 78, 38, 18, 149, 2, 0, 190, 239, 0, 2, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 1, 0, 0, 0, 1, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 66, 14, 93, 191, 127, 66, 74, 241, 125, 170, 129, 166, 243, 197, 68, 192, 103, 112, 159, 203, 101, 127, 183, 143, 58, 195, 215, 0, 110, 44, 41, 176, 2, 32, 114, 106, 30, 139, 50, 230, 133, 40, 237, 121, 125, 133, 142, 224, 204, 239, 0, 4, 218, 54, 13, 182, 19, 101, 217, 117, 103, 68, 96, 184, 253, 80, 65, 33, 2, 230, 23, 112, 84, 194, 66, 4, 226, 22, 143, 1, 219, 133, 227, 212, 185, 166, 70, 196, 238, 145, 95, 81, 92, 148, 136, 75, 54, 19, 51, 69, 19, 255, 255, 255, 255, 2, 1, 0, 0, 0, 0, 0, 0, 0, 28, 0, 0, 109, 118, 169, 20, 181, 99, 117, 205, 237, 91, 33, 92, 140, 128, 14, 84, 10, 179, 40, 244, 157, 231, 27, 214, 136, 172, 5, 39, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color 9512264e2e558ac823b36f5b1ffa9583fb9f7017f5b730f0313403b69256384ev0 authority",
          "customInstructions": "{\"version\":1,\"assetId\":\"ISSUE\",\"derivationPrefix\":\"Cg==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDgy",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

The receipt uses ISSUE only to reconstruct the original key. The basket uses the final transaction ID and authority suffix. The wallet independently authenticates the funding input as bare satoshis and derives no token topic for it. This internalization neither recreates the issuance nor assigns a new derivation tuple.

### A.83. Idempotent receipt retry

The wallet has already accepted the same B output under the same validated receipt metadata in A.77. The caller repeats that receipt.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDgz",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDgz",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

Acceptance is idempotent: the output exists once, the B balance increases only once, and permission accounting is not charged again. Conflicting metadata in a later call must still be validated and rejected; idempotence does not make alternate ownership claims valid.

### A.84. Receipt instructions reject embedded proofs

The ordinary swap receipt is otherwise valid. The added tokenProofs member is empty and the instructions remain below 1000 UTF-8 bytes, isolating the module schema error.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg0",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"tokenProofs\":[]}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg0",
  "status": "error",
  "code": 6,
  "description": "Unknown receipt member tokenProofs"
}
```

No receipt is accepted. Proof packets remain permitted in action plans and signature envelopes, but never in receipt custom instructions.

### A.85. Wallet-held lineage with current receipt admission

Historical lookup for B’s consumed source is unavailable. The wallet already holds the following packet in its evidence store; this is fixture state, not an additional wire field. The A.0 chain anchors apply. The received B output has a positive current membership answer, and A’s historical admission is independently available.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg1",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

Wallet-held evidence, validated for exactly the stated subject:

```json
{
  "assetId": "b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0",
  "subjects": [
    "a85bb1cd5778cdd0c8606f6c640e96c2ad5779579ee6f6dad0edbf1c00360196.1"
  ],
  "beef": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 12, 3, 160, 134, 1, 7, 99, 111, 108, 111, 114, 45, 65, 255, 255, 255, 255, 1, 232, 3, 0, 0, 0, 0, 0, 0, 30, 0, 2, 232, 3, 109, 118, 169, 20, 108, 189, 32, 193, 137, 17, 82, 157, 212, 167, 7, 203, 231, 171, 205, 41, 106, 9, 127, 228, 136, 172, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 12, 3, 161, 134, 1, 7, 99, 111, 108, 111, 114, 45, 66, 255, 255, 255, 255, 1, 232, 3, 0, 0, 0, 0, 0, 0, 30, 0, 2, 208, 7, 109, 118, 169, 20, 176, 109, 50, 129, 118, 226, 254, 137, 16, 28, 119, 1, 232, 41, 12, 62, 199, 228, 26, 242, 136, 172, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0]
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg1",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

The wallet validates this BRC-176 packet’s scope, token-parent bodies, conservation and chain evidence independently. It does not process the packet as the receipt’s ordinary tx field. Complete lineage supplies the missing historical fact; current receipt membership remains separately mandatory. An unanchored or scope-mismatched cached packet cannot justify acceptance.

### A.86. Foreign destination is not a wallet credit

Output 0 belongs to Bob, but the application adds an Alice-owned remittance claim to reduce the displayed A debit.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg2",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens foreign reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 116, 244, 113, 121, 41, 186, 221, 99, 164, 186, 213, 203, 120, 134, 20, 154, 145, 205, 234, 135, 247, 200, 9, 210, 209, 144, 73, 20, 226, 226, 211, 94],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402207e2da7e5cb5ce2c14985c2e069002c5c0434b3b1e7983d85262b8cb7ba70ee7102202b2e72386c3e15fcb05d27d8b3bb1a6a4a9b198c7f586a6d14d853619220be54412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "52"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2074f4717929badd63a4bad5cb7886149a91cdea87f7c809d2d1904914e2e2d35e02d0076d76a91486f9dbe298d4239ea2fc02b77a95db6e74e13eca88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color 5ed3e2e2144990d1d209c8f787eacd919a148678cbd5baa463ddba297971f474v0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 999,
        "outputDescription": "Agreed output 2"
      }
    ],
    "labels": [
      "p color plan 0 10 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2235656433653265",
      "p color plan 1 10 3231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 10 744964223a22356564336532653231343439393064316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30222c2264657269766174696f6e507265666978223a2242773d3d222c",
      "p color plan 3 10 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 10 32383135623136663831373938227d2c7b226f7574707574496e646578223a302c2261737365744964223a226535343435623136313336303434383464373535616437633136396633653337663633613638366338643162313065336261653037373665",
      "p color plan 5 10 38646233616130345f30222c2264657269766174696f6e507265666978223a2242773d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a22303237396265363637656639646362",
      "p color plan 6 10 626163353561303632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 7 10 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a22356564336532653231343439393064",
      "p color plan 8 10 316432303963386637383765616364393139613134383637386362643562616134363364646261323937393731663437345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c2263",
      "p color plan 9 10 68616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a337d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg2",
  "status": "error",
  "description": "Owned output does not match derived wallet P2PKH",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.87. Duplicate explicit input does not duplicate value

The application lists the same authenticated A outpoint twice as explicit inputs. That cannot create another 1000 token units.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg3",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg3",
  "status": "error",
  "description": "Duplicate explicit input outpoint",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.88. Declared asset set must equal the transaction

The plan declares B even though neither its inputs nor outputs contain B.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg4",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 7 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2262373633363564",
      "p color plan 1 7 6536663761653639303538303130646166373862613831306532373237313761653039666432376237306261373735313533646635376561315f30225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a312c2261737365",
      "p color plan 2 7 744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c2264657269766174696f6e507265666978223a2243413d3d222c",
      "p color plan 3 7 2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264636532386439353966",
      "p color plan 4 7 32383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538",
      "p color plan 5 7 646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a32",
      "p color plan 6 7 7d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg4",
  "status": "error",
  "description": "Colored Tokens plan asset set does not match transaction",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.89. Signature override must name a consumed managed input

The A transfer asks the wallet to apply an override to Bob’s B outpoint, which is not consumed.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDg5",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100e6d380c58b3a29eb96d82f45739e11470a10db0b69b379e3b371ef1263fe33a7022073881a510bed9eaf5f2da60c19039352e13798064edd98cea814f9c1dbb81ace412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 7 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 7 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 7 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 7 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 7 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 7 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b7b226f7574706f696e74223a2262373633363564653666376165363930353830313064616637386261383130653237323731",
      "p color plan 6 7 3761653039666432376237306261373735313533646635376561312e30222c227369676861736854797065223a36357d5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDg5",
  "status": "error",
  "description": "Signature override does not identify a consumed managed input",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.90. Action-label queries retain their matching meaning

No saved action has the selected plan label in this isolated read fixture. The wallet authorizes this origin to query its actions.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDkw",
  "call": "listActions",
  "args": {
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22346631306265393630356630646661636362373339646664346162383936376332636262366236383139346665393936616134313064316533646366373135385f30225d2c22696e70757454"
    ],
    "labelQueryMode": "all",
    "limit": 10,
    "offset": 0
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDkw",
  "status": "success",
  "result": {
    "totalActions": 0,
    "actions": []
  }
}
```

The label is an ordinary exact query term at this boundary, not an instruction to reconstruct and execute a new plan. A wallet applies module-aware read permissions and returns the usual action list shape.

### A.91. Send to an arbitrary foreign locking script

Alice pays 300 A units to the specified OP\_2 OP\_EQUAL destination and receives 700 A units of P2PKH change. The destination is identified by its script, not an asserted exclusive owner.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDkx",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens foreignDest reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402202f032ddf84eb11bd3f4376b20214084bcadf29f665a770a7661d83d9bb527b14022032295e4c5fe24f7e9d602728c4230cf874a1019fd5a095e053e461ba1c847cbd412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d5287",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDkx",
  "status": "success",
  "result": {
    "txid": "806e1966b6712586f3d4e99455d816df518b075e88d97fb1a343cfc8dbc5284e"
  }
}
```

The foreign destination remains valid token value, but it is not wallet custody and is not credited to Alice. The wallet does not replace the script with P2PKH or constrain how a counterparty later spends it. The complete source script, token conservation, satoshis, change and consent are still checked.

### A.92. Executed OP\_CODESEPARATOR determines script code

The K source has a recognized module-key signing role in a foreign script. After its token prefix it executes OP\_CODESEPARATOR unconditionally and then P2PKH. Alice consents to this fully specified transfer.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDky",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 45413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 50, 52, 50, 44, 56, 53, 44, 49, 51, 49, 44, 49, 52, 50, 44, 49, 48, 56, 44, 50, 52, 56, 44, 49, 51, 51, 44, 50, 52, 54, 44, 50, 48, 53, 44, 56, 55, 44, 50, 55, 44, 49, 55, 54, 44, 49, 48, 57, 44, 49, 55, 51, 44, 50, 52, 55, 44, 49, 53, 54, 44, 49, 57, 51, 44, 49, 54, 44, 50, 49, 53, 44, 50, 51, 51, 44, 50, 49, 48, 44, 53, 49, 44, 49, 54, 49, 44, 51, 51, 44, 49, 55, 56, 44, 49, 55, 57, 44, 49, 52, 57, 44, 55, 52, 44, 50, 52, 56, 44, 50, 57, 44, 52, 49, 44, 56, 48, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 50, 48, 57, 44, 50, 50, 49, 44, 55, 50, 44, 55, 55, 44, 51, 44, 50, 53, 50, 44, 49, 57, 51, 44, 50, 52, 50, 44, 55, 44, 50, 51, 56, 44, 53, 44, 50, 51, 56, 44, 52, 53, 44, 53, 51, 44, 52, 50, 44, 55, 55, 44, 49, 50, 56, 44, 49, 52, 52, 44, 49, 56, 57, 44, 49, 53, 48, 44, 51, 56, 44, 49, 56, 54, 44, 53, 51, 44, 49, 51, 52, 44, 49, 56, 44, 49, 57, 57, 44, 49, 49, 50, 44, 52, 56, 44, 49, 50, 50, 44, 49, 53, 53, 44, 49, 51, 50, 44, 49, 52, 49, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 50, 48, 57, 44, 50, 50, 49, 44, 55, 50, 44, 55, 55, 44, 51, 44, 50, 53, 50, 44, 49, 57, 51, 44, 50, 52, 50, 44, 55, 44, 50, 51, 56, 44, 53, 44, 50, 51, 56, 44, 52, 53, 44, 53, 51, 44, 52, 50, 44, 55, 55, 44, 49, 50, 56, 44, 49, 52, 52, 44, 49, 56, 57, 44, 49, 53, 48, 44, 51, 56, 44, 49, 56, 54, 44, 53, 51, 44, 49, 51, 52, 44, 49, 56, 44, 49, 57, 57, 44, 49, 49, 50, 44, 52, 56, 44, 49, 50, 50, 44, 49, 53, 53, 44, 49, 51, 50, 44, 49, 52, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 50, 48, 57, 44, 50, 50, 49, 44, 55, 50, 44, 55, 55, 44, 51, 44, 50, 53, 50, 44, 49, 57, 51, 44, 50, 52, 50, 44, 55, 44, 50, 51, 56, 44, 53, 44, 50, 51, 56, 44, 52, 53, 44, 53, 51, 44, 52, 50, 44, 55, 55, 44, 49, 50, 56, 44, 49, 52, 52, 44, 49, 56, 57, 44, 49, 53, 48, 44, 51, 56, 44, 49, 56, 54, 44, 53, 51, 44, 49, 51, 52, 44, 49, 56, 44, 49, 57, 57, 44, 49, 49, 50, 44, 52, 56, 44, 49, 50, 50, 44, 49, 53, 53, 44, 49, 51, 50, 44, 49, 52, 49, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 54, 44, 49, 51, 57, 44, 55, 48, 44, 51, 57, 44, 50, 52, 54, 44, 50, 48, 52, 44, 50, 51, 44, 49, 53, 55, 44, 49, 49, 51, 44, 56, 50, 44, 50, 51, 48, 44, 50, 51, 54, 44, 49, 56, 51, 44, 50, 53, 44, 50, 50, 55, 44, 50, 51, 48, 44, 49, 53, 53, 44, 50, 53, 51, 44, 49, 51, 54, 44, 49, 51, 55, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 50, 48, 57, 44, 50, 50, 49, 44, 55, 50, 44, 55, 55, 44, 51, 44, 50, 53, 50, 44, 49, 57, 51, 44, 50, 52, 50, 44, 55, 44, 50, 51, 56, 44, 53, 44, 50, 51, 56, 44, 52, 53, 44, 53, 51, 44, 52, 50, 44, 55, 55, 44, 49, 50, 56, 44, 49, 52, 52, 44, 49, 56, 57, 44, 49, 53, 48, 44, 51, 56, 44, 49, 56, 54, 44, 53, 51, 44, 49, 51, 52, 44, 49, 56, 44, 49, 57, 57, 44, 49, 49, 50, 44, 52, 56, 44, 49, 50, 50, 44, 49, 53, 53, 44, 49, 51, 50, 44, 49, 52, 49, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 56, 50, 44, 52, 55, 44, 50, 53, 50, 44, 50, 49, 57, 44, 50, 54, 44, 56, 51, 44, 49, 57, 56, 44, 49, 55, 51, 44, 49, 49, 56, 44, 50, 53, 52, 44, 49, 55, 44, 49, 49, 55, 44, 51, 55, 44, 49, 48, 56, 44, 56, 52, 44, 50, 53, 49, 44, 49, 49, 56, 44, 49, 54, 44, 49, 48, 51, 44, 50, 48, 49, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 56, 100, 56, 52, 57, 98, 55, 97, 51, 48, 55, 48, 99, 55, 49, 50, 56, 54, 51, 53, 98, 97, 50, 54, 57, 54, 98, 100, 57, 48, 56, 48, 52, 100, 50, 97, 51, 53, 50, 100, 101, 101, 48, 53, 101, 101, 48, 55, 102, 50, 99, 49, 102, 99, 48, 51, 52, 100, 52, 56, 100, 100, 100, 49, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 69, 103, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDky",
  "status": "success",
  "result": {
    "signature": [48, 68, 2, 32, 21, 254, 106, 34, 170, 22, 93, 73, 42, 136, 159, 231, 139, 33, 192, 9, 24, 65, 76, 39, 157, 230, 87, 159, 211, 183, 191, 253, 164, 85, 15, 245, 2, 32, 101, 143, 44, 148, 192, 83, 172, 63, 108, 31, 91, 169, 172, 175, 56, 65, 46, 203, 33, 91, 181, 111, 48, 82, 241, 125, 131, 131, 175, 161, 103, 85]
  }
}
```

The wallet determines the actual executed path and uses only the script after the executed separator in this input’s consensus preimage. It verifies that the derived ISSUE key controls that signing role. The fixture signature executes successfully in the complete source script. K is not placed in an ordinary managed P2PKH basket merely because the module can sign this recognized foreign role.

### A.93. Missing branch material prevents a preimage decision

The Q script conditionally executes one of two OP\_CODESEPARATOR positions before the module-key P2PKH check. Its input has no unlocking script or branch-selection material.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDkz",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 45513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 49, 57, 57, 44, 49, 48, 52, 44, 49, 54, 54, 44, 49, 54, 44, 50, 53, 50, 44, 50, 48, 55, 44, 57, 56, 44, 50, 48, 54, 44, 49, 52, 51, 44, 50, 50, 56, 44, 52, 50, 44, 50, 55, 44, 49, 48, 55, 44, 50, 48, 52, 44, 49, 55, 48, 44, 52, 56, 44, 49, 48, 51, 44, 49, 53, 44, 51, 55, 44, 49, 51, 53, 44, 50, 54, 44, 50, 48, 56, 44, 49, 55, 54, 44, 49, 56, 49, 44, 49, 53, 54, 44, 49, 55, 53, 44, 50, 53, 51, 44, 50, 52, 48, 44, 50, 49, 49, 44, 49, 54, 57, 44, 49, 49, 53, 44, 49, 50, 51, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 53, 55, 44, 49, 50, 51, 44, 49, 49, 54, 44, 56, 52, 44, 52, 51, 44, 49, 51, 54, 44, 50, 50, 50, 44, 49, 55, 52, 44, 49, 54, 54, 44, 49, 50, 50, 44, 57, 56, 44, 49, 57, 49, 44, 57, 52, 44, 50, 49, 49, 44, 57, 54, 44, 54, 51, 44, 49, 56, 44, 50, 48, 50, 44, 49, 56, 50, 44, 49, 54, 54, 44, 49, 55, 55, 44, 54, 55, 44, 57, 53, 44, 56, 57, 44, 49, 55, 49, 44, 53, 50, 44, 56, 44, 49, 55, 55, 44, 49, 55, 50, 44, 49, 53, 44, 55, 55, 44, 50, 49, 56, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 53, 55, 44, 49, 50, 51, 44, 49, 49, 54, 44, 56, 52, 44, 52, 51, 44, 49, 51, 54, 44, 50, 50, 50, 44, 49, 55, 52, 44, 49, 54, 54, 44, 49, 50, 50, 44, 57, 56, 44, 49, 57, 49, 44, 57, 52, 44, 50, 49, 49, 44, 57, 54, 44, 54, 51, 44, 49, 56, 44, 50, 48, 50, 44, 49, 56, 50, 44, 49, 54, 54, 44, 49, 55, 55, 44, 54, 55, 44, 57, 53, 44, 56, 57, 44, 49, 55, 49, 44, 53, 50, 44, 56, 44, 49, 55, 55, 44, 49, 55, 50, 44, 49, 53, 44, 55, 55, 44, 50, 49, 56, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 53, 55, 44, 49, 50, 51, 44, 49, 49, 54, 44, 56, 52, 44, 52, 51, 44, 49, 51, 54, 44, 50, 50, 50, 44, 49, 55, 52, 44, 49, 54, 54, 44, 49, 50, 50, 44, 57, 56, 44, 49, 57, 49, 44, 57, 52, 44, 50, 49, 49, 44, 57, 54, 44, 54, 51, 44, 49, 56, 44, 50, 48, 50, 44, 49, 56, 50, 44, 49, 54, 54, 44, 49, 55, 55, 44, 54, 55, 44, 57, 53, 44, 56, 57, 44, 49, 55, 49, 44, 53, 50, 44, 56, 44, 49, 55, 55, 44, 49, 55, 50, 44, 49, 53, 44, 55, 55, 44, 50, 49, 56, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 55, 54, 44, 51, 44, 49, 57, 53, 44, 49, 49, 44, 54, 48, 44, 49, 48, 49, 44, 52, 51, 44, 53, 51, 44, 50, 53, 48, 44, 55, 50, 44, 49, 48, 51, 44, 49, 52, 44, 52, 57, 44, 49, 48, 48, 44, 49, 50, 50, 44, 53, 54, 44, 49, 48, 56, 44, 49, 57, 56, 44, 49, 52, 44, 49, 57, 51, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 53, 55, 44, 49, 50, 51, 44, 49, 49, 54, 44, 56, 52, 44, 52, 51, 44, 49, 51, 54, 44, 50, 50, 50, 44, 49, 55, 52, 44, 49, 54, 54, 44, 49, 50, 50, 44, 57, 56, 44, 49, 57, 49, 44, 57, 52, 44, 50, 49, 49, 44, 57, 54, 44, 54, 51, 44, 49, 56, 44, 50, 48, 50, 44, 49, 56, 50, 44, 49, 54, 54, 44, 49, 55, 55, 44, 54, 55, 44, 57, 53, 44, 56, 57, 44, 49, 55, 49, 44, 53, 50, 44, 56, 44, 49, 55, 55, 44, 49, 55, 50, 44, 49, 53, 44, 55, 55, 44, 50, 49, 56, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 48, 51, 44, 49, 55, 56, 44, 57, 51, 44, 49, 56, 49, 44, 50, 50, 54, 44, 50, 57, 44, 51, 51, 44, 49, 55, 53, 44, 50, 50, 44, 49, 48, 54, 44, 50, 49, 52, 44, 49, 56, 48, 44, 50, 53, 53, 44, 49, 49, 48, 44, 57, 55, 44, 50, 49, 57, 44, 51, 51, 44, 49, 48, 44, 49, 51, 55, 44, 52, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 100, 97, 52, 100, 48, 102, 97, 99, 98, 49, 48, 56, 51, 52, 97, 98, 53, 57, 53, 102, 52, 51, 98, 49, 97, 54, 98, 54, 99, 97, 49, 50, 51, 102, 54, 48, 100, 51, 53, 101, 98, 102, 54, 50, 55, 97, 97, 54, 97, 101, 100, 101, 56, 56, 50, 98, 53, 52, 55, 52, 55, 98, 51, 57, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 69, 119, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDkz",
  "status": "error",
  "description": "Missing execution context for module-key signature",
  "code": 6
}
```

The wallet cannot determine the correct script code and returns no signature. It does not accept an asserted separator offset or assume a branch. The caller must supply enough execution context before retrying. This restriction concerns a signature the module itself must construct; other foreign inputs may remain explicitly pending when their final scripts are not yet available.

### A.94. Direct signature for a committed partial swap

Alice authorizes a pending offer exchanging her 1000 A units for Bob’s 2000 B units. The two named current inputs and all promised outputs are committed by ALL|FORKID; Bob has not supplied his unlocking script. The wallet has completed its configured A/B acceptance preflight and verified the fixture's system-specific settlement safeguard.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk0",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 49, 56, 51, 44, 50, 50, 54, 44, 50, 49, 55, 44, 57, 55, 44, 54, 56, 44, 49, 55, 51, 44, 50, 53, 52, 44, 49, 57, 50, 44, 49, 51, 50, 44, 49, 51, 50, 44, 49, 55, 48, 44, 49, 51, 55, 44, 55, 48, 44, 50, 48, 57, 44, 49, 50, 53, 44, 49, 51, 50, 44, 50, 52, 52, 44, 49, 56, 52, 44, 49, 49, 51, 44, 56, 49, 44, 55, 49, 44, 50, 50, 51, 44, 49, 51, 57, 44, 51, 56, 44, 49, 48, 48, 44, 50, 53, 48, 44, 57, 48, 44, 49, 48, 49, 44, 50, 49, 50, 44, 51, 54, 44, 55, 44, 49, 49, 56, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 51, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 52, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 50, 51, 50, 44, 51, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 50, 44, 50, 48, 56, 44, 55, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 54, 56, 44, 49, 53, 51, 44, 49, 49, 44, 50, 51, 56, 44, 54, 56, 44, 52, 55, 44, 49, 55, 55, 44, 56, 57, 44, 49, 54, 57, 44, 50, 48, 50, 44, 49, 55, 48, 44, 56, 57, 44, 49, 50, 51, 44, 49, 53, 50, 44, 49, 53, 51, 44, 52, 53, 44, 52, 53, 44, 49, 52, 49, 44, 49, 48, 53, 44, 49, 51, 52, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 51, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 52, 56, 44, 50, 50, 54, 44, 49, 51, 52, 44, 50, 49, 44, 55, 48, 44, 50, 50, 48, 44, 50, 52, 54, 44, 49, 48, 49, 44, 56, 55, 44, 57, 53, 44, 54, 48, 44, 52, 48, 44, 56, 44, 56, 55, 44, 50, 51, 48, 44, 50, 49, 48, 44, 49, 57, 54, 44, 50, 49, 55, 44, 54, 56, 44, 50, 49, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 98, 55, 54, 51, 54, 53, 100, 101, 54, 102, 55, 97, 101, 54, 57, 48, 53, 56, 48, 49, 48, 100, 97, 102, 55, 56, 98, 97, 56, 49, 48, 101, 50, 55, 50, 55, 49, 55, 97, 101, 48, 57, 102, 100, 50, 55, 98, 55, 48, 98, 97, 55, 55, 53, 49, 53, 51, 100, 102, 53, 55, 101, 97, 49, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 66, 119, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk0",
  "status": "success",
  "result": {
    "signature": [48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247]
  }
}
```

The wallet records the precise authorized exposure and reserves A before releasing the signature. It does not credit B while the transaction remains partial. Every consensus-valid completion using this signature retains the named input outpoints and outputs; a conflicting spend of the named B UTXO prevents this offer from completing. This is transaction-level commitment, not a guarantee that either overlay will later admit the output.

### A.95. Decline an unguaranteed swap

Alice permits only an offer whose named incoming B source and B receipt remain binding. The request instead uses ALL|FORKID|ANYONECANPAY, and no independent covenant or other commitment binds that incoming source.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk1",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 49, 56, 51, 44, 50, 50, 54, 44, 50, 49, 55, 44, 57, 55, 44, 54, 56, 44, 49, 55, 51, 44, 50, 53, 52, 44, 49, 57, 50, 44, 49, 51, 50, 44, 49, 51, 50, 44, 49, 55, 48, 44, 49, 51, 55, 44, 55, 48, 44, 50, 48, 57, 44, 49, 50, 53, 44, 49, 51, 50, 44, 50, 52, 52, 44, 49, 56, 52, 44, 49, 49, 51, 44, 56, 49, 44, 55, 49, 44, 50, 50, 51, 44, 49, 51, 57, 44, 51, 56, 44, 49, 48, 48, 44, 50, 53, 48, 44, 57, 48, 44, 49, 48, 49, 44, 50, 49, 50, 44, 51, 54, 44, 55, 44, 49, 49, 56, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 51, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 52, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 50, 51, 50, 44, 51, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 50, 44, 50, 48, 56, 44, 55, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 54, 56, 44, 49, 53, 51, 44, 49, 49, 44, 50, 51, 56, 44, 54, 56, 44, 52, 55, 44, 49, 55, 55, 44, 56, 57, 44, 49, 54, 57, 44, 50, 48, 50, 44, 49, 55, 48, 44, 56, 57, 44, 49, 50, 51, 44, 49, 53, 50, 44, 49, 53, 51, 44, 52, 53, 44, 52, 53, 44, 49, 52, 49, 44, 49, 48, 53, 44, 49, 51, 52, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 51, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 52, 56, 44, 50, 50, 54, 44, 49, 51, 52, 44, 50, 49, 44, 55, 48, 44, 50, 50, 48, 44, 50, 52, 54, 44, 49, 48, 49, 44, 56, 55, 44, 57, 53, 44, 54, 48, 44, 52, 48, 44, 56, 44, 56, 55, 44, 50, 51, 48, 44, 50, 49, 48, 44, 49, 57, 54, 44, 50, 49, 55, 44, 54, 56, 44, 50, 49, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 49, 57, 51, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 98, 55, 54, 51, 54, 53, 100, 101, 54, 102, 55, 97, 101, 54, 57, 48, 53, 56, 48, 49, 48, 100, 97, 102, 55, 56, 98, 97, 56, 49, 48, 101, 50, 55, 50, 55, 49, 55, 97, 101, 48, 57, 102, 100, 50, 55, 98, 55, 48, 98, 97, 55, 55, 53, 49, 53, 51, 100, 102, 53, 55, 101, 97, 49, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 66, 119, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk1",
  "status": "error",
  "description": "Requested signature does not satisfy the authorized swap commitments",
  "code": 1
}
```

The wallet releases no capability and rolls back provisional reservations. ALL output coverage alone is not named-input coverage.

### A.96. Conditional consideration with a verified remedy

The proposed layout is the same swap, but Alice's wallet has independently verified an applicable asset-ecosystem remedy under its owner-configured policy. That remedy covers the loss of her A contribution if the unbound B source does not supply admitted consideration. The fixture deliberately does not instantiate or standardize the remedy. Alice consents to the precisely described conditional capability, including its limits; disclosure alone would not be enough to authorize it.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk2",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 49, 56, 51, 44, 50, 50, 54, 44, 50, 49, 55, 44, 57, 55, 44, 54, 56, 44, 49, 55, 51, 44, 50, 53, 52, 44, 49, 57, 50, 44, 49, 51, 50, 44, 49, 51, 50, 44, 49, 55, 48, 44, 49, 51, 55, 44, 55, 48, 44, 50, 48, 57, 44, 49, 50, 53, 44, 49, 51, 50, 44, 50, 52, 52, 44, 49, 56, 52, 44, 49, 49, 51, 44, 56, 49, 44, 55, 49, 44, 50, 50, 51, 44, 49, 51, 57, 44, 51, 56, 44, 49, 48, 48, 44, 50, 53, 48, 44, 57, 48, 44, 49, 48, 49, 44, 50, 49, 50, 44, 51, 54, 44, 55, 44, 49, 49, 56, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 51, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 52, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 50, 51, 50, 44, 51, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 49, 54, 49, 44, 49, 50, 54, 44, 50, 52, 53, 44, 54, 49, 44, 50, 49, 44, 49, 49, 55, 44, 49, 54, 55, 44, 49, 49, 44, 49, 56, 51, 44, 51, 57, 44, 50, 53, 51, 44, 57, 44, 49, 55, 52, 44, 50, 51, 44, 51, 57, 44, 51, 57, 44, 49, 52, 44, 49, 50, 57, 44, 49, 56, 54, 44, 49, 50, 48, 44, 49, 55, 53, 44, 49, 51, 44, 49, 44, 56, 56, 44, 49, 52, 52, 44, 50, 51, 48, 44, 49, 50, 50, 44, 49, 49, 49, 44, 50, 50, 50, 44, 49, 48, 49, 44, 57, 57, 44, 49, 56, 51, 44, 50, 44, 50, 48, 56, 44, 55, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 54, 56, 44, 49, 53, 51, 44, 49, 49, 44, 50, 51, 56, 44, 54, 56, 44, 52, 55, 44, 49, 55, 55, 44, 56, 57, 44, 49, 54, 57, 44, 50, 48, 50, 44, 49, 55, 48, 44, 56, 57, 44, 49, 50, 51, 44, 49, 53, 50, 44, 49, 53, 51, 44, 52, 53, 44, 52, 53, 44, 49, 52, 49, 44, 49, 48, 53, 44, 49, 51, 52, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 51, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 52, 56, 44, 50, 50, 54, 44, 49, 51, 52, 44, 50, 49, 44, 55, 48, 44, 50, 50, 48, 44, 50, 52, 54, 44, 49, 48, 49, 44, 56, 55, 44, 57, 53, 44, 54, 48, 44, 52, 48, 44, 56, 44, 56, 55, 44, 50, 51, 48, 44, 50, 49, 48, 44, 49, 57, 54, 44, 50, 49, 55, 44, 54, 56, 44, 50, 49, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 49, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 49, 57, 51, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 98, 55, 54, 51, 54, 53, 100, 101, 54, 102, 55, 97, 101, 54, 57, 48, 53, 56, 48, 49, 48, 100, 97, 102, 55, 56, 98, 97, 56, 49, 48, 101, 50, 55, 50, 55, 49, 55, 97, 101, 48, 57, 102, 100, 50, 55, 98, 55, 48, 98, 97, 55, 55, 53, 49, 53, 51, 100, 102, 53, 55, 101, 97, 49, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 66, 119, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk2",
  "status": "success",
  "result": {
    "signature": [48, 69, 2, 33, 0, 131, 121, 138, 62, 155, 196, 191, 144, 243, 206, 87, 38, 19, 242, 125, 205, 75, 243, 72, 33, 98, 215, 169, 232, 195, 210, 209, 111, 27, 192, 238, 108, 2, 32, 98, 48, 190, 139, 78, 169, 56, 180, 137, 107, 107, 29, 165, 114, 191, 72, 234, 98, 239, 71, 105, 170, 31, 197, 21, 230, 173, 3, 227, 102, 73, 153]
  }
}
```

The wallet records the precise authorized exposure and reserves A before releasing the signature. It does not credit B while the transaction remains partial. The wallet explains that the signature does not bind the incoming B source, that overlay admission remains conditional, and which independently verified remedy protects Alice if consideration fails. It neither claims hashPrevouts coverage nor relies on future signAction revalidation to constrain an externally completed signature. Without that remedy the required result is A.95's refusal.

### A.97. Legacy mint is authorized by an authority

The caller supplies a BRC-161 mint destination for 2500 H units to Bob and a binary replacement authority to Alice. The H authority input is admitted and controlled by Alice.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk3",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens legacyMint reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100f6f76ba4b84abc08ee08e1c8c650cd754aaf50d9b0351415518179432ea3404a02201b285ef5e38a0b497b6134933cfae3c73a7b859cb72aae44836df5c41708c8884121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "0063036f726451126170706c69636174696f6e2f6273762d3230004c717b2270223a226273762d3230222c226f70223a226d696e74222c226964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c22616d74223a2232353030227d6876a914f39d18223ace5d48278e74ec10eca2f01c165e8d88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a914046f95ea33fe01c0957b8631fbb005493883e86b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      }
    ],
    "labels": [
      "p color plan 0 7 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 7 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 7 635f30222c2264657269766174696f6e507265666978223a2246513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 7 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226563653034383139323537323435616633323930333730323235",
      "p color plan 4 7 64623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a226d696e74227d2c7b2261737365744964223a226563653034383139323537323435616633323930333730323235",
      "p color plan 5 7 64623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a22617574686f726974792d7472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368",
      "p color plan 6 7 616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk3",
  "status": "success",
  "result": {
    "txid": "f4575586e4dc060caffa389c20400c419a10610412ebcaab1d731c8b73f345c9"
  }
}
```

The wallet recognizes the legacy mint operation as new supply authorized by H’s authority, validates the ordinary scripts and current overlay, and separately consents to supply creation and authority replacement. New wallet-generated custody outputs use BRC-162; recognizing a caller-supplied foreign legacy destination does not convert it to a different asset.

### A.98. Authority does not fund legacy transfers

The H authority input contributes zero fungible units. The caller’s legacy transfer output requests 2500 units with no valid H value inputs. All supplied signatures are otherwise valid.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk4",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens legacyBad reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 108, 125, 140, 21, 9, 60, 205, 168, 179, 15, 140, 109, 9, 65, 149, 59, 45, 142, 219, 37, 2, 55, 144, 50, 175, 69, 114, 37, 25, 72, 224, 236],
    "inputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100b2dc3759131aed8a5c23a6a0aac3b92720f36972e608548691628823e374801d02204699104e3d7885c8fe41a2ee33b7670ef93c313c947ad7ac5716ab2bc57765a04121035c5c837a779d6acfe11abe4470873cdb504c03d846c1ad2f258b0af22fce2ef8"
      }
    ],
    "outputs": [
      {
        "lockingScript": "0063036f726451126170706c69636174696f6e2f6273762d3230004c757b2270223a226273762d3230222c226f70223a227472616e73666572222c226964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c22616d74223a2232353030227d6876a914f39d18223ace5d48278e74ec10eca2f01c165e8d88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "206c7d8c15093ccda8b30f8c6d0941953b2d8edb2502379032af4572251948e0ec006d76a914046f95ea33fe01c0957b8631fbb005493883e86b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
      }
    ],
    "labels": [
      "p color plan 0 7 7b2276657273696f6e223a312c22617373657473223a5b22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30225d2c226f776e65644f",
      "p color plan 1 7 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22656365303438313932353732343561663332393033373032323564623865326433623935343130393664386330666233613863643363303931353863376436",
      "p color plan 2 7 635f30222c2264657269766174696f6e507265666978223a2246513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 7 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226563653034383139323537323435616633323930333730323235",
      "p color plan 4 7 64623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a227472616e73666572227d2c7b2261737365744964223a2265636530343831393235373234356166333239303337",
      "p color plan 5 7 3032323564623865326433623935343130393664386330666233613863643363303931353863376436635f30222c226f7065726174696f6e223a22617574686f726974792d7472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d",
      "p color plan 6 7 2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk4",
  "status": "error",
  "description": "Legacy transfer and burn outputs lack value-input coverage",
  "code": 6
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.99. Legacy burn output cannot enter value custody

The completed legacy burn fixture consumes L’s 1000 units and creates an explicit burn inscription for that amount, with a P2PKH remainder derived for Alice. Its scripts are valid and historical source admission is available.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMDk5",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 182, 85, 74, 219, 184, 11, 10, 142, 220, 227, 177, 252, 162, 124, 68, 141, 142, 235, 210, 234, 174, 136, 157, 219, 126, 60, 131, 219, 188, 127, 224, 15, 2, 0, 190, 239, 0, 3, 2, 81, 163, 105, 87, 99, 203, 143, 187, 119, 20, 243, 224, 157, 22, 131, 97, 82, 145, 212, 105, 40, 31, 180, 156, 189, 32, 243, 94, 64, 184, 201, 50, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 1, 0, 0, 0, 2, 81, 163, 105, 87, 99, 203, 143, 187, 119, 20, 243, 224, 157, 22, 131, 97, 82, 145, 212, 105, 40, 31, 180, 156, 189, 32, 243, 94, 64, 184, 201, 50, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 88, 154, 232, 180, 21, 97, 225, 72, 84, 36, 54, 117, 182, 125, 85, 73, 225, 239, 167, 106, 145, 194, 212, 178, 177, 50, 89, 208, 214, 62, 182, 89, 2, 32, 68, 3, 130, 71, 193, 123, 229, 255, 142, 152, 69, 185, 110, 39, 195, 175, 54, 1, 94, 242, 84, 47, 250, 29, 201, 66, 45, 171, 203, 57, 127, 208, 65, 33, 2, 22, 167, 128, 7, 182, 240, 53, 225, 137, 216, 57, 95, 139, 65, 183, 82, 68, 181, 235, 7, 135, 66, 69, 209, 24, 28, 2, 103, 192, 111, 161, 217, 255, 255, 255, 255, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 129, 50, 89, 45, 142, 6, 134, 253, 195, 3, 166, 130, 250, 169, 191, 156, 143, 242, 251, 18, 190, 168, 26, 84, 98, 199, 7, 12, 193, 5, 200, 241, 2, 32, 0, 209, 54, 76, 174, 214, 163, 109, 243, 46, 162, 97, 4, 129, 81, 114, 27, 189, 11, 244, 9, 121, 161, 66, 130, 226, 107, 213, 73, 17, 60, 132, 65, 33, 2, 230, 23, 112, 84, 194, 66, 4, 226, 22, 143, 1, 219, 133, 227, 212, 185, 166, 70, 196, 238, 145, 95, 81, 92, 148, 136, 75, 54, 19, 51, 69, 19, 255, 255, 255, 255, 2, 1, 0, 0, 0, 0, 0, 0, 0, 168, 0, 99, 3, 111, 114, 100, 81, 18, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 98, 115, 118, 45, 50, 48, 0, 76, 113, 123, 34, 112, 34, 58, 34, 98, 115, 118, 45, 50, 48, 34, 44, 34, 111, 112, 34, 58, 34, 98, 117, 114, 110, 34, 44, 34, 105, 100, 34, 58, 34, 51, 50, 99, 57, 98, 56, 52, 48, 53, 101, 102, 51, 50, 48, 98, 100, 57, 99, 98, 52, 49, 102, 50, 56, 54, 57, 100, 52, 57, 49, 53, 50, 54, 49, 56, 51, 49, 54, 57, 100, 101, 48, 102, 51, 49, 52, 55, 55, 98, 98, 56, 102, 99, 98, 54, 51, 53, 55, 54, 57, 97, 51, 53, 49, 95, 48, 34, 44, 34, 97, 109, 116, 34, 58, 34, 49, 48, 48, 48, 34, 125, 104, 118, 169, 20, 141, 22, 36, 1, 44, 109, 5, 57, 134, 137, 143, 0, 143, 48, 191, 43, 248, 255, 85, 39, 136, 172, 6, 39, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color 32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"32c9b8405ef320bd9cb41f2869d491526183169de0f31477bb8fcb635769a351_0\",\"derivationPrefix\":\"Fg==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMDk5",
  "status": "error",
  "description": "Legacy burn output has no spendable token value",
  "code": 6
}
```

Even a matching wallet key and a known overlay history cannot turn the burn record into value. It is excluded from incoming-value netting, value selection and managed value baskets. Both the explicit burn and any implicit remainder burn require the creator’s consent; neither is an incoming payment.

### A.100. Lookup results must match requested outpoints

The wallet requests A’s current state, but the authenticated provider returns B’s output instead:

```json
{
  "type": "output-list",
  "outputs": [
    {
      "outputIndex": 0,
      "beef": [2, 0, 190, 239, 0, 1, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183]
    }
  ]
}
```

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTAw",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTAw",
  "status": "error",
  "description": "Overlay output does not match requested outpoint",
  "code": 1
}
```

No signature is released and provisional reservations are rolled back. No token credit or settled debit is recorded.

### A.101. Documented lower host resource limit

This host documents a 512-byte decoded signature-envelope limit. The complete request below exceeds that limit. The source and permissions would otherwise be acceptable.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTAx",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 41513d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 54, 44, 49, 57, 44, 50, 49, 57, 44, 49, 57, 56, 44, 50, 51, 49, 44, 49, 48, 51, 44, 49, 54, 49, 44, 50, 51, 49, 44, 50, 52, 57, 44, 50, 50, 44, 52, 49, 44, 55, 48, 44, 49, 49, 48, 44, 49, 48, 44, 49, 52, 53, 44, 49, 53, 55, 44, 49, 55, 49, 44, 49, 56, 55, 44, 49, 48, 51, 44, 49, 52, 54, 44, 49, 55, 50, 44, 55, 51, 44, 50, 57, 44, 57, 44, 50, 48, 51, 44, 57, 50, 44, 49, 52, 48, 44, 50, 51, 49, 44, 49, 53, 57, 44, 56, 44, 50, 48, 44, 57, 50, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 51, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 52, 52, 44, 49, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 50, 44, 50, 57, 44, 49, 50, 50, 44, 53, 57, 44, 50, 48, 52, 44, 56, 49, 44, 51, 50, 44, 49, 52, 48, 44, 50, 48, 44, 57, 50, 44, 51, 57, 44, 50, 48, 44, 57, 53, 44, 50, 48, 53, 44, 49, 49, 52, 44, 52, 51, 44, 50, 48, 55, 44, 49, 50, 48, 44, 50, 48, 49, 44, 49, 51, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 54, 50, 44, 51, 50, 44, 52, 44, 49, 55, 48, 44, 49, 55, 57, 44, 49, 52, 49, 44, 49, 49, 48, 44, 49, 49, 57, 44, 50, 50, 52, 44, 49, 56, 54, 44, 50, 50, 55, 44, 49, 54, 44, 50, 55, 44, 49, 52, 49, 44, 49, 48, 56, 44, 49, 48, 52, 44, 53, 56, 44, 50, 52, 54, 44, 53, 53, 44, 54, 50, 44, 49, 53, 57, 44, 50, 50, 44, 49, 50, 52, 44, 49, 55, 51, 44, 56, 53, 44, 50, 49, 53, 44, 49, 51, 50, 44, 54, 56, 44, 57, 54, 44, 49, 57, 44, 50, 50, 44, 57, 49, 44, 54, 56, 44, 50, 50, 57, 44, 50, 44, 49, 56, 56, 44, 50, 44, 49, 48, 57, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 57, 55, 44, 50, 52, 56, 44, 49, 55, 50, 44, 49, 49, 44, 52, 57, 44, 49, 53, 48, 44, 50, 51, 51, 44, 49, 53, 53, 44, 50, 53, 53, 44, 49, 55, 44, 49, 48, 54, 44, 49, 53, 44, 50, 48, 49, 44, 49, 56, 48, 44, 50, 54, 44, 49, 50, 57, 44, 49, 53, 49, 44, 55, 50, 44, 50, 54, 44, 57, 44, 49, 51, 54, 44, 49, 55, 50, 44, 50, 50, 48, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 49, 53, 48, 44, 50, 52, 55, 44, 49, 57, 56, 44, 49, 55, 53, 44, 50, 52, 53, 44, 49, 51, 53, 44, 55, 55, 44, 52, 52, 44, 56, 55, 44, 50, 52, 56, 44, 50, 48, 50, 44, 49, 49, 55, 44, 55, 48, 44, 49, 48, 57, 44, 53, 52, 44, 51, 50, 44, 50, 53, 53, 44, 50, 53, 49, 44, 49, 53, 53, 44, 49, 57, 54, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 49, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 101, 53, 52, 52, 53, 98, 49, 54, 49, 51, 54, 48, 52, 52, 56, 52, 100, 55, 53, 53, 97, 100, 55, 99, 49, 54, 57, 102, 51, 101, 51, 55, 102, 54, 51, 97, 54, 56, 54, 99, 56, 100, 49, 98, 49, 48, 101, 51, 98, 97, 101, 48, 55, 55, 54, 101, 56, 100, 98, 51, 97, 97, 48, 52, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 67, 65, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 125],
    "seekPermission": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTAx",
  "status": "error",
  "description": "Colored Tokens envelope exceeds host limit of 512 bytes",
  "code": 6
}
```

The wallet fails explicitly before large allocation or signing; it does not truncate the transaction, omit evidence, or partially decode a plan. The module’s 16 MiB ceiling still applies when the host supports larger requests. Lower plan and proof resource limits must likewise be explicit failures, never silent omission.

### A.102. Bitcoin processing succeeds but an overlay fails

Alice authorizes the ordinary transfer. Bitcoin processing succeeds with the shown transfer transaction ID, but the A-topic submission fails.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTAy",
  "call": "createAction",
  "args": {
    "description": "Pay 300 token units to Bob",
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Bob receives 300 units"
      }
    ],
    "labels": [
      "p color plan 0 5 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 5 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 5 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a2265353434356231363133363034343834643735",
      "p color plan 3 5 3561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30222c22696e646578223a317d2c7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369676861736854797065",
      "p color plan 4 5 73223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTAy",
  "status": "error",
  "description": "Transaction completed but Colored Tokens overlay admission is pending",
  "code": 1
}
```

The wallet retains the real completed transaction b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6 and reports its unresolved token processing state. It does not pretend Bitcoin completion was rolled back or mark the 700-unit change spendable. A later admission retry reuses this action and does not create a second debit or transaction. Multi-asset processing must similarly retain each topic’s individual result; one successful topic does not authorize credits on the others.

### A.103. Managed-only two-phase action

Alice authorizes the final transfer but requests signAndProcess:false. No foreign script is involved; the fixture wallet chooses to prepare its own signature now and defer processing.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTAz",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens transfer reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScriptLength": 107
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e5022c016d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 1",
        "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
      }
    ],
    "labels": [
      "p color plan 0 6 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 6 757470757473223a5b7b226f7574707574496e646578223a312c2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 6 345f30222c2264657269766174696f6e507265666978223a2243413d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 6 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226535343435623136313336303434383464373535616437633136",
      "p color plan 4 6 39663365333766363361363836633864316231306533626165303737366538646233616130345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 6 6f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTAz",
  "status": "success",
  "result": {
    "signableTransaction": {
      "tx": [1, 1, 1, 1, 198, 31, 249, 131, 77, 82, 252, 17, 14, 118, 209, 226, 45, 0, 191, 153, 22, 85, 108, 118, 10, 132, 96, 232, 104, 66, 48, 0, 134, 21, 125, 180, 2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 1, 0, 0, 0, 1, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 230, 211, 128, 197, 139, 58, 41, 235, 150, 216, 47, 69, 115, 158, 17, 71, 10, 16, 219, 11, 105, 179, 121, 227, 179, 113, 239, 18, 99, 254, 51, 167, 2, 32, 115, 136, 26, 81, 11, 237, 158, 175, 95, 45, 166, 12, 25, 3, 147, 82, 225, 55, 152, 6, 78, 221, 152, 206, 168, 20, 249, 193, 219, 184, 26, 206, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 44, 1, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 188, 2, 109, 118, 169, 20, 197, 248, 172, 11, 49, 150, 233, 155, 255, 17, 106, 15, 201, 180, 26, 129, 151, 72, 26, 9, 136, 172, 220, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
      "reference": "bWFuZGFsYS1wYXktMDAwMDE="
    }
  }
}
```

The pending reference remains valid for a later signAction with an empty spends object, because no caller script remains to be supplied. The wallet still refreshes overlay state and consent before processing. Returning fully populated scripts inside a pending action does not assert admission or release its reservations.

### A.104. Process a managed-only pending action

Start with the prepared action in A.103. Its sources remain current and the recorded permission remains valid. No new unlocking script is needed.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA0",
  "call": "signAction",
  "args": {
    "reference": "bWFuZGFsYS1wYXktMDAwMDE=",
    "spends": {},
    "options": {
      "returnTXIDOnly": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA0",
  "status": "success",
  "result": {
    "txid": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6"
  }
}
```

The wallet completes the saved action after revalidation and submits it to the A topic. It shares the reservation and authorization already associated with the prepared transaction and does not bill the user’s policy twice.

### A.105. Discover multiple assets without knowing their IDs

This independent inventory contains C reserved by a pending offer, A available, and H available authority. Canonical outpoint ordering puts C before A before H. The origin is authorized to read all three.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA1",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true,
    "limit": 2,
    "offset": 0
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA1",
  "status": "success",
  "result": {
    "totalOutputs": 3,
    "outputs": [
      {
        "outpoint": "91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47.0",
        "satoshis": 1000,
        "lockingScript": "0002b80b6d76a91482a6d141057b6bd1d85839bd83b58f6e657c0a9088ac",
        "spendable": false,
        "tags": [
          "color_state_reserved"
        ]
      },
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "satoshis": 1000,
        "lockingScript": "0002e8036d76a9146cbd20c18911529dd4a707cbe7abcd296a097fe488ac",
        "spendable": true,
        "tags": [
          "color_state_available"
        ]
      }
    ]
  }
}
```

The client derives C and A IDs from the deploy outpoints and scripts. The C record remains visible but is not available for spending. totalOutputs is 3 actual records, not the two records on this page or a sum of their units.

### A.106. Discover an authority-only asset on the next page

The A.105 snapshot has not changed. The client advances by the two records already read.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA2",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true,
    "limit": 2,
    "offset": 2
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA2",
  "status": "success",
  "result": {
    "totalOutputs": 3,
    "outputs": [
      {
        "outpoint": "ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6c.0",
        "satoshis": 1000,
        "lockingScript": "00006d76a91475a06c850b20c3c7e7e9d607f2cc64ba885daaed88ac",
        "spendable": true,
        "tags": [
          "color_state_available"
        ]
      }
    ]
  }
}
```

The H amount is zero because this is authority. The client lists H with one capability even though it has no fungible value holdings. Every available asset can be discovered without a guessed ID or a global storage basket.

### A.107. BRC-112 token-unit balance

Alice has the available A fixture output, carrying 1000 token units and 1000 satoshis. These happen to coincide in this fixture; the sum is determined by token fields.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA3",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0",
    "limit": 1,
    "offset": 999
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA3",
  "status": "success",
  "result": {
    "totalOutputs": "1000",
    "outputs": []
  }
}
```

The module ignores limit and offset and returns the whole available token balance. It omits BEEF and never returns output entries for a balance query. Read permission does not grant permission to sign.

### A.108. Authority balance is a capability count

The available H authority fixture is in its authority basket.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA4",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0 authority"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA4",
  "status": "success",
  "result": {
    "totalOutputs": "1",
    "outputs": []
  }
}
```

The result means one authority, not one token unit or a zero-valued fungible balance. An accessible H value basket with no available value would return zero separately.

### A.109. Reserved value is excluded from available balance

C’s existing value basket contains only the C output reserved in A.105.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTA5",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTA5",
  "status": "success",
  "result": {
    "totalOutputs": "0",
    "outputs": []
  }
}
```

The inventory still exposes the reserved record to an authorized reader, but an available-balance query excludes it. Failed pre-signature reservations are rolled back; released-signature exposure is retained under the action lifecycle rules.

### A.110. Exact uint64 maximum over JSON

The fixed-supply issuance from A.38 is complete, admitted, unreserved and owned by Alice. Both ends implement the exact module-balance profile.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTEw",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 520f26d99fd56c7720ee96497b17a04d2b7c845eb6bce3b4682eec06bf36a5f1v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTEw",
  "status": "success",
  "result": {
    "totalOutputs": "18446744073709551615",
    "outputs": []
  }
}
```

The API and JSON both preserve this decimal string. The binary field is the byte length 20 followed by the UTF-8 bytes of these 20 decimal digits, not a uint64 VarInt.

### A.111. String balance at the JavaScript numeric boundary

In this independent H state, valid authority issuance has produced an available value total of exactly 9007199254740991 units.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTEx",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTEx",
  "status": "success",
  "result": {
    "totalOutputs": "9007199254740991",
    "outputs": []
  }
}
```

This boundary has no special wire treatment: small values, this value and the next integer all use canonical decimal strings. Numeric JSON balances are rejected for this P-basket query.

### A.112. Aggregate greater than uint64 succeeds

In this independent H state, two admitted, available value outputs each carry 18446744073709551615 units. Their exact sum is 36893488147419103230.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTEy",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTEy",
  "status": "success",
  "result": {
    "totalOutputs": "36893488147419103230",
    "outputs": []
  }
}
```

Both outputs satisfy the per-output uint64 limit. The wallet sums them exactly and returns the full decimal string. It does not round, wrap, truncate to one output or require an enumeration fallback.

### A.113. Transport cannot preserve the exact balance

The H balance is 9007199254740993 units, but this wallet adapter cannot emit the exact-integer profile.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTEz",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTEz",
  "status": "error",
  "code": 2,
  "description": "Exact module balance representation is unsupported"
}
```

The adapter returns an ordinary error rather than casting to Number. An older receiver encountering an unsupported exact result must likewise raise a local protocol error; this example does not claim that all deployed older implementations already enforce that rule.

### A.114. Reject reversed balance prefix

The caller places balance outside the reserved module namespace.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTE0",
  "call": "listOutputs",
  "args": {
    "basket": "balance p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTE0",
  "status": "error",
  "code": 6,
  "description": "Balance prefix must follow the module identifier"
}
```

The wallet does not bypass module dispatch or normal read permission checks. The supported order is p color balance followed by the asset tag.

### A.115. Discovery permission is required

This origin lacks inventory read permission and forbids an interactive request.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTE1",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true,
    "limit": 2,
    "offset": 0,
    "seekPermission": false
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTE1",
  "status": "error",
  "code": 1,
  "description": "Colored Tokens inventory read permission is required"
}
```

No output, asset ID or count is disclosed. A generic cryptographic or spending grant does not automatically authorize this inventory scope.

### A.116. Receipt UTF-8 bound is enforced before module use

The instructions below are valid JSON text but exceed the core 1000 UTF-8 byte bound. The repeated non-ASCII characters also demonstrate why character count is insufficient.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTE2",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"ééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTE2",
  "status": "error",
  "code": 6,
  "description": "customInstructions exceeds 1000 UTF-8 bytes"
}
```

Core validation rejects this receipt before key derivation or proof handling. Valid bounded instructions still undergo the independent protocol-length, ownership and closed-object checks.

### A.117. Pending admission is visible without spendable credit

The transfer from A.14 has completed on Bitcoin, consuming A’s source, but its generated 700-unit change is awaiting overlay admission. This independent inventory has only that locally controlled pending record.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTE3",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true,
    "limit": 10,
    "offset": 0
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTE3",
  "status": "success",
  "result": {
    "totalOutputs": 1,
    "outputs": [
      {
        "outpoint": "b47d158600304268e860840a766c551699bf002de2d1760e11fc524d83f91fc6.1",
        "satoshis": 1,
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502bc026d76a914c5f8ac0b3196e99bff116a0fc9b41a8197481a0988ac",
        "spendable": false,
        "tags": [
          "color_state_pending"
        ]
      }
    ]
  }
}
```

The client may display 700 prospective units with pending status. It must not display them as available or resurrect the spent source. The available-balance query is zero until the required admission checks succeed.

### A.118. Cached proof with a wrong scope cannot justify receipt

The request is the valid compact swap receipt from A.77. Historical B admission evidence is unavailable, and the wallet's only cached packet covers the unrelated C asset, `91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47_0`, at subject `91fbfbe785fbd6ba33974791c1e59e343a0e783cba0f195bc2b037f1afed7d47.0`. It contains no B token ancestry. The ordinary transaction sources and current received-output membership are authenticated, but no alternative B historical admission or complete lineage evidence is available.

The application sends:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "bWFuZGFsYTAwMTE4",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 150, 1, 54, 0, 28, 191, 237, 208, 218, 246, 230, 158, 87, 121, 87, 173, 194, 150, 14, 100, 108, 111, 96, 200, 208, 205, 120, 87, 205, 177, 91, 168, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 78, 39, 87, 21, 30, 84, 162, 141, 128, 230, 114, 184, 60, 244, 48, 211, 236, 214, 178, 198, 222, 124, 228, 112, 35, 118, 12, 182, 149, 201, 34, 135, 2, 32, 89, 73, 49, 163, 0, 76, 46, 31, 8, 179, 131, 75, 177, 221, 243, 123, 38, 159, 20, 129, 209, 195, 87, 106, 181, 72, 54, 226, 29, 134, 91, 247, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 255, 255, 255, 255, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 243, 224, 239, 230, 103, 35, 14, 205, 159, 117, 14, 92, 28, 51, 72, 238, 137, 218, 221, 37, 207, 241, 26, 215, 14, 217, 10, 14, 19, 29, 122, 180, 2, 32, 1, 25, 160, 217, 29, 61, 118, 106, 41, 251, 194, 63, 26, 192, 122, 32, 214, 251, 1, 240, 83, 6, 236, 24, 112, 245, 159, 121, 37, 109, 64, 60, 65, 33, 3, 84, 38, 38, 106, 209, 138, 253, 219, 214, 153, 123, 114, 75, 97, 146, 73, 243, 52, 210, 89, 67, 14, 151, 51, 184, 250, 146, 248, 166, 21, 62, 203, 255, 255, 255, 255, 4, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 161, 126, 245, 61, 21, 117, 167, 11, 183, 39, 253, 9, 174, 23, 39, 39, 14, 129, 186, 120, 175, 13, 1, 88, 144, 230, 122, 111, 222, 101, 99, 183, 2, 208, 7, 109, 118, 169, 20, 68, 153, 11, 238, 68, 47, 177, 89, 169, 202, 170, 89, 123, 152, 153, 45, 45, 141, 105, 134, 136, 172, 231, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 221, 3, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 1,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"b76365de6f7ae69058010daf78ba810e272717ae09fd27b70ba775153df57ea1_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ],
    "description": "Receive authenticated Colored Tokens output"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "bWFuZGFsYTAwMTE4",
  "status": "error",
  "code": 1,
  "description": "Insufficient valid token-lineage evidence for receipt"
}
```

The wallet rejects after validating the cached packet’s scope. Removing proof bytes from the request does not weaken BRC-176 completeness, asset binding or subject binding. A positive current receipt lookup alone does not replace missing historical or lineage evidence.

### A.119. Prepare a height-constrained auction offer

At fixture tip 100200, Alice’s wallet has established the required authenticated processor arrangement for these ordered inputs and the auction lifetime. Alice offers 1000 A units for Bob’s 5000-satoshi contribution. Both authenticated sources are unspent. Alice consents to those exact terms becoming executable after height 100250; she has not authorized a highest-bid-wins promise. Bob has not signed. The fixed output order and accounting are those of A.43.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTE5",
  "call": "createAction",
  "args": {
    "description": "Colored Token auction: 5000 satoshi bid, earliest inclusion after height 100250",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 7,
        "unlockingScriptLength": 107
      },
      {
        "outpoint": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 9,
        "unlockingScriptLength": 107
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 4999,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 100250,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": false,
      "signAndProcess": false,
      "acceptDelayedBroadcast": false,
      "noSend": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTE5",
  "status": "success",
  "result": {
    "signableTransaction": {
      "tx": [1, 1, 1, 1, 253, 43, 129, 235, 21, 176, 106, 76, 206, 221, 15, 143, 191, 166, 166, 95, 180, 242, 18, 137, 12, 29, 111, 21, 253, 177, 136, 59, 116, 21, 52, 56, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 159, 105, 230, 49, 169, 218, 22, 135, 113, 98, 196, 53, 77, 87, 196, 123, 162, 253, 29, 149, 143, 188, 1, 84, 160, 249, 93, 131, 214, 237, 218, 186, 2, 32, 107, 144, 99, 50, 224, 129, 85, 208, 217, 199, 185, 161, 210, 112, 6, 171, 121, 26, 175, 141, 146, 231, 174, 220, 246, 30, 223, 13, 230, 130, 62, 228, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 7, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 0, 9, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 135, 19, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 102, 23, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0],
      "reference": "Y29sb3ItYXVjdGlvbi0wMDAx"
    }
  }
}
```

The wallet validates the A overlay and both source scripts, reserves A, and signs Alice’s input with ALL|FORKID. Bob’s unlocking script stays empty. Sequence numbers 7 and 9 activate the height constraint; the current transaction ID is provisional. No ordinary submission, spendable credit or settled debit occurs. Alice’s satoshi receipt would be 5990, comprising 1000 carrier satoshis plus Bob’s 5000 contribution less the 10-satoshi fee. The pending offer may be delivered to Bob. Its preparation does not assert processor acceptance; that requires the later processing step. The wallet retains responsibility for the coordination arrangement.

### A.120. Revise a sequence and regenerate affected signatures

Start independently from A.119. Bob supplies a signature for sequence 10 on his input. The wallet’s saved-action contract permits the requested sequence revision after revalidation, and Alice consents with knowledge that the earlier revision remains exposed. The tip is still 100200.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTIw",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": true,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTIw",
  "status": "success",
  "result": {
    "txid": "a0e189f78d599eca8039f64ce6d223d876fa009beb6a2c1c43cc25d110cb1c35",
    "tx": [1, 1, 1, 1, 53, 28, 203, 16, 209, 37, 204, 67, 28, 44, 106, 235, 155, 0, 250, 118, 216, 35, 210, 230, 76, 246, 57, 128, 202, 158, 89, 141, 247, 137, 225, 160, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 99, 76, 112, 100, 26, 160, 52, 158, 2, 200, 61, 109, 25, 104, 221, 186, 244, 162, 195, 240, 46, 102, 236, 162, 68, 123, 250, 89, 197, 198, 235, 213, 2, 32, 10, 120, 1, 11, 133, 194, 239, 69, 194, 136, 209, 232, 154, 192, 25, 160, 93, 142, 90, 144, 42, 191, 9, 116, 40, 27, 7, 28, 11, 121, 21, 44, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 7, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 154, 6, 148, 48, 63, 244, 24, 134, 128, 4, 232, 156, 65, 52, 233, 135, 49, 245, 182, 66, 68, 21, 110, 69, 156, 186, 193, 162, 233, 154, 163, 120, 2, 32, 61, 90, 245, 193, 175, 206, 20, 152, 147, 93, 211, 151, 13, 6, 255, 159, 36, 55, 12, 126, 157, 48, 207, 38, 173, 91, 84, 70, 218, 106, 192, 166, 65, 33, 3, 160, 157, 3, 72, 11, 29, 38, 247, 73, 9, 10, 207, 245, 96, 66, 246, 62, 16, 232, 44, 20, 186, 199, 1, 88, 187, 130, 33, 158, 97, 202, 129, 10, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 135, 19, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 102, 23, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0]
  }
}
```

The wallet reconstructs sequences \[7, 10], rechecks source/overlay state and the unchanged price, validates Bob’s new script, and regenerates Alice’s signature because her ALL|FORKID digest commits to the revised sequence set. All scripts now pass, but the transaction is still consensus-non-final. noSend returns the exact bytes without processing or credit. Both released revisions share one exposure record. This noSend result is prepared, not processor-accepted; upon a processing request the wallet must enforce acceptance and supersession under its arrangement. Sequence 10 alone has not revoked sequence 9.

### A.121. Reject a stale signature on a sequence update

Start from A.119 at tip 100200. Bob’s script below was signed for sequence 9, but this request changes it to 10.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTIx",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "47304402202fe02afd05fb618ddae3cfef25e3b2019efd0175838f148c12598e983c42022a02201faa124cca6e925c26563041f6bbc4fb90612c69949bb32213840e41289a8542412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": true,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTIx",
  "status": "error",
  "code": 6,
  "description": "Provided unlocking script is invalid for revised sequence"
}
```

The wallet verifies the supplied script against the revised preimage and rejects it. It does not overwrite Bob’s signature, restore the old sequence silently, or return a complete transaction. The previously released Alice signature remains recorded; no new signature is released.

### A.122. Do not process a signed non-final bid as settled

Start from A.119 at tip 100200. Bob supplies the correct original script, but asks for ordinary processing now.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTIy",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "47304402202fe02afd05fb618ddae3cfef25e3b2019efd0175838f148c12598e983c42022a02201faa124cca6e925c26563041f6bbc4fb90612c69949bb32213840e41289a8542412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81"
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTIy",
  "status": "error",
  "code": 1,
  "description": "Auction transaction is not yet consensus-final"
}
```

The complete scripts would be valid, but the height constraint is not yet satisfied. The wallet retains the pending action and exposure and reports the failed processing request. Delayed relay or an overlay acknowledgement cannot make this a completed settlement. It does not set sequences to maximum or alter lockTime.

### A.123. A higher bid is a new authorized transaction context

The A.119 revision has already been delivered, and its inputs are still unspent at tip 100200. Bob raises his contribution from 5000 to 6000 satoshis. Alice authorizes a linked revision under the wallet’s processor arrangement. The wallet, rather than the application, is responsible for getting the valid higher-sequence revision accepted when processing is requested. Both parties supply signatures for the new exact layout.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTIz",
  "call": "createAction",
  "args": {
    "description": "Colored Token auction: 6000 satoshi bid, earliest inclusion after height 100250",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 8,
        "unlockingScript": "483045022100fc195e0691cfa2275c13f12af6e11418372d6b1da436cb09232a94aab5dec98f02204451d5236f9ff5499d24ec9d2e3fdf935d55e132ec2f6edd969f7e4403964abc412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 10,
        "unlockingScript": "483045022100fc915a18d8c45b31908e6d231ffaf662a82a4a951cc1e8d89c88cce5340d940a0220200ec334eaf051d6aa394b8b434414a71032d3275c1b2c4ede2f621192a00a8f412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 3999,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 100250,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": false,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false,
      "noSend": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTIz",
  "status": "success",
  "result": {
    "txid": "400f4a99000d080530654e0c8faa9b9878534b57c94bfebafee6e37c445101aa",
    "tx": [1, 1, 1, 1, 170, 1, 81, 68, 124, 227, 230, 254, 186, 254, 75, 201, 87, 75, 83, 120, 152, 155, 170, 143, 12, 78, 101, 48, 5, 8, 13, 0, 153, 74, 15, 64, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 252, 25, 94, 6, 145, 207, 162, 39, 92, 19, 241, 42, 246, 225, 20, 24, 55, 45, 107, 29, 164, 54, 203, 9, 35, 42, 148, 170, 181, 222, 201, 143, 2, 32, 68, 81, 213, 35, 111, 159, 245, 73, 157, 36, 236, 157, 46, 63, 223, 147, 93, 85, 225, 50, 236, 47, 110, 221, 150, 159, 126, 68, 3, 150, 74, 188, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 8, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 252, 145, 90, 24, 216, 196, 91, 49, 144, 142, 109, 35, 31, 250, 246, 98, 168, 42, 74, 149, 28, 193, 232, 216, 156, 136, 204, 229, 52, 13, 148, 10, 2, 32, 32, 14, 195, 52, 234, 240, 81, 214, 170, 57, 75, 139, 67, 68, 20, 167, 16, 50, 211, 39, 92, 27, 44, 78, 222, 47, 98, 17, 146, 160, 10, 143, 65, 33, 3, 160, 157, 3, 72, 11, 29, 38, 247, 73, 9, 10, 207, 245, 96, 66, 246, 62, 16, 232, 44, 20, 186, 199, 1, 88, 187, 130, 33, 158, 97, 202, 129, 10, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 159, 15, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 78, 27, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0]
  }
}
```

The wallet links the new context to the existing reservation instead of rejecting it as an unrelated spend or freeing the old exposure. Alice would receive 6990 satoshis and Bob 3999 ordinary change plus the token carrier. The fee remains 10. Both scripts, the new preimages and the A overlay are checked. The bid stays pending with noSend. The wallet’s processor arrangement must enforce acceptance of this valid higher-sequence replacement when processing is requested; a warning about old bids is not a substitute. Until that step succeeds, noSend does not claim supersession. A highest-bid-wins assertion also needs validated auction bid-ordering rules.

### A.124. All-final sequences bypass a future lock time

The application presents the same future lockTime, but every sequence below is 4294967295. Alice’s only permission requires no settlement before the stated auction height; she declines an immediate-settlement revision.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI0",
  "call": "createAction",
  "args": {
    "description": "Colored Token auction: 5000 satoshi bid, earliest inclusion after height 100250",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100c68c6871602a191d110a84dc3b2f293bdf7f9883926f45393ef4aad12abef48f02204c314f965de9cd8b44244ce062965554932c9692ef86c16586db50fc1dcc15f5412103a3c96df02329cbbf056ee25ee0c24ba36c7620cce9b4eba41972327a12a1f22d"
      },
      {
        "outpoint": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 4294967295,
        "unlockingScript": "47304402203ffddea64d228f284f78e00b2f613133d96a853a22f1e70aa573f64829bfd62502206e88468f7edbf6741bbe9b5433ed41bf72178432dd85cd6853a5ac722b07d205412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81"
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 4999,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 100250,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": false,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false,
      "noSend": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI0",
  "status": "error",
  "code": 1,
  "description": "Consent does not cover immediately final auction terms"
}
```

The wallet computes effective finality from all inputs. It informs Alice that lockTime is bypassed rather than claiming the transaction waits until height 100250. The scripts are otherwise valid, but the authorization fails. Nothing new is signed or credited. An explicitly authorized immediate transaction would not be rejected merely for using final sequences.

### A.125. Settle after the lock height and admit the token leg

Start independently from A.119. The evaluated block context is now height 100251, both sources remain unspent, the A overlay answers positively, and Alice’s authorization remains applicable. At height exactly 100250 the strict comparison would still fail. Bob supplies the original signature.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI1",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "47304402202fe02afd05fb618ddae3cfef25e3b2019efd0175838f148c12598e983c42022a02201faa124cca6e925c26563041f6bbc4fb90612c69949bb32213840e41289a8542412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81"
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI1",
  "status": "success",
  "result": {
    "txid": "386568731d8b742983b6d98a9bb20bb504a402db11aee0de81e09e1e2d335d47",
    "tx": [1, 1, 1, 1, 71, 93, 51, 45, 30, 158, 224, 129, 222, 224, 174, 17, 219, 2, 164, 4, 181, 11, 178, 155, 138, 217, 182, 131, 41, 116, 139, 29, 115, 104, 101, 56, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 159, 105, 230, 49, 169, 218, 22, 135, 113, 98, 196, 53, 77, 87, 196, 123, 162, 253, 29, 149, 143, 188, 1, 84, 160, 249, 93, 131, 214, 237, 218, 186, 2, 32, 107, 144, 99, 50, 224, 129, 85, 208, 217, 199, 185, 161, 210, 112, 6, 171, 121, 26, 175, 141, 146, 231, 174, 220, 246, 30, 223, 13, 230, 130, 62, 228, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 7, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 47, 224, 42, 253, 5, 251, 97, 141, 218, 227, 207, 239, 37, 227, 178, 1, 158, 253, 1, 117, 131, 143, 20, 140, 18, 89, 142, 152, 60, 66, 2, 42, 2, 32, 31, 170, 18, 76, 202, 110, 146, 92, 38, 86, 48, 65, 246, 187, 196, 251, 144, 97, 44, 105, 148, 155, 179, 34, 19, 132, 14, 65, 40, 154, 133, 66, 65, 33, 3, 160, 157, 3, 72, 11, 29, 38, 247, 73, 9, 10, 207, 245, 96, 66, 246, 62, 16, 232, 44, 20, 186, 199, 1, 88, 187, 130, 33, 158, 97, 202, 129, 9, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 135, 19, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 102, 23, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0]
  }
}
```

The wallet rechecks finality, scripts, satoshi accounting, A conservation and its processors’ agreed latest revision, then processes these unchanged signed bytes. In this independent fixture no later revision has been accepted. Sequences remain 7 and 9. It submits to `tm_color_e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0` and verifies admission of output 0 under the configured provider policy. Alice’s default-basket satoshis and the token destination are reconciled once; the source reservation resolves on established completion. If required admission instead failed, the wallet would retain the actual pending/error state rather than claim a reversal.

### A.126. An earlier bid consumes the named token input

Start from A.119, but an earlier authorized revision has now consumed A before the requested replacement was accepted. A current lookup therefore returns no spendable A outpoint.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI2",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": true,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI2",
  "status": "error",
  "code": 1,
  "description": "Auction input has been consumed by a conflicting revision"
}
```

The wallet verifies the conflict, does not sign or process this competing revision, and reconciles its shared exposure record against the observed spender. It cannot credit prospective outputs from both revisions or count a sequence increase as evidence that this revision won.

### A.127. Reject internalization of a fully signed non-final receipt

This independent case selects Bob’s wallet/root (private fixture integer 22), which controls output 0 of the supplied auction transaction. Both scripts pass, but the evaluated chain context is height 100200. A provider’s pending record or premature positive answer cannot waive that fact.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI3",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 71, 93, 51, 45, 30, 158, 224, 129, 222, 224, 174, 17, 219, 2, 164, 4, 181, 11, 178, 155, 138, 217, 182, 131, 41, 116, 139, 29, 115, 104, 101, 56, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 159, 105, 230, 49, 169, 218, 22, 135, 113, 98, 196, 53, 77, 87, 196, 123, 162, 253, 29, 149, 143, 188, 1, 84, 160, 249, 93, 131, 214, 237, 218, 186, 2, 32, 107, 144, 99, 50, 224, 129, 85, 208, 217, 199, 185, 161, 210, 112, 6, 171, 121, 26, 175, 141, 146, 231, 174, 220, 246, 30, 223, 13, 230, 130, 62, 228, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 7, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 47, 224, 42, 253, 5, 251, 97, 141, 218, 227, 207, 239, 37, 227, 178, 1, 158, 253, 1, 117, 131, 143, 20, 140, 18, 89, 142, 152, 60, 66, 2, 42, 2, 32, 31, 170, 18, 76, 202, 110, 146, 92, 38, 86, 48, 65, 246, 187, 196, 251, 144, 97, 44, 105, 148, 155, 179, 34, 19, 132, 14, 65, 40, 154, 133, 66, 65, 33, 3, 160, 157, 3, 72, 11, 29, 38, 247, 73, 9, 10, 207, 245, 96, 66, 246, 62, 16, 232, 44, 20, 186, 199, 1, 88, 187, 130, 33, 158, 97, 202, 129, 9, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 135, 19, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 102, 23, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04_0\",\"derivationPrefix\":\"Bw==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI3",
  "status": "error",
  "code": 1,
  "description": "Receipt transaction is consensus-non-final"
}
```

The wallet rejects internalization without silently submitting or crediting 1000 A. At an eligible later context, the same bytes can be reconsidered only with complete receipt, history and current per-asset admission checks. Signature completeness and receipt spendability remain separate facts.

### A.128. Abort does not expire an auction signature

The independently established A.119 offer has been delivered to Bob. The application cancels local processing before lockTime is reached.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI4",
  "call": "abortAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI4",
  "status": "success",
  "result": {
    "aborted": true
  }
}
```

The wallet stops its local pending processing and retains the released-signature exposure. This fixture has no processor-accepted revision to cancel. If one existed, cancellation would require coordination and could not be reported as complete from local deletion alone. The earlier transaction may become final after height 100250; lockTime is not an expiry. A remains reserved/unavailable unless a verified conflict or an independently validated condition prevents the earlier capability from consuming it. An explicit conflicting-spend workflow requires consent and does not release this record merely by being requested.

### A.129. Bootstrap a new issuance through the shared discovery topic

The fixed-supply deployment is authorized as in A.28. No provider serves its asset-specific topic before the transaction is signed, but an accepted provider already serves tm\_color. Normal processing is requested.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTI5",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens issuance reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100c5d805453982b10c19f9bde6916d180bbd61d6a0415d8690d53009fe135a43d2022019a4dab39a1fb736771a8e201ab265fa3a9e561c190a8b4eb8e97590e6bd2a50412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "000340420f6d76a914cfebd325666fc742644d84fa85c1128d9cfa05ef88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2243513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d6669786564227d5d2c22696e707574506f73697469",
      "p color plan 3 4 6f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTI5",
  "status": "success",
  "result": {
    "txid": "b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98"
  }
}
```

The signed deployment fixes asset ID `b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0`. The wallet sends the deployment BEEF to BRC-22 POST `/submit` with header `X-Topics: ["tm_color"]`. A successful discovery response is:

```json
{
  "tm_color": {
    "outputsToAdmit": [0],
    "coinsToRetain": []
  }
}
```

The configured provider validates the deploy and seeds `tm_color_b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0` and `ls_color_b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0`; the wallet discovers an accepted advertised service and queries:

```json
{
  "service": "ls_color_b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0",
  "query": {
    "type": "color-spendability",
    "assetId": "b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0",
    "topic": "tm_color_b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98_0",
    "outpoints": [
      "b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98.0"
    ]
  }
}
```

The lookup returns:

```json
{
  "type": "output-list",
  "outputs": [
    {
      "beef": [1, 1, 1, 1, 152, 58, 28, 219, 231, 54, 35, 177, 4, 51, 28, 107, 242, 171, 243, 195, 7, 189, 79, 13, 14, 131, 5, 191, 185, 163, 38, 31, 153, 209, 221, 178, 2, 0, 190, 239, 0, 2, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 1, 0, 0, 0, 1, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 197, 216, 5, 69, 57, 130, 177, 12, 25, 249, 189, 230, 145, 109, 24, 11, 189, 97, 214, 160, 65, 93, 134, 144, 213, 48, 9, 254, 19, 90, 67, 210, 2, 32, 25, 164, 218, 179, 154, 31, 183, 54, 119, 26, 142, 32, 26, 178, 101, 250, 58, 158, 86, 28, 25, 10, 139, 78, 184, 233, 117, 144, 230, 189, 42, 80, 65, 33, 2, 230, 23, 112, 84, 194, 66, 4, 226, 22, 143, 1, 219, 133, 227, 212, 185, 166, 70, 196, 238, 145, 95, 81, 92, 148, 136, 75, 54, 19, 51, 69, 19, 255, 255, 255, 255, 2, 1, 0, 0, 0, 0, 0, 0, 0, 31, 0, 3, 64, 66, 15, 109, 118, 169, 20, 207, 235, 211, 37, 102, 111, 199, 66, 100, 77, 132, 250, 133, 193, 18, 141, 156, 250, 5, 239, 136, 172, 5, 39, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
      "outputIndex": 0
    }
  ]
}
```

The wallet reconstructs and verifies the returned output, assigns its per-asset basket, preserves the original ISSUE derivation, and credits 1000000 units only after all required admissions succeed. The discovery response alone was not that permission.

### A.130. Discovery succeeds but no per-asset provider is available

Use the same authorized deployment as A.129. Its Bitcoin processing and tm\_color admission succeeded, but configured discovery cannot yet find an accepted service for the final asset ID.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTMw",
  "call": "createAction",
  "args": {
    "description": "Colored Tokens issuance reference action",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12],
    "inputs": [
      {
        "outpoint": "0c4d943ef031945db8a49d30a820891c8b0fdd53fc2dab6c4d7ac3fa78744fc7.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 4294967295,
        "unlockingScript": "483045022100c5d805453982b10c19f9bde6916d180bbd61d6a0415d8690d53009fe135a43d2022019a4dab39a1fb736771a8e201ab265fa3a9e561c190a8b4eb8e97590e6bd2a50412102e6177054c24204e2168f01db85e3d4b9a646c4ee915f515c94884b3613334513"
      }
    ],
    "outputs": [
      {
        "lockingScript": "000340420f6d76a914cfebd325666fc742644d84fa85c1128d9cfa05ef88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b224953535545225d2c226f776e65644f757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a224953535545222c2264657269766174696f6e507265666978",
      "p color plan 1 4 223a2243513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a223032373962653636376566396463626261633535613036323935636538373062303730323962666364623264",
      "p color plan 2 4 63653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a224953535545222c226f7065726174696f6e223a226465706c6f792d6669786564227d5d2c22696e707574506f73697469",
      "p color plan 3 4 6f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a317d5d2c22736967686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": true,
      "signAndProcess": true,
      "acceptDelayedBroadcast": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTMw",
  "status": "error",
  "code": 1,
  "description": "Deployment processed; per-asset admission remains pending"
}
```

The wallet retains transaction `b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98` and its prospective asset basket in pending state. It reports the actual processing result through its action records; the error does not imply that the transaction was undone. Reconciliation and resubmission of the retained action MUST use the same deployment and be idempotent; they must not select new inputs or create a second issuance. This does not make a repeated createAction request idempotent: its CWI id is only correlation. The application first inspects the retained action through normal action discovery and the pending inventory, rather than blindly issuing a fresh createAction. Inventory may expose the signed pending output with spendable:false, but its available balance is "0". Once accepted per-asset evidence arrives, reconciliation credits it once.

### A.131. Discovery admission is insufficient for an incoming receipt

The signed deployment is present in tm\_color, but its per-asset lookup does not show the received output as admitted and unspent. Alice controls the ISSUE-derived output.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTMx",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 152, 58, 28, 219, 231, 54, 35, 177, 4, 51, 28, 107, 242, 171, 243, 195, 7, 189, 79, 13, 14, 131, 5, 191, 185, 163, 38, 31, 153, 209, 221, 178, 2, 0, 190, 239, 0, 2, 2, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 1, 0, 0, 0, 1, 199, 79, 116, 120, 250, 195, 122, 77, 108, 171, 45, 252, 83, 221, 15, 139, 28, 137, 32, 168, 48, 157, 164, 184, 93, 148, 49, 240, 62, 148, 77, 12, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 197, 216, 5, 69, 57, 130, 177, 12, 25, 249, 189, 230, 145, 109, 24, 11, 189, 97, 214, 160, 65, 93, 134, 144, 213, 48, 9, 254, 19, 90, 67, 210, 2, 32, 25, 164, 218, 179, 154, 31, 183, 54, 119, 26, 142, 32, 26, 178, 101, 250, 58, 158, 86, 28, 25, 10, 139, 78, 184, 233, 117, 144, 230, 189, 42, 80, 65, 33, 2, 230, 23, 112, 84, 194, 66, 4, 226, 22, 143, 1, 219, 133, 227, 212, 185, 166, 70, 196, 238, 145, 95, 81, 92, 148, 136, 75, 54, 19, 51, 69, 19, 255, 255, 255, 255, 2, 1, 0, 0, 0, 0, 0, 0, 0, 31, 0, 3, 64, 66, 15, 109, 118, 169, 20, 207, 235, 211, 37, 102, 111, 199, 66, 100, 77, 132, 250, 133, 193, 18, 141, 156, 250, 5, 239, 136, 172, 5, 39, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color b2ddd1991f26a3b9bf05830e0d4fbd07c3f3abf26b1c3304b12336e7db1c3a98v0",
          "customInstructions": "{\"version\":1,\"assetId\":\"ISSUE\",\"derivationPrefix\":\"CQ==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\"}"
        }
      }
    ]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTMx",
  "status": "error",
  "code": 1,
  "description": "Received deploy output is absent from its asset overlay"
}
```

The wallet rejects without submission to either topic. The sender or higher-level deployment flow must finish asset-service bootstrap before retrying this receipt. A caller-supplied service URL or discovery-topic acknowledgement cannot replace the independently derived per-asset service.

### A.132. Non-P balance wire compatibility

The ordinary savings basket contains 2500 available satoshis and the origin has read permission. This is the existing non-P BRC-112 query.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTMy",
  "call": "listOutputs",
  "args": {
    "basket": "balance savings"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTMy",
  "status": "success",
  "result": {
    "totalOutputs": 2500,
    "outputs": []
  }
}
```

totalOutputs remains a JSON/API number and the existing numeric VarInt bytes fd c4 09 in the binary response. The P-basket decimal-text format does not apply. Ordinary p color assets inventory counts also retain numeric VarInt encoding, even though the basket starts with p.

### A.133. P-basket balance resource exhaustion fails explicitly

This wallet documents a response-size bound. The requested exact aggregate would exceed it; this case assumes individually valid u64 outputs and sufficient input provenance, not an invalid asset.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTMz",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance ece04819257245af3290370225db8e2d3b9541096d8c0fb3a8cd3c09158c7d6cv0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTMz",
  "status": "error",
  "code": 1,
  "description": "Exact balance exceeds documented response resource limit"
}
```

No success value is returned. Holdings remain tracked, and the wallet does not truncate digits, cap the sum, silently page the balance, emit a number, or switch back to numeric VarInt. The receiver likewise checks the declared byte length before allocation and rejects over-limit or noncanonical text. This implementation bound is distinct from the per-output uint64 consensus-independent token rule.

### A.134. Processor coordination is required before a partial offer

Use A.119’s sources, terms and fixture tip, but this wallet cannot establish authenticated processor coverage for the family’s intended lifetime.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM0",
  "call": "createAction",
  "args": {
    "description": "Colored Token auction: 5000 satoshi bid, earliest inclusion after height 100250",
    "inputBEEF": [2, 0, 190, 239, 0, 2, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61],
    "inputs": [
      {
        "outpoint": "e5445b1613604484d755ad7c169f3e37f63a686c8d1b10e3bae0776e8db3aa04.0",
        "inputDescription": "Authenticated input 0",
        "sequenceNumber": 7,
        "unlockingScriptLength": 107
      },
      {
        "outpoint": "3d41442b05101fe2b9dc74e24853cb278b2e9494b3d78452b0e858b991bce28e.0",
        "inputDescription": "Authenticated input 1",
        "sequenceNumber": 9,
        "unlockingScriptLength": 107
      }
    ],
    "outputs": [
      {
        "lockingScript": "2004aab38d6e77e0bae3101b8d6c683af6373e9f167cad55d784446013165b44e502e8036d76a914981d7a3bcc51208c145c27145fcd722bcf78c98b88ac",
        "satoshis": 1,
        "outputDescription": "Agreed output 0"
      },
      {
        "lockingScript": "76a91430e2861546dcf665575f3c280857e6d2c4d944da88ac",
        "satoshis": 4999,
        "outputDescription": "Agreed output 1"
      }
    ],
    "labels": [
      "p color plan 0 4 7b2276657273696f6e223a312c22617373657473223a5b22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130345f30225d2c226f776e65644f",
      "p color plan 1 4 757470757473223a5b5d2c226f7065726174696f6e73223a5b7b2261737365744964223a22653534343562313631333630343438346437353561643763313639663365333766363361363836633864316231306533626165303737366538646233616130",
      "p color plan 2 4 345f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f736974696f6e73223a5b7b2261737365744964223a6e756c6c2c22696e646578223a327d5d2c227369",
      "p color plan 3 4 67686173685479706573223a5b5d7d"
    ],
    "version": 1,
    "lockTime": 100250,
    "options": {
      "randomizeOutputs": false,
      "returnTXIDOnly": false,
      "signAndProcess": false,
      "acceptDelayedBroadcast": false,
      "noSend": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM0",
  "status": "error",
  "code": 2,
  "description": "Required processor replacement coordination is unavailable"
}
```

The wallet fails before releasing Alice’s signature or a signable transaction. It rolls back only provisional reservations. The application is not asked to implement miner selection, propagate revisions itself, or accept a disclaimer in place of coordinated replacement.

### A.135. Accept a higher sequence through the coordinated processors

The wallet retains the A.119 saved context at height 100200. Bob previously completed the original revision and the wallet reconciled its processor acceptance as `386568731d8b742983b6d98a9bb20bb504a402db11aee0de81e09e1e2d335d47`, sequence vector \[7, 9]. Both named inputs remain unspent; the token source remains currently admitted. This call requests processing of the valid \[7, 10] revision, allowing delayed broadcast.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM1",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM1",
  "status": "success",
  "result": {
    "txid": "a0e189f78d599eca8039f64ce6d223d876fa009beb6a2c1c43cc25d110cb1c35",
    "tx": [1, 1, 1, 1, 53, 28, 203, 16, 209, 37, 204, 67, 28, 44, 106, 235, 155, 0, 250, 118, 216, 35, 210, 230, 76, 246, 57, 128, 202, 158, 89, 141, 247, 137, 225, 160, 2, 0, 190, 239, 0, 3, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 1, 0, 0, 0, 2, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 99, 76, 112, 100, 26, 160, 52, 158, 2, 200, 61, 109, 25, 104, 221, 186, 244, 162, 195, 240, 46, 102, 236, 162, 68, 123, 250, 89, 197, 198, 235, 213, 2, 32, 10, 120, 1, 11, 133, 194, 239, 69, 194, 136, 209, 232, 154, 192, 25, 160, 93, 142, 90, 144, 42, 191, 9, 116, 40, 27, 7, 28, 11, 121, 21, 44, 65, 33, 3, 163, 201, 109, 240, 35, 41, 203, 191, 5, 110, 226, 94, 224, 194, 75, 163, 108, 118, 32, 204, 233, 180, 235, 164, 25, 114, 50, 122, 18, 161, 242, 45, 7, 0, 0, 0, 142, 226, 188, 145, 185, 88, 232, 176, 82, 132, 215, 179, 148, 148, 46, 139, 39, 203, 83, 72, 226, 116, 220, 185, 226, 31, 16, 5, 43, 68, 65, 61, 0, 0, 0, 0, 107, 72, 48, 69, 2, 33, 0, 154, 6, 148, 48, 63, 244, 24, 134, 128, 4, 232, 156, 65, 52, 233, 135, 49, 245, 182, 66, 68, 21, 110, 69, 156, 186, 193, 162, 233, 154, 163, 120, 2, 32, 61, 90, 245, 193, 175, 206, 20, 152, 147, 93, 211, 151, 13, 6, 255, 159, 36, 55, 12, 126, 157, 48, 207, 38, 173, 91, 84, 70, 218, 106, 192, 166, 65, 33, 3, 160, 157, 3, 72, 11, 29, 38, 247, 73, 9, 10, 207, 245, 96, 66, 246, 62, 16, 232, 44, 20, 186, 199, 1, 88, 187, 130, 33, 158, 97, 202, 129, 10, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0, 62, 32, 4, 170, 179, 141, 110, 119, 224, 186, 227, 16, 27, 141, 108, 104, 58, 246, 55, 62, 159, 22, 124, 173, 85, 215, 132, 68, 96, 19, 22, 91, 68, 229, 2, 232, 3, 109, 118, 169, 20, 152, 29, 122, 59, 204, 81, 32, 140, 20, 92, 39, 20, 95, 205, 114, 43, 207, 120, 201, 139, 136, 172, 135, 19, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 48, 226, 134, 21, 70, 220, 246, 101, 87, 95, 60, 40, 8, 87, 230, 210, 196, 217, 68, 218, 136, 172, 102, 23, 0, 0, 0, 0, 0, 0, 25, 118, 169, 20, 150, 247, 198, 175, 245, 135, 77, 44, 87, 248, 202, 117, 70, 109, 54, 32, 255, 251, 155, 196, 136, 172, 154, 135, 1, 0]
  }
}
```

The wallet validates the new Bob signature, regenerates Alice’s affected signature and authenticates the required processors’ acceptance of `a0e189f78d599eca8039f64ce6d223d876fa009beb6a2c1c43cc25d110cb1c35` as the latest revision superseding `386568731d8b742983b6d98a9bb20bb504a402db11aee0de81e09e1e2d335d47`. Their evidence binds the fixture network, both ordered outpoints, sequence vector \[7, 10], processor identities and the arrangement’s lifetime and obligations. Only after all required acceptance evidence agrees does this success return. It means processor-accepted pending processing under acceptDelayedBroadcast, not mined settlement or spendable overlay credit. The processors must retain the new revision and suppress stale preference; the application has no processor-specific work to perform.

### A.136. A valid revision refused by a processor is an explicit failure

Use exactly A.135’s valid candidate, but a required processor refuses it under a replacement policy that the wallet was responsible for arranging. Another required processor has already accepted it.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM2",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM2",
  "status": "error",
  "code": 1,
  "description": "Valid replacement was not accepted by the required processors"
}
```

The wallet reports failure of the coordination obligation, preserves both transmitted revisions, reconciles partial acceptance and arranges recovery. It does not call the transaction invalid, report successful replacement, silently settle the older bid, or delegate processor negotiation to the application. The error does not undo the processor that already accepted the new bytes.

### A.137. Timeout leaves acceptance uncertain

Use A.135’s candidate. The wallet submitted it, but the required acknowledgement is lost. It cannot yet establish whether all required processors accepted the replacement.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM3",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM3",
  "status": "error",
  "code": 1,
  "description": "Processor replacement acceptance is unconfirmed"
}
```

The wallet keeps the candidate transaction ID, exact bytes, reservations and transmitted exposure. It queries authenticated state and retries the same transaction idempotently rather than generating another revision or restoring the earlier one. A later acknowledgement must match the exact candidate, family and applicable arrangement; a generic HTTP success cannot finish reconciliation.

### A.138. An older revision cannot be revived after restart

Processors have accepted `400f4a99000d080530654e0c8faa9b9878534b57c94bfebafee6e37c445101aa` with sequence vector \[8, 10], as an independently processed version of A.123. After a wallet restart the application supplies the otherwise valid \[7, 10] revision from A.135. The wallet restores its reservation history and authenticates the latest processor state before deciding.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM4",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM4",
  "status": "error",
  "code": 6,
  "description": "Replacement sequence is older than the accepted family state"
}
```

The request decreases one sequence relative to the latest accepted revision and therefore is not a valid replacement. The wallet rejects before releasing or submitting a new capability. It must not compare against stale \[7, 9] state or treat provider migration/restart as resetting the family. The already accepted higher revision remains authoritative for coordinated processing.

### A.139. Identical accepted bytes are an idempotent resubmission

Independently start after all A.135 acceptance checks succeeded. A later delivery retry uses the existing sendWith flow for that exact stored transaction; it does not repeat createAction or ask signAction to create a new revision.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTM5",
  "call": "createAction",
  "args": {
    "description": "Resubmit accepted auction revision",
    "options": {
      "sendWith": [
        "a0e189f78d599eca8039f64ce6d223d876fa009beb6a2c1c43cc25d110cb1c35"
      ],
      "acceptDelayedBroadcast": true,
      "returnTXIDOnly": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTM5",
  "status": "success",
  "result": {
    "sendWithResults": [
      {
        "txid": "a0e189f78d599eca8039f64ce6d223d876fa009beb6a2c1c43cc25d110cb1c35",
        "status": "unproven"
      }
    ]
  }
}
```

The wallet authenticates the current accepted state and resubmits or confirms the same bytes under the existing arrangement. The unproven status has its ordinary BRC-100 meaning; it does not claim mining or overlay credit. There is no new signature, sequence increment, issuance, reservation or spending charge. If a later revision had been accepted, this older transaction could not be resubmitted as preferred.

### A.140. Cancellation requires processor reconciliation

The A.135 non-final revision is processor-accepted and still outstanding. The application now requests cancellation, but the wallet cannot establish cancellation under the processor arrangement.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQw",
  "call": "abortAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQw",
  "status": "error",
  "code": 1,
  "description": "Processor-accepted action cancellation could not be established"
}
```

The wallet may stop its local work, but it cannot report that the coordinated action was successfully cancelled. It retains the processor state and released exposure, informs the application of the failure and continues reconciliation. Neither local reference deletion nor noSend expiry revokes the transaction or the processors’ obligations.

### A.141. An overlay answer cannot stand in for processor acceptance

Use A.135’s valid candidate. The asset overlay reports a prospective record, and a relay returns HTTP success, but the wallet has no authenticated evidence that the required processors accepted and superseded the prior revision.

An application originates this request to the conforming wallet:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQx",
  "call": "signAction",
  "args": {
    "reference": "Y29sb3ItYXVjdGlvbi0wMDAx",
    "spends": {
      "1": {
        "unlockingScript": "4830450221009a0694303ff418868004e89c4134e98731f5b64244156e459cbac1a2e99aa37802203d5af5c1afce1498935dd3970d06ff9f24370c7e9d30cf26ad5b5446da6ac0a6412103a09d03480b1d26f749090acff56042f63e10e82c14bac70158bb82219e61ca81",
        "sequenceNumber": 10
      }
    },
    "options": {
      "noSend": false,
      "returnTXIDOnly": false,
      "acceptDelayedBroadcast": true
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQx",
  "status": "error",
  "code": 1,
  "description": "Required authenticated replacement acceptance evidence is missing"
}
```

The wallet rejects the acceptance claim and preserves uncertain transmitted exposure. Token validity, topical membership, request delivery and sequence replacement acceptance are distinct facts. The application receives an explicit failure and is not expected to inspect provider internals to discover the missing guarantee.

### A.141a. Fixtures for required-profile cases

The following are offline example fixtures using the same Alice identity and trusted-chain assumptions as A.0. Their coinbase outputs are assumed mature and authenticated in the fixture chain; no live transaction is implied. Unless a case states otherwise, source history and baseline overlay checks pass. Each case begins independently. The URI identifiers below are test identifiers, not additional standardized profiles or network endpoints.

The example revision profile at `urn:example:color:revision`, version 1 with null parameters, accepts only the illustrated binary prefix followed by OP\_CODESEPARATOR and the remittance-derived P2PKH. It validates `ext[identifier]` as exactly `{revision: nonnegative integer}` from an authenticated initialization at 0, requires a single same-amount successor whose revision increments by one and whose signing role is verified, and requires resource-update consent. It uses baseline overlay evidence and never marks these outputs eligible for automatic selection. Its optional profileData leaf for the asset is exactly `{expectedRevision: integer}` and must equal the received or successor revision. These fixture rules exercise the extension contract; real profiles must publish their complete rules separately.

| Fixture | Asset ID                                                             | Deploy payload (DAG-CBOR diagnostic JSON)                                                                                                                                        |
| ------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| U       | `5f0ebd014a0b9b4147d3e0da863a74634237eeaf6d62ef04339aa685092dd3f8_0` | `{"req":{"urn:example:color:unknown":{"version":1,"params":null}}}`                                                                                                              |
| V       | `a3e442c17e1fbe9f6b7a212d79900270fed9c9bad3620d96adc152501fca081e_0` | `{"req":{"urn:example:color:revision":{"version":2,"params":null}}}`                                                                                                             |
| C       | `d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7a_0` | `{"req":{"urn:example:color:revision":{"version":1,"params":null}},"ext":{"urn:example:color:revision":{"revision":0}}}`                                                         |
| D       | `2032d9508230bbe5b8eb7b3e1cccd9db4a4fc6ccd462c4a2b71f820864c45cca_0` | `{"sym":"ITEM","ext":{"urn:example:display:words":{"one":"item","many":"items"}}}`                                                                                               |
| M       | `aa53e365fda06d46075fe264b1b52ad25e927e92ab08fde1a87f31c1e8aaa731_0` | `{"req":null}`                                                                                                                                                                   |
| L       | `6e983e939820a9267f7ae63ac549577292781c5e1904e6c358205d5bee90a8a7_0` | None: legacy JSON; its extra `colorPayload` member has no processing meaning.                                                                                                    |
| X       | `2320c3ff826483010d41501fad1c766835e7d25fe7e3c74d2fcb2fade4ba1257_0` | `{"req":{"urn:example:color:revision":{"version":1,"params":null},"urn:example:color:unknown":{"version":1,"params":null}},"ext":{"urn:example:color:revision":{"revision":0}}}` |
| N       | `34495df365e76d8fa01596ca8348126e0c60cbb898c5162670a8e7d670ad0f3e_0` | `{"req":{"urn:example:color:no-overlay":{"version":1,"params":null}}}`                                                                                                           |

The actual deploy locking scripts are:

| Fixture | Script hex                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| U       | `00596d33a163726571a1781975726e3a6578616d706c653a636f6c6f723a756e6b6e6f776ea266706172616d73f66776657273696f6e017576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                                                                                                                   |
| V       | `00596d34a163726571a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea266706172616d73f66776657273696f6e027576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                                                                                                                 |
| C       | `00596d4c60a263657874a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea1687265766973696f6e0063726571a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea266706172616d73f66776657273696f6e0175ab76a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                     |
| D       | `00596d3fa263657874a1781975726e3a6578616d706c653a646973706c61793a776f726473a2636f6e65646974656d646d616e79656974656d736373796d644954454d7576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                                                                                           |
| M       | `00596d06a163726571f67576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                                                                                                                                                                                                             |
| L       | `0063036f726451126170706c69636174696f6e2f6273762d3230004ca37b2270223a226273762d3230222c226f70223a226465706c6f792b6d696e74222c22616d74223a2239222c22636f6c6f725061796c6f6164223a22613136333732363537316131373831393735373236653361363537383631366437303663363533613633366636633666373233613735366536623665366637373665613236363730363137323631366437336636363737363635373237333639366636653031227d6876a914fc63218003683dc3305d0644a52f73eb0802e5c188ac` |
| X       | `00596d4c8da263657874a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea1687265766973696f6e0063726571a2781975726e3a6578616d706c653a636f6c6f723a756e6b6e6f776ea266706172616d73f66776657273696f6e01781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea266706172616d73f66776657273696f6e0175ab76a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                           |
| N       | `00596d36a163726571a1781c75726e3a6578616d706c653a636f6c6f723a6e6f2d6f7665726c6179a266706172616d73f66776657273696f6e017576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac`                                                                                                                                                                                                                                                                             |

### A.142. Unknown required profile

Use fixture U below. The deploy and nine units are valid under the base token rules and its current overlay answer is positive. This wallet does not implement its required profile.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQy",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 5f0ebd014a0b9b4147d3e0da863a74634237eeaf6d62ef04339aa685092dd3f8v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQy",
  "status": "error",
  "code": 1,
  "description": "Required processing profile is unsupported"
}
```

Base admission does not justify a wallet balance. The operation fails without credited value, signatures or fallback to an ordinary basket. An unsupported-resource notice may be displayed separately.

### A.143. Known identifier with an unsupported version

Use fixture V. The wallet implements only version 1 of the example revision profile.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQz",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance a3e442c17e1fbe9f6b7a212d79900270fed9c9bad3620d96adc152501fca081ev0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQz",
  "status": "error",
  "code": 1,
  "description": "Required processing profile version is unsupported"
}
```

Recognizing the identifier does not authorize interpreting version 2 as version 1. Parameters and exact versions must be supported.

### A.144. Advisory data does not acquire authority

Use fixture D. No required profiles exist. All baseline checks pass, and Alice authorizes the ten-satoshi fee and this internal transfer.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ0",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,50,53,52,44,50,50,56,44,50,48,44,53,51,44,50,51,52,44,49,50,56,44,50,49,48,44,50,51,57,44,49,50,56,44,49,48,51,44,49,48,53,44,49,52,53,44,49,56,53,44,49,54,52,44,55,55,44,51,53,44,49,52,53,44,57,51,44,53,48,44,49,51,55,44,49,48,49,44,56,55,44,50,48,49,44,55,53,44,57,53,44,49,54,57,44,49,54,53,44,49,56,56,44,49,56,49,44,49,57,57,44,49,51,44,50,51,52,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,48,44,51,44,50,48,55,44,49,51,53,44,49,44,49,53,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,49,48,48,44,49,48,53,44,49,49,53,44,49,49,50,44,49,48,56,44,57,55,44,49,50,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,57,51,44,48,44,56,57,44,49,48,57,44,54,51,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,53,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,49,48,48,44,49,48,53,44,49,49,53,44,49,49,50,44,49,48,56,44,57,55,44,49,50,49,44,53,56,44,49,49,57,44,49,49,49,44,49,49,52,44,49,48,48,44,49,49,53,44,49,54,50,44,57,57,44,49,49,49,44,49,49,48,44,49,48,49,44,49,48,48,44,49,48,53,44,49,49,54,44,49,48,49,44,49,48,57,44,49,48,48,44,49,48,57,44,57,55,44,49,49,48,44,49,50,49,44,49,48,49,44,49,48,53,44,49,49,54,44,49,48,49,44,49,48,57,44,49,49,53,44,57,57,44,49,49,53,44,49,50,49,44,49,48,57,44,49,48,48,44,55,51,44,56,52,44,54,57,44,55,55,44,49,49,55,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,50,48,50,44,57,50,44,49,57,54,44,49,48,48,44,56,44,49,51,48,44,51,49,44,49,56,51,44,49,54,50,44,49,57,54,44,57,56,44,50,49,50,44,50,48,52,44,49,57,56,44,55,57,44,55,52,44,50,49,57,44,50,49,55,44,50,48,52,44,50,56,44,54,50,44,49,50,51,44,50,51,53,44,49,56,52,44,50,50,57,44,49,56,55,44,52,56,44,49,51,48,44,56,48,44,50,49,55,44,53,48,44,51,50,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,51,44,51,50,44,50,48,50,44,57,50,44,49,57,54,44,49,48,48,44,56,44,49,51,48,44,51,49,44,49,56,51,44,49,54,50,44,49,57,54,44,57,56,44,50,49,50,44,50,48,52,44,49,57,56,44,55,57,44,55,52,44,50,49,57,44,50,49,55,44,50,48,52,44,50,56,44,54,50,44,49,50,51,44,50,51,53,44,49,56,52,44,50,50,57,44,49,56,55,44,52,56,44,49,51,48,44,56,48,44,50,49,55,44,53,48,44,51,50,44,56,57,44,49,48,57,44,49,44,49,54,48,44,49,49,55,44,49,49,56,44,49,54,57,44,50,48,44,49,57,55,44,49,51,48,44,49,55,56,44,49,49,54,44,50,48,52,44,49,50,56,44,50,55,44,49,50,44,55,49,44,57,51,44,49,48,56,44,56,51,44,55,57,44,50,53,48,44,49,54,56,44,49,50,48,44,49,55,51,44,49,54,51,44,50,53,48,44,49,55,50,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,50,48,51,50,100,57,53,48,56,50,51,48,98,98,101,53,98,56,101,98,55,98,51,101,49,99,99,99,100,57,100,98,52,97,52,102,99,54,99,99,100,52,54,50,99,52,97,50,98,55,49,102,56,50,48,56,54,52,99,52,53,99,99,97,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ0",
  "status": "success",
  "result": {
    "signature": [48,69,2,33,0,212,118,42,3,231,91,15,99,218,234,194,67,181,141,3,3,172,206,153,227,33,69,156,115,14,66,38,33,189,75,249,248,2,32,83,233,214,146,31,4,76,18,106,69,27,20,17,76,150,252,10,179,122,32,108,109,182,1,148,213,220,134,139,106,34,19]
  }
}
```

The unknown display extension does not change nine-unit accounting or trigger code loading. The returned signature verifies against the full baseline source script. Display words neither prove identity nor authorize signing.

### A.145. Omitting requirements from a successor cannot remove them

Use fixture U. This transaction omits req from its proposed successor payload; its nine units and ordinary script are otherwise valid.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ1",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,50,50,51,44,55,57,44,49,57,50,44,49,55,54,44,52,54,44,53,51,44,49,49,44,49,50,56,44,52,57,44,49,56,53,44,56,57,44,49,54,51,44,49,55,44,49,48,57,44,50,51,48,44,49,56,51,44,53,56,44,49,55,57,44,49,54,53,44,51,44,49,52,48,44,50,53,44,50,49,51,44,54,48,44,53,57,44,55,54,44,50,50,57,44,50,52,56,44,49,56,55,44,49,57,55,44,49,51,51,44,52,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,48,44,51,44,50,48,52,44,49,51,53,44,49,44,49,53,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,49,49,55,44,49,49,48,44,49,48,55,44,49,49,48,44,49,49,49,44,49,49,57,44,49,49,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,56,49,44,48,44,56,57,44,49,48,57,44,53,49,44,49,54,49,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,49,44,49,50,48,44,50,53,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,55,44,49,49,48,44,49,48,55,44,49,49,48,44,49,49,49,44,49,49,57,44,49,49,48,44,49,54,50,44,49,48,50,44,49,49,50,44,57,55,44,49,49,52,44,57,55,44,49,48,57,44,49,49,53,44,50,52,54,44,49,48,51,44,49,49,56,44,49,48,49,44,49,49,52,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,50,52,56,44,50,49,49,44,52,53,44,57,44,49,51,51,44,49,54,54,44,49,53,52,44,53,49,44,52,44,50,51,57,44,57,56,44,49,48,57,44,49,55,53,44,50,51,56,44,53,53,44,54,54,44,57,57,44,49,49,54,44,53,56,44,49,51,52,44,50,49,56,44,50,50,52,44,50,49,49,44,55,49,44,54,53,44,49,53,53,44,49,49,44,55,52,44,49,44,49,56,57,44,49,52,44,57,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,51,44,51,50,44,50,52,56,44,50,49,49,44,52,53,44,57,44,49,51,51,44,49,54,54,44,49,53,52,44,53,49,44,52,44,50,51,57,44,57,56,44,49,48,57,44,49,55,53,44,50,51,56,44,53,53,44,54,54,44,57,57,44,49,49,54,44,53,56,44,49,51,52,44,50,49,56,44,50,50,52,44,50,49,49,44,55,49,44,54,53,44,49,53,53,44,49,49,44,55,52,44,49,44,49,56,57,44,49,52,44,57,53,44,56,57,44,49,48,57,44,49,44,49,54,48,44,49,49,55,44,49,49,56,44,49,54,57,44,50,48,44,49,48,51,44,55,54,44,49,52,57,44,50,51,54,44,49,54,48,44,52,57,44,49,55,57,44,49,52,53,44,50,49,52,44,55,51,44,54,50,44,50,51,51,44,54,51,44,50,48,55,44,49,54,48,44,50,49,44,50,51,48,44,49,49,57,44,57,56,44,49,56,57,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,53,102,48,101,98,100,48,49,52,97,48,98,57,98,52,49,52,55,100,51,101,48,100,97,56,54,51,97,55,52,54,51,52,50,51,55,101,101,97,102,54,100,54,50,101,102,48,52,51,51,57,97,97,54,56,53,48,57,50,100,100,51,102,56,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ1",
  "status": "error",
  "code": 1,
  "description": "Required processing profile is unsupported"
}
```

The wallet resolves the original deploy and still encounters the unsupported requirement. Inheritance does not depend on copying metadata into every output.

### A.146. A successor cannot replace the required map

Use fixture C and its supported revision profile. The proposed successor repeats an empty req map although the deploy requires the profile.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ2",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,49,52,52,44,49,49,51,44,49,55,50,44,49,56,54,44,54,54,44,50,54,44,57,52,44,49,50,51,44,49,49,56,44,51,50,44,51,52,44,50,49,50,44,56,52,44,49,53,56,44,54,44,50,50,48,44,49,52,50,44,49,57,57,44,56,57,44,50,51,50,44,49,54,57,44,49,52,54,44,49,55,53,44,49,51,52,44,57,50,44,49,57,53,44,49,56,48,44,50,48,57,44,49,53,44,51,57,44,49,53,48,44,49,55,50,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,49,44,51,44,50,48,54,44,49,51,53,44,49,44,49,54,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,57,57,44,49,49,49,44,49,49,48,44,49,49,54,44,49,49,52,44,57,55,44,57,57,44,49,49,54,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,50,56,44,48,44,56,57,44,49,48,57,44,55,54,44,57,54,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,48,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,50,44,49,48,50,44,49,49,50,44,57,55,44,49,49,52,44,57,55,44,49,48,57,44,49,49,53,44,50,52,54,44,49,48,51,44,49,49,56,44,49,48,49,44,49,49,52,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,49,51,44,51,50,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,56,57,44,49,48,57,44,53,48,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,48,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,52,54,44,49,49,54,44,50,51,55,44,49,56,57,44,49,51,50,44,49,56,51,44,49,57,55,44,56,55,44,49,54,52,44,50,49,56,44,50,49,50,44,50,56,44,48,44,49,48,55,44,50,49,54,44,49,52,56,44,55,51,44,50,52,51,44,49,54,50,44,50,49,56,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,100,55,52,56,101,99,54,101,57,98,53,101,48,50,54,54,52,99,56,48,102,57,57,54,50,101,51,56,53,56,55,52,53,54,100,54,55,52,97,102,51,57,49,102,101,54,101,51,54,102,49,49,50,54,97,98,50,52,100,57,98,101,55,97,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ2",
  "status": "error",
  "code": 1,
  "description": "Successor requirements differ from the deploy"
}
```

The repeated map conflicts with the immutable deploy descriptor. A user grant or a positive overlay response cannot authorize removing the requirement.

### A.147. Malformed critical metadata fails wallet processing

Use fixture M, whose base token prefix is valid but whose payload sets req to null.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ3",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance aa53e365fda06d46075fe264b1b52ad25e927e92ab08fde1a87f31c1e8aaa731v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ3",
  "status": "error",
  "code": 1,
  "description": "Malformed required processing profile map"
}
```

An indexer may admit the output under base rules. The wallet must reject the malformed requirement map instead of treating it as absent.

### A.148. Every required profile must be supported

Use fixture X. The wallet implements the revision profile but not the other declared requirement.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ4",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 2320c3ff826483010d41501fad1c766835e7d25fe7e3c74d2fcb2fade4ba1257v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ4",
  "status": "error",
  "code": 1,
  "description": "Required processing profile is unsupported"
}
```

Partial support cannot satisfy the requirement set. No value is returned from the otherwise supported part.

### A.149. Authorize a contract update with unchanged token quantity

Use fixture C. Alice grants this application permission to advance the revision state for this resource and separately authorizes the ten-satoshi fee. The profile verifies source history and the exact transition from revision 0 to 1.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTQ5",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,55,53,44,49,53,52,44,49,57,55,44,50,49,55,44,50,52,57,44,49,48,53,44,55,55,44,49,53,52,44,52,52,44,51,57,44,52,56,44,50,48,56,44,55,57,44,55,56,44,54,54,44,53,51,44,50,51,49,44,49,49,56,44,50,49,44,50,48,44,49,53,56,44,49,49,53,44,57,56,44,49,48,52,44,49,49,52,44,50,50,56,44,49,57,52,44,50,49,50,44,49,51,55,44,53,54,44,49,54,54,44,49,49,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,49,44,51,44,50,48,54,44,49,51,53,44,49,44,49,54,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,57,57,44,49,49,49,44,49,49,48,44,49,49,54,44,49,49,52,44,57,55,44,57,57,44,49,49,54,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,50,56,44,48,44,56,57,44,49,48,57,44,55,54,44,57,54,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,48,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,50,44,49,48,50,44,49,49,50,44,57,55,44,49,49,52,44,57,55,44,49,48,57,44,49,49,53,44,50,52,54,44,49,48,51,44,49,49,56,44,49,48,49,44,49,49,52,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,48,56,44,51,50,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,56,57,44,49,48,57,44,52,53,44,49,54,49,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,52,54,44,49,49,54,44,50,51,55,44,49,56,57,44,49,51,50,44,49,56,51,44,49,57,55,44,56,55,44,49,54,52,44,50,49,56,44,50,49,50,44,50,56,44,48,44,49,48,55,44,50,49,54,44,49,52,56,44,55,51,44,50,52,51,44,49,54,50,44,50,49,56,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,100,55,52,56,101,99,54,101,57,98,53,101,48,50,54,54,52,99,56,48,102,57,57,54,50,101,51,56,53,56,55,52,53,54,100,54,55,52,97,102,51,57,49,102,101,54,101,51,54,102,49,49,50,54,97,98,50,52,100,57,98,101,55,97,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTQ5",
  "status": "success",
  "result": {
    "signature": [48,68,2,32,18,141,103,118,82,44,147,74,246,188,147,75,79,235,253,172,28,204,48,227,223,194,223,90,49,15,86,196,251,99,152,72,2,32,41,240,108,38,113,190,185,103,108,42,140,195,206,188,135,187,81,146,216,184,184,149,218,49,98,235,31,211,105,170,57,54]
  }
}
```

The output preserves nine units and a verified wallet signing role, but state changes still require resource authorization. The wallet computes the actual post-separator preimage and returns its valid signature. The resource remains restricted; signing does not imply automatic balance eligibility.

### A.150. A protocol grant does not authorize a resource update

Use the exact independently evaluated transaction from A.149. Alice has only an ordinary cryptographic protocol grant and denies the resource update.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTUw",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,55,53,44,49,53,52,44,49,57,55,44,50,49,55,44,50,52,57,44,49,48,53,44,55,55,44,49,53,52,44,52,52,44,51,57,44,52,56,44,50,48,56,44,55,57,44,55,56,44,54,54,44,53,51,44,50,51,49,44,49,49,56,44,50,49,44,50,48,44,49,53,56,44,49,49,53,44,57,56,44,49,48,52,44,49,49,52,44,50,50,56,44,49,57,52,44,50,49,50,44,49,51,55,44,53,54,44,49,54,54,44,49,49,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,49,44,51,44,50,48,54,44,49,51,53,44,49,44,49,54,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,57,57,44,49,49,49,44,49,49,48,44,49,49,54,44,49,49,52,44,57,55,44,57,57,44,49,49,54,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,50,56,44,48,44,56,57,44,49,48,57,44,55,54,44,57,54,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,48,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,50,44,49,48,50,44,49,49,50,44,57,55,44,49,49,52,44,57,55,44,49,48,57,44,49,49,53,44,50,52,54,44,49,48,51,44,49,49,56,44,49,48,49,44,49,49,52,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,48,56,44,51,50,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,56,57,44,49,48,57,44,52,53,44,49,54,49,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,52,54,44,49,49,54,44,50,51,55,44,49,56,57,44,49,51,50,44,49,56,51,44,49,57,55,44,56,55,44,49,54,52,44,50,49,56,44,50,49,50,44,50,56,44,48,44,49,48,55,44,50,49,54,44,49,52,56,44,55,51,44,50,52,51,44,49,54,50,44,50,49,56,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,100,55,52,56,101,99,54,101,57,98,53,101,48,50,54,54,52,99,56,48,102,57,57,54,50,101,51,56,53,56,55,52,53,54,100,54,55,52,97,102,51,57,49,102,101,54,101,51,54,102,49,49,50,54,97,98,50,52,100,57,98,101,55,97,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTUw",
  "status": "error",
  "code": 1,
  "description": "Contract resource update is not authorized"
}
```

The wallet verifies the transition but releases no signature. Zero token debit and a permitted fee are insufficient without authorization for this protected state change.

### A.151. Required state cannot be silently dropped

Use fixture C. The proposed successor omits ext entirely, although its inherited revision profile requires a successor revision.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTUx",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,54,52,44,53,49,44,50,51,56,44,55,50,44,49,57,54,44,49,56,51,44,49,55,49,44,49,48,44,49,57,51,44,56,54,44,49,49,53,44,50,49,51,44,49,57,53,44,52,53,44,50,50,48,44,53,56,44,49,50,57,44,57,44,56,55,44,50,53,52,44,51,54,44,49,49,54,44,48,44,52,44,56,57,44,50,55,44,50,49,48,44,51,49,44,49,53,54,44,50,57,44,55,53,44,49,51,52,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,49,44,51,44,50,48,54,44,49,51,53,44,49,44,49,54,44,49,49,50,44,49,49,52,44,49,49,49,44,49,48,50,44,49,48,53,44,49,48,56,44,49,48,49,44,52,53,44,57,57,44,49,49,49,44,49,49,48,44,49,49,54,44,49,49,52,44,57,55,44,57,57,44,49,49,54,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,49,50,56,44,48,44,56,57,44,49,48,57,44,55,54,44,57,54,44,49,54,50,44,57,57,44,49,48,49,44,49,50,48,44,49,49,54,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,49,44,49,48,52,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,48,44,57,57,44,49,49,52,44,49,48,49,44,49,49,51,44,49,54,49,44,49,50,48,44,50,54,44,49,49,55,44,49,49,52,44,49,49,48,44,53,56,44,49,48,49,44,49,50,48,44,57,55,44,49,48,57,44,49,49,50,44,49,48,56,44,49,48,49,44,53,56,44,57,57,44,49,49,49,44,49,48,56,44,49,49,49,44,49,49,52,44,53,56,44,49,49,52,44,49,48,49,44,49,49,56,44,49,48,53,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,54,50,44,49,48,50,44,49,49,50,44,57,55,44,49,49,52,44,57,55,44,49,48,57,44,49,49,53,44,50,52,54,44,49,48,51,44,49,49,56,44,49,48,49,44,49,49,52,44,49,49,53,44,49,48,53,44,49,49,49,44,49,49,48,44,49,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,53,50,44,57,57,44,51,51,44,49,50,56,44,51,44,49,48,52,44,54,49,44,49,57,53,44,52,56,44,57,51,44,54,44,54,56,44,49,54,53,44,52,55,44,49,49,53,44,50,51,53,44,56,44,50,44,50,50,57,44,49,57,51,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,52,44,51,50,44,49,50,50,44,49,57,48,44,50,49,55,44,51,54,44,49,55,49,44,51,56,44,49,55,44,49,49,49,44,50,50,55,44,50,51,48,44,51,49,44,53,55,44,49,55,53,44,49,49,54,44,50,49,52,44,56,54,44,49,49,54,44,56,56,44,53,54,44,52,54,44,49,53,48,44,50,52,57,44,49,50,56,44,55,54,44,49,48,50,44,50,44,57,52,44,49,53,53,44,49,49,48,44,50,51,54,44,55,50,44,50,49,53,44,56,57,44,49,48,57,44,49,44,49,54,48,44,49,49,55,44,49,55,49,44,49,49,56,44,49,54,57,44,50,48,44,50,52,54,44,49,49,54,44,50,51,55,44,49,56,57,44,49,51,50,44,49,56,51,44,49,57,55,44,56,55,44,49,54,52,44,50,49,56,44,50,49,50,44,50,56,44,48,44,49,48,55,44,50,49,54,44,49,52,56,44,55,51,44,50,52,51,44,49,54,50,44,50,49,56,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,100,55,52,56,101,99,54,101,57,98,53,101,48,50,54,54,52,99,56,48,102,57,57,54,50,101,51,56,53,56,55,52,53,54,100,54,55,52,97,102,51,57,49,102,101,54,101,51,54,102,49,49,50,54,97,98,50,52,100,57,98,101,55,97,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTUx",
  "status": "error",
  "code": 1,
  "description": "Required successor state is missing"
}
```

The profile rejects missing state; omission of the repeated req map remains permitted and is not the failure.

### A.152. Restricted resources remain discoverable

Use fixture C alone in this independently initialized wallet. It is admitted, unspent and unreserved; the application has inventory permission.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTUy",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTUy",
  "status": "success",
  "result": {
    "totalOutputs": 1,
    "outputs": [
      {
        "outpoint": "d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7a.0",
        "satoshis": 1000,
        "lockingScript": "00596d4c60a263657874a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea1687265766973696f6e0063726571a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea266706172616d73f66776657273696f6e0175ab76a914fc63218003683dc3305d0644a52f73eb0802e5c188ac",
        "spendable": false,
        "tags": [
          "color_state_restricted"
        ]
      }
    ]
  }
}
```

The actual outpoint and script are returned. The profile establishes a signing role and state-management capability, not eligibility for automatic fungible selection.

### A.153. Restricted resources are excluded from available balances

Use the same single-resource state as A.152, with balance-query permission.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTUz",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7av0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTUz",
  "status": "success",
  "result": {
    "totalOutputs": "0",
    "outputs": []
  }
}
```

Nine units remain discoverable in restricted custody, but none is available for automatic spending. No amount is lost, rounded or silently credited as unrestricted.

### A.154. Caller profile data cannot introduce a requirement or grant

Use fixture C. The request adds profileData for an identifier absent from its deploy requirements.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU0",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 53, 44, 49, 53, 52, 44, 49, 57, 55, 44, 50, 49, 55, 44, 50, 52, 57, 44, 49, 48, 53, 44, 55, 55, 44, 49, 53, 52, 44, 52, 52, 44, 51, 57, 44, 52, 56, 44, 50, 48, 56, 44, 55, 57, 44, 55, 56, 44, 54, 54, 44, 53, 51, 44, 50, 51, 49, 44, 49, 49, 56, 44, 50, 49, 44, 50, 48, 44, 49, 53, 56, 44, 49, 49, 53, 44, 57, 56, 44, 49, 48, 52, 44, 49, 49, 52, 44, 50, 50, 56, 44, 49, 57, 52, 44, 50, 49, 50, 44, 49, 51, 55, 44, 53, 54, 44, 49, 54, 54, 44, 49, 49, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 49, 44, 51, 44, 50, 48, 54, 44, 49, 51, 53, 44, 49, 44, 49, 54, 44, 49, 49, 50, 44, 49, 49, 52, 44, 49, 49, 49, 44, 49, 48, 50, 44, 49, 48, 53, 44, 49, 48, 56, 44, 49, 48, 49, 44, 52, 53, 44, 57, 57, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 49, 54, 44, 49, 49, 52, 44, 57, 55, 44, 57, 57, 44, 49, 49, 54, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 44, 50, 51, 50, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 50, 56, 44, 48, 44, 56, 57, 44, 49, 48, 57, 44, 55, 54, 44, 57, 54, 44, 49, 54, 50, 44, 57, 57, 44, 49, 48, 49, 44, 49, 50, 48, 44, 49, 49, 54, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 49, 44, 49, 48, 52, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 48, 44, 57, 57, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 51, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 50, 44, 49, 48, 50, 44, 49, 49, 50, 44, 57, 55, 44, 49, 49, 52, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 53, 44, 50, 52, 54, 44, 49, 48, 51, 44, 49, 49, 56, 44, 49, 48, 49, 44, 49, 49, 52, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 44, 49, 49, 55, 44, 49, 55, 49, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 53, 50, 44, 57, 57, 44, 51, 51, 44, 49, 50, 56, 44, 51, 44, 49, 48, 52, 44, 54, 49, 44, 49, 57, 53, 44, 52, 56, 44, 57, 51, 44, 54, 44, 54, 56, 44, 49, 54, 53, 44, 52, 55, 44, 49, 49, 53, 44, 50, 51, 53, 44, 56, 44, 50, 44, 50, 50, 57, 44, 49, 57, 51, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 49, 50, 50, 44, 49, 57, 48, 44, 50, 49, 55, 44, 51, 54, 44, 49, 55, 49, 44, 51, 56, 44, 49, 55, 44, 49, 49, 49, 44, 50, 50, 55, 44, 50, 51, 48, 44, 51, 49, 44, 53, 55, 44, 49, 55, 53, 44, 49, 49, 54, 44, 50, 49, 52, 44, 56, 54, 44, 49, 49, 54, 44, 56, 56, 44, 53, 54, 44, 52, 54, 44, 49, 53, 48, 44, 50, 52, 57, 44, 49, 50, 56, 44, 55, 54, 44, 49, 48, 50, 44, 50, 44, 57, 52, 44, 49, 53, 53, 44, 49, 49, 48, 44, 50, 51, 54, 44, 55, 50, 44, 50, 49, 53, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 44, 50, 50, 50, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 48, 56, 44, 51, 50, 44, 49, 50, 50, 44, 49, 57, 48, 44, 50, 49, 55, 44, 51, 54, 44, 49, 55, 49, 44, 51, 56, 44, 49, 55, 44, 49, 49, 49, 44, 50, 50, 55, 44, 50, 51, 48, 44, 51, 49, 44, 53, 55, 44, 49, 55, 53, 44, 49, 49, 54, 44, 50, 49, 52, 44, 56, 54, 44, 49, 49, 54, 44, 56, 56, 44, 53, 54, 44, 52, 54, 44, 49, 53, 48, 44, 50, 52, 57, 44, 49, 50, 56, 44, 55, 54, 44, 49, 48, 50, 44, 50, 44, 57, 52, 44, 49, 53, 53, 44, 49, 49, 48, 44, 50, 51, 54, 44, 55, 50, 44, 50, 49, 53, 44, 56, 57, 44, 49, 48, 57, 44, 52, 53, 44, 49, 54, 49, 44, 57, 57, 44, 49, 48, 49, 44, 49, 50, 48, 44, 49, 49, 54, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 49, 44, 49, 48, 52, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 44, 49, 49, 55, 44, 49, 55, 49, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 52, 54, 44, 49, 49, 54, 44, 50, 51, 55, 44, 49, 56, 57, 44, 49, 51, 50, 44, 49, 56, 51, 44, 49, 57, 55, 44, 56, 55, 44, 49, 54, 52, 44, 50, 49, 56, 44, 50, 49, 50, 44, 50, 56, 44, 48, 44, 49, 48, 55, 44, 50, 49, 54, 44, 49, 52, 56, 44, 55, 51, 44, 50, 52, 51, 44, 49, 54, 50, 44, 50, 49, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 100, 55, 52, 56, 101, 99, 54, 101, 57, 98, 53, 101, 48, 50, 54, 54, 52, 99, 56, 48, 102, 57, 57, 54, 50, 101, 51, 56, 53, 56, 55, 52, 53, 54, 100, 54, 55, 52, 97, 102, 51, 57, 49, 102, 101, 54, 101, 51, 54, 102, 49, 49, 50, 54, 97, 98, 50, 52, 100, 57, 98, 101, 55, 97, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 70, 81, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 44, 34, 112, 114, 111, 102, 105, 108, 101, 68, 97, 116, 97, 34, 58, 123, 34, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 117, 110, 107, 110, 111, 119, 110, 34, 58, 123, 34, 100, 55, 52, 56, 101, 99, 54, 101, 57, 98, 53, 101, 48, 50, 54, 54, 52, 99, 56, 48, 102, 57, 57, 54, 50, 101, 51, 56, 53, 56, 55, 52, 53, 54, 100, 54, 55, 52, 97, 102, 51, 57, 49, 102, 101, 54, 101, 51, 54, 102, 49, 49, 50, 54, 97, 98, 50, 52, 100, 57, 98, 101, 55, 97, 95, 48, 34, 58, 123, 34, 103, 114, 97, 110, 116, 34, 58, 116, 114, 117, 101, 125, 125, 125, 125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU0",
  "status": "error",
  "code": 1,
  "description": "Profile data names an undeclared profile"
}
```

The optional transport is not a policy-selection mechanism. Even a truthy grant member cannot establish user consent or install a handler.

### A.155. A no-overlay declaration is not a validation bypass

Use fixture N. This wallet has no implementation of the declared alternative-evidence profile.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU1",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 34495df365e76d8fa01596ca8348126e0c60cbb898c5162670a8e7d670ad0f3ev0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU1",
  "status": "error",
  "code": 1,
  "description": "Required processing profile is unsupported"
}
```

The wallet does not interpret a name as permission to skip evidence. A later supported profile must define the replacement checks and preserve the common guarantees.

### A.156. Legacy JSON cannot declare processing profiles

Use fixture L. Its BRC-161 deploy includes an extra colorPayload JSON member, which is not a supported extension carrier. Its nine units, Bitcoin evidence and baseline overlay checks are valid; Alice allows the balance query.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU2",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance 6e983e939820a9267f7ae63ac549577292781c5e1904e6c358205d5bee90a8a7v0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU2",
  "status": "success",
  "result": {
    "totalOutputs": "9",
    "outputs": []
  }
}
```

The extra JSON member is ignored, so no required profile was declared. Ordinary BRC-161 accounting and admission apply. Converting later outputs to binary cannot add profiles to this issuance; that functionality requires a new BRC-162 deployment. Conversely, encoding an asset from a binary deploy as JSON cannot remove its inherited requirements.

### A.157. Declared profile data is checked rather than trusted

Use fixture C. Alice has the resource and fee grants from A.149. The example profile permits exactly an expectedRevision integer in the asset-scoped profileData leaf and checks it against the authenticated successor state.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU3",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color issue 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123, 34, 118, 101, 114, 115, 105, 111, 110, 34, 58, 49, 44, 34, 116, 120, 34, 58, 91, 49, 44, 49, 44, 49, 44, 49, 44, 55, 53, 44, 49, 53, 52, 44, 49, 57, 55, 44, 50, 49, 55, 44, 50, 52, 57, 44, 49, 48, 53, 44, 55, 55, 44, 49, 53, 52, 44, 52, 52, 44, 51, 57, 44, 52, 56, 44, 50, 48, 56, 44, 55, 57, 44, 55, 56, 44, 54, 54, 44, 53, 51, 44, 50, 51, 49, 44, 49, 49, 56, 44, 50, 49, 44, 50, 48, 44, 49, 53, 56, 44, 49, 49, 53, 44, 57, 56, 44, 49, 48, 52, 44, 49, 49, 52, 44, 50, 50, 56, 44, 49, 57, 52, 44, 50, 49, 50, 44, 49, 51, 55, 44, 53, 54, 44, 49, 54, 54, 44, 49, 49, 44, 50, 44, 48, 44, 49, 57, 48, 44, 50, 51, 57, 44, 48, 44, 50, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 49, 44, 51, 44, 50, 48, 54, 44, 49, 51, 53, 44, 49, 44, 49, 54, 44, 49, 49, 50, 44, 49, 49, 52, 44, 49, 49, 49, 44, 49, 48, 50, 44, 49, 48, 53, 44, 49, 48, 56, 44, 49, 48, 49, 44, 52, 53, 44, 57, 57, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 49, 54, 44, 49, 49, 52, 44, 57, 55, 44, 57, 57, 44, 49, 49, 54, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 44, 50, 51, 50, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 50, 56, 44, 48, 44, 56, 57, 44, 49, 48, 57, 44, 55, 54, 44, 57, 54, 44, 49, 54, 50, 44, 57, 57, 44, 49, 48, 49, 44, 49, 50, 48, 44, 49, 49, 54, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 49, 44, 49, 48, 52, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 48, 44, 57, 57, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 51, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 50, 44, 49, 48, 50, 44, 49, 49, 50, 44, 57, 55, 44, 49, 49, 52, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 53, 44, 50, 52, 54, 44, 49, 48, 51, 44, 49, 49, 56, 44, 49, 48, 49, 44, 49, 49, 52, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 44, 49, 49, 55, 44, 49, 55, 49, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 53, 50, 44, 57, 57, 44, 51, 51, 44, 49, 50, 56, 44, 51, 44, 49, 48, 52, 44, 54, 49, 44, 49, 57, 53, 44, 52, 56, 44, 57, 51, 44, 54, 44, 54, 56, 44, 49, 54, 53, 44, 52, 55, 44, 49, 49, 53, 44, 50, 51, 53, 44, 56, 44, 50, 44, 50, 50, 57, 44, 49, 57, 51, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 44, 48, 44, 48, 44, 48, 44, 49, 44, 49, 50, 50, 44, 49, 57, 48, 44, 50, 49, 55, 44, 51, 54, 44, 49, 55, 49, 44, 51, 56, 44, 49, 55, 44, 49, 49, 49, 44, 50, 50, 55, 44, 50, 51, 48, 44, 51, 49, 44, 53, 55, 44, 49, 55, 53, 44, 49, 49, 54, 44, 50, 49, 52, 44, 56, 54, 44, 49, 49, 54, 44, 56, 56, 44, 53, 54, 44, 52, 54, 44, 49, 53, 48, 44, 50, 52, 57, 44, 49, 50, 56, 44, 55, 54, 44, 49, 48, 50, 44, 50, 44, 57, 52, 44, 49, 53, 53, 44, 49, 49, 48, 44, 50, 51, 54, 44, 55, 50, 44, 50, 49, 53, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 50, 53, 53, 44, 49, 44, 50, 50, 50, 44, 51, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 48, 44, 49, 48, 56, 44, 51, 50, 44, 49, 50, 50, 44, 49, 57, 48, 44, 50, 49, 55, 44, 51, 54, 44, 49, 55, 49, 44, 51, 56, 44, 49, 55, 44, 49, 49, 49, 44, 50, 50, 55, 44, 50, 51, 48, 44, 51, 49, 44, 53, 55, 44, 49, 55, 53, 44, 49, 49, 54, 44, 50, 49, 52, 44, 56, 54, 44, 49, 49, 54, 44, 56, 56, 44, 53, 54, 44, 52, 54, 44, 49, 53, 48, 44, 50, 52, 57, 44, 49, 50, 56, 44, 55, 54, 44, 49, 48, 50, 44, 50, 44, 57, 52, 44, 49, 53, 53, 44, 49, 49, 48, 44, 50, 51, 54, 44, 55, 50, 44, 50, 49, 53, 44, 56, 57, 44, 49, 48, 57, 44, 52, 53, 44, 49, 54, 49, 44, 57, 57, 44, 49, 48, 49, 44, 49, 50, 48, 44, 49, 49, 54, 44, 49, 54, 49, 44, 49, 50, 48, 44, 50, 54, 44, 49, 49, 55, 44, 49, 49, 52, 44, 49, 49, 48, 44, 53, 56, 44, 49, 48, 49, 44, 49, 50, 48, 44, 57, 55, 44, 49, 48, 57, 44, 49, 49, 50, 44, 49, 48, 56, 44, 49, 48, 49, 44, 53, 56, 44, 57, 57, 44, 49, 49, 49, 44, 49, 48, 56, 44, 49, 49, 49, 44, 49, 49, 52, 44, 53, 56, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 54, 49, 44, 49, 48, 52, 44, 49, 49, 52, 44, 49, 48, 49, 44, 49, 49, 56, 44, 49, 48, 53, 44, 49, 49, 53, 44, 49, 48, 53, 44, 49, 49, 49, 44, 49, 49, 48, 44, 49, 44, 49, 49, 55, 44, 49, 55, 49, 44, 49, 49, 56, 44, 49, 54, 57, 44, 50, 48, 44, 50, 52, 54, 44, 49, 49, 54, 44, 50, 51, 55, 44, 49, 56, 57, 44, 49, 51, 50, 44, 49, 56, 51, 44, 49, 57, 55, 44, 56, 55, 44, 49, 54, 52, 44, 50, 49, 56, 44, 50, 49, 50, 44, 50, 56, 44, 48, 44, 49, 48, 55, 44, 50, 49, 54, 44, 49, 52, 56, 44, 55, 51, 44, 50, 52, 51, 44, 49, 54, 50, 44, 50, 49, 56, 44, 49, 51, 54, 44, 49, 55, 50, 44, 48, 44, 48, 44, 48, 44, 48, 93, 44, 34, 105, 110, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 115, 105, 103, 104, 97, 115, 104, 84, 121, 112, 101, 34, 58, 54, 53, 44, 34, 111, 119, 110, 101, 100, 79, 117, 116, 112, 117, 116, 115, 34, 58, 91, 123, 34, 111, 117, 116, 112, 117, 116, 73, 110, 100, 101, 120, 34, 58, 48, 44, 34, 97, 115, 115, 101, 116, 73, 100, 34, 58, 34, 100, 55, 52, 56, 101, 99, 54, 101, 57, 98, 53, 101, 48, 50, 54, 54, 52, 99, 56, 48, 102, 57, 57, 54, 50, 101, 51, 56, 53, 56, 55, 52, 53, 54, 100, 54, 55, 52, 97, 102, 51, 57, 49, 102, 101, 54, 101, 51, 54, 102, 49, 49, 50, 54, 97, 98, 50, 52, 100, 57, 98, 101, 55, 97, 95, 48, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 80, 114, 101, 102, 105, 120, 34, 58, 34, 70, 81, 61, 61, 34, 44, 34, 100, 101, 114, 105, 118, 97, 116, 105, 111, 110, 83, 117, 102, 102, 105, 120, 34, 58, 34, 65, 103, 61, 61, 34, 44, 34, 115, 101, 110, 100, 101, 114, 73, 100, 101, 110, 116, 105, 116, 121, 75, 101, 121, 34, 58, 34, 48, 50, 55, 57, 98, 101, 54, 54, 55, 101, 102, 57, 100, 99, 98, 98, 97, 99, 53, 53, 97, 48, 54, 50, 57, 53, 99, 101, 56, 55, 48, 98, 48, 55, 48, 50, 57, 98, 102, 99, 100, 98, 50, 100, 99, 101, 50, 56, 100, 57, 53, 57, 102, 50, 56, 49, 53, 98, 49, 54, 102, 56, 49, 55, 57, 56, 34, 125, 93, 44, 34, 112, 114, 111, 102, 105, 108, 101, 68, 97, 116, 97, 34, 58, 123, 34, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 34, 58, 123, 34, 100, 55, 52, 56, 101, 99, 54, 101, 57, 98, 53, 101, 48, 50, 54, 54, 52, 99, 56, 48, 102, 57, 57, 54, 50, 101, 51, 56, 53, 56, 55, 52, 53, 54, 100, 54, 55, 52, 97, 102, 51, 57, 49, 102, 101, 54, 101, 51, 54, 102, 49, 49, 50, 54, 97, 98, 50, 52, 100, 57, 98, 101, 55, 97, 95, 48, 34, 58, 123, 34, 101, 120, 112, 101, 99, 116, 101, 100, 82, 101, 118, 105, 115, 105, 111, 110, 34, 58, 49, 125, 125, 125, 125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU3",
  "status": "success",
  "result": {
    "signature": [48, 68, 2, 32, 18, 141, 103, 118, 82, 44, 147, 74, 246, 188, 147, 75, 79, 235, 253, 172, 28, 204, 48, 227, 223, 194, 223, 90, 49, 15, 86, 196, 251, 99, 152, 72, 2, 32, 41, 240, 108, 38, 113, 190, 185, 103, 108, 42, 140, 195, 206, 188, 135, 187, 81, 146, 216, 184, 184, 149, 218, 49, 98, 235, 31, 211, 105, 170, 57, 54]
  }
}
```

The declared revision equals the validated output revision. The witness neither changes the signed transaction nor replaces source-history validation. A value of 2, duplicate member or additional field must fail the profile schema/transition check.

### A.158. Required profile data in receipt instructions

Receive the fully signed successor from A.149. The wallet verifies the complete input script, authenticated transition from revision 0 to 1, current admission of the received output, signing role and receipt authorization.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU4",
  "call": "internalizeAction",
  "args": {
    "tx": [1, 1, 1, 1, 18, 161, 14, 180, 196, 17, 140, 68, 139, 59, 6, 186, 242, 244, 220, 197, 60, 46, 43, 152, 240, 209, 85, 18, 208, 70, 73, 121, 18, 104, 116, 250, 2, 0, 190, 239, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 21, 3, 206, 135, 1, 16, 112, 114, 111, 102, 105, 108, 101, 45, 99, 111, 110, 116, 114, 97, 99, 116, 255, 255, 255, 255, 1, 232, 3, 0, 0, 0, 0, 0, 0, 128, 0, 89, 109, 76, 96, 162, 99, 101, 120, 116, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 161, 104, 114, 101, 118, 105, 115, 105, 111, 110, 0, 99, 114, 101, 113, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 162, 102, 112, 97, 114, 97, 109, 115, 246, 103, 118, 101, 114, 115, 105, 111, 110, 1, 117, 171, 118, 169, 20, 252, 99, 33, 128, 3, 104, 61, 195, 48, 93, 6, 68, 165, 47, 115, 235, 8, 2, 229, 193, 136, 172, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 122, 190, 217, 36, 171, 38, 17, 111, 227, 230, 31, 57, 175, 116, 214, 86, 116, 88, 56, 46, 150, 249, 128, 76, 102, 2, 94, 155, 110, 236, 72, 215, 0, 0, 0, 0, 106, 71, 48, 68, 2, 32, 18, 141, 103, 118, 82, 44, 147, 74, 246, 188, 147, 75, 79, 235, 253, 172, 28, 204, 48, 227, 223, 194, 223, 90, 49, 15, 86, 196, 251, 99, 152, 72, 2, 32, 41, 240, 108, 38, 113, 190, 185, 103, 108, 42, 140, 195, 206, 188, 135, 187, 81, 146, 216, 184, 184, 149, 218, 49, 98, 235, 31, 211, 105, 170, 57, 54, 65, 33, 2, 196, 8, 48, 116, 2, 150, 192, 67, 144, 66, 149, 220, 69, 85, 98, 12, 72, 32, 79, 237, 228, 124, 242, 173, 106, 214, 252, 155, 145, 2, 83, 228, 255, 255, 255, 255, 1, 222, 3, 0, 0, 0, 0, 0, 0, 108, 32, 122, 190, 217, 36, 171, 38, 17, 111, 227, 230, 31, 57, 175, 116, 214, 86, 116, 88, 56, 46, 150, 249, 128, 76, 102, 2, 94, 155, 110, 236, 72, 215, 89, 109, 45, 161, 99, 101, 120, 116, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 161, 104, 114, 101, 118, 105, 115, 105, 111, 110, 1, 117, 171, 118, 169, 20, 246, 116, 237, 189, 132, 183, 197, 87, 164, 218, 212, 28, 0, 107, 216, 148, 73, 243, 162, 218, 136, 172, 0, 0, 0, 0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7av0",
          "customInstructions": "{\"version\":1,\"assetId\":\"d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7a_0\",\"derivationPrefix\":\"FQ==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"profileData\":{\"urn:example:color:revision\":{\"d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7a_0\":{\"expectedRevision\":1}}}}"
        }
      }
    ]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU4",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

The 372-byte instruction string remains within the existing 1000-byte limit. The known profile validates expectedRevision against the received state; it is not an override. The wallet records the nine-unit resource as restricted, keeps its authenticated successor derivation and credits no available fungible balance. Unknown profile identifiers or mismatched state must fail. No implicit overlay submission occurs.

### A.159. Required profile data in an action plan

Use fixture C. Alice grants the resource update and ten-satoshi fee from A.149. The application explicitly selects the restricted resource and will provide its unlocking script later.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "Y29sb3ItMDAwMTU5",
  "call": "createAction",
  "args": {
    "description": "Advance the authorized resource revision",
    "inputBEEF": [2, 0, 190, 239, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 21, 3, 206, 135, 1, 16, 112, 114, 111, 102, 105, 108, 101, 45, 99, 111, 110, 116, 114, 97, 99, 116, 255, 255, 255, 255, 1, 232, 3, 0, 0, 0, 0, 0, 0, 128, 0, 89, 109, 76, 96, 162, 99, 101, 120, 116, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 161, 104, 114, 101, 118, 105, 115, 105, 111, 110, 0, 99, 114, 101, 113, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 162, 102, 112, 97, 114, 97, 109, 115, 246, 103, 118, 101, 114, 115, 105, 111, 110, 1, 117, 171, 118, 169, 20, 252, 99, 33, 128, 3, 104, 61, 195, 48, 93, 6, 68, 165, 47, 115, 235, 8, 2, 229, 193, 136, 172, 0, 0, 0, 0],
    "inputs": [
      {
        "outpoint": "d748ec6e9b5e02664c80f9962e38587456d674af391fe6e36f1126ab24d9be7a.0",
        "unlockingScriptLength": 108,
        "inputDescription": "Update resource revision zero to one"
      }
    ],
    "outputs": [
      {
        "lockingScript": "207abed924ab26116fe3e61f39af74d6567458382e96f9804c66025e9b6eec48d7596d2da163657874a1781a75726e3a6578616d706c653a636f6c6f723a7265766973696f6ea1687265766973696f6e0175ab76a914f674edbd84b7c557a4dad41c006bd89449f3a2da88ac",
        "satoshis": 990,
        "outputDescription": "Preserve the resource under revision one"
      }
    ],
    "labels": [
      "p color plan 0 7 7b2276657273696f6e223a312c22617373657473223a5b22643734386563366539623565303236363463383066393936326533383538373435366436373461663339316665366533366631313236616232346439626537615f30225d2c226f776e65644f",
      "p color plan 1 7 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22643734386563366539623565303236363463383066393936326533383538373435366436373461663339316665366533366631313236616232346439626537",
      "p color plan 2 7 615f30222c2264657269766174696f6e507265666978223a2246513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 7 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226437343865633665396235653032363634633830663939363265",
      "p color plan 4 7 33383538373435366436373461663339316665366533366631313236616232346439626537615f30222c226f7065726174696f6e223a227472616e73666572227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 7 6f6e73223a5b5d2c22736967686173685479706573223a5b5d2c2270726f66696c6544617461223a7b2275726e3a6578616d706c653a636f6c6f723a7265766973696f6e223a7b2264373438656336653962356530323636346338306639393632653338",
      "p color plan 6 7 3538373435366436373461663339316665366533366631313236616232346439626537615f30223a7b2265787065637465645265766973696f6e223a317d7d7d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "signAndProcess": false,
      "noSend": true,
      "randomizeOutputs": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "Y29sb3ItMDAwMTU5",
  "status": "success",
  "result": {
    "signableTransaction": {
      "tx": [1, 1, 1, 1, 75, 154, 197, 217, 249, 105, 77, 154, 44, 39, 48, 208, 79, 78, 66, 53, 231, 118, 21, 20, 158, 115, 98, 104, 114, 228, 194, 212, 137, 56, 166, 11, 2, 0, 190, 239, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 21, 3, 206, 135, 1, 16, 112, 114, 111, 102, 105, 108, 101, 45, 99, 111, 110, 116, 114, 97, 99, 116, 255, 255, 255, 255, 1, 232, 3, 0, 0, 0, 0, 0, 0, 128, 0, 89, 109, 76, 96, 162, 99, 101, 120, 116, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 161, 104, 114, 101, 118, 105, 115, 105, 111, 110, 0, 99, 114, 101, 113, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 162, 102, 112, 97, 114, 97, 109, 115, 246, 103, 118, 101, 114, 115, 105, 111, 110, 1, 117, 171, 118, 169, 20, 252, 99, 33, 128, 3, 104, 61, 195, 48, 93, 6, 68, 165, 47, 115, 235, 8, 2, 229, 193, 136, 172, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 122, 190, 217, 36, 171, 38, 17, 111, 227, 230, 31, 57, 175, 116, 214, 86, 116, 88, 56, 46, 150, 249, 128, 76, 102, 2, 94, 155, 110, 236, 72, 215, 0, 0, 0, 0, 0, 255, 255, 255, 255, 1, 222, 3, 0, 0, 0, 0, 0, 0, 108, 32, 122, 190, 217, 36, 171, 38, 17, 111, 227, 230, 31, 57, 175, 116, 214, 86, 116, 88, 56, 46, 150, 249, 128, 76, 102, 2, 94, 155, 110, 236, 72, 215, 89, 109, 45, 161, 99, 101, 120, 116, 161, 120, 26, 117, 114, 110, 58, 101, 120, 97, 109, 112, 108, 101, 58, 99, 111, 108, 111, 114, 58, 114, 101, 118, 105, 115, 105, 111, 110, 161, 104, 114, 101, 118, 105, 115, 105, 111, 110, 1, 117, 171, 118, 169, 20, 246, 116, 237, 189, 132, 183, 197, 87, 164, 218, 212, 28, 0, 107, 216, 148, 73, 243, 162, 218, 136, 172, 0, 0, 0, 0],
      "reference": "cHJvZmlsZS0wMTU5"
    }
  }
}
```

The wallet reconstructs the plan, validates the profile evidence and successor state, preserves the requested position, and reserves the resource. Its explicit selection does not make it ordinary funding. The returned transaction is pending, with an empty input unlock and no spendable credit. Later signAction must revalidate the same requirements and actual unlocking script. The base transfer operation accounts for the nine units; profile authorization independently covers the state update.

## Appendix B — Exact transition and safety vectors

These vectors supplement Appendix A. Amounts are integer token units for one existing asset, with authenticated admitted inputs and individually valid encoded outputs. All non-token funding, scripts, source proofs and current topic checks pass unless a row says otherwise. A successful row requires exactly the listed operation set in its plan and consent to each positive quantity; an omitted or extra operation fails before signing. An invalid row fails before signing regardless of declared operations. An empty operation set does not authorize a token-bearing action without its exact `assets` entry.

### B.1. Mixed-encoding value accounting

`V`, `T`, `E`, `M` and `B` have the definitions in Transaction evidence and admission. `A` is the number of admitted authority inputs and outputs. `X/Y/Z` is the resulting `transferred/minted/burned` triple. Each row is a separate transaction; no value or authority carries between rows. `tr`, `mi`, `bu` and `at` abbreviate `transfer`, `mint`, `burn` and `authority-transfer` only within this table.

| Case                                      |                    V |   T |   E |   M |                    B | A in→out | X/Y/Z                    | Required operations or result                                                                        |
| ----------------------------------------- | -------------------: | --: | --: | --: | -------------------: | -------- | ------------------------ | ---------------------------------------------------------------------------------------------------- |
| B01 exact binary transfer                 |                  100 |   0 |   0 |   0 |                  100 | 0→0      | 100/0/0                  | tr                                                                                                   |
| B02 implicit binary burn                  |                  100 |   0 |   0 |   0 |                   90 | 0→0      | 90/0/10                  | tr, bu                                                                                               |
| B03 no-authority binary excess            |                  100 |   0 |   0 |   0 |                  101 | 0→0      | —                        | Reject; binary outputs lack coverage even though inputs would be destroyed by an invalid transition. |
| B04 legacy plus binary transfer           |                  100 |  40 |   0 |   0 |                   60 | 0→0      | 100/0/0                  | tr                                                                                                   |
| B05 mixed over-output                     |                  100 |  70 |   0 |   0 |                   40 | 0→0      | —                        | Reject; remaining value covers only 30 binary units.                                                 |
| B06 explicit plus implicit burn           |                  100 |  20 |  10 |   0 |                   60 | 0→0      | 80/0/20                  | tr, bu                                                                                               |
| B07 explicit burn exhausts remainder      |                  100 |  20 |  10 |   0 |                   80 | 0→0      | —                        | Reject; binary amount exceeds the remaining 70.                                                      |
| B08 authority mints binary only           |                    0 |   0 |   0 |   0 |                  250 | 1→1      | 0/250/0                  | mi, at                                                                                               |
| B09 authority with value burn             |                  100 |   0 |   0 |   0 |                   90 | 1→1      | 90/0/10                  | tr, bu, at; authority does not erase the burn.                                                       |
| B10 authority with exact value coverage   |                  100 |   0 |   0 |   0 |                  100 | 1→1      | 100/0/0                  | tr, at; `mint` is extra and fails.                                                                   |
| B11 authority with binary excess          |                  100 |   0 |   0 |   0 |                  140 | 1→1      | 100/40/0                 | tr, mi, at                                                                                           |
| B12 mixed, simultaneous mint and burn     |                  100 |  40 |  10 |   0 |                   70 | 1→1      | 90/20/10                 | tr, mi, bu, at; net supply rises 10.                                                                 |
| B13 mixed, remaining value burned         |                  100 |  40 |  10 |   0 |                   30 | 1→1      | 70/0/30                  | tr, bu, at                                                                                           |
| B14 legacy mint plus mixed transfer       |                  100 |  40 |  10 |  30 |                   50 | 1→1      | 90/30/10                 | tr, mi, bu, at                                                                                       |
| B15 equal legacy mint and burn            |                  100 |   0 | 100 | 100 |                    0 | 1→0      | 0/100/100                | mi, bu, authority-end; net zero does not cancel either consent.                                      |
| B16 authority cannot fund legacy transfer |                    0 |   1 |   0 | 100 |                    0 | 1→1      | —                        | Reject; `T + E > V` despite authority.                                                               |
| B17 legacy mint only                      |                    0 |   0 |   0 | 100 |                    0 | 1→1      | 0/100/0                  | mi, at; no `transfer`.                                                                               |
| B18 legacy transfer plus binary mint      |                  100 | 100 |   0 |   0 |                  100 | 1→1      | 100/100/0                | tr, mi, at; the same value input cannot fund both outputs.                                           |
| B19 authority-free legacy mint            |                    0 |   0 |   0 |   1 |                    0 | 0→0      | —                        | Reject; `mint` requires an admitted authority input.                                                 |
| B20 wide aggregate                        | 36893488147419103230 |   0 |   0 |   0 | 36893488147419103230 | 0→0      | 36893488147419103230/0/0 | tr; two maximum-uint64 inputs and outputs, summed exactly without uint64 overflow.                   |

For B20 each input and output amount is 18446744073709551615; the table totals deliberately exceed uint64. A single encoded output above uint64 remains invalid. If B16's legacy mint is independently admissible under BRC-161, that does not make its legacy transfer valid: the wallet still rejects the action because the intended transition is not wholly admitted.

### B.2. Authority and operation-set boundaries

All rows have a single existing asset, no value amounts, valid authority scripts and separately verified controller consent. The operation is in addition to any positive value operation from B.1; count changes never turn authority into numeric value.

| Case                      | Admitted authority inputs | Authority outputs | Required result                                             |
| ------------------------- | ------------------------: | ----------------: | ----------------------------------------------------------- |
| B21 end one               |                         1 |                 0 | `authority-end`                                             |
| B22 transfer one          |                         1 |                 1 | `authority-transfer`, even if the controller changes        |
| B23 split one             |                         1 |                 2 | `authority-split`                                           |
| B24 end several           |                         2 |                 0 | `authority-end`                                             |
| B25 combine               |                         2 |                 1 | `authority-combine`                                         |
| B26 same count            |                         2 |                 2 | `authority-transfer`, including swaps of controllers        |
| B27 net split             |                         2 |                 3 | `authority-split`                                           |
| B28 unauthorized creation |                         0 |                 1 | Reject; no authority input                                  |
| B29 absent capability     |                         0 |                 0 | No authority operation; an asserted one is extra and fails. |

A fixed or authority deploy is instead its new asset's sole deploy operation. The BRC-162 deploy must occupy output zero. Its genesis amount or authority is not charged as an existing-asset transfer or mint. A BRC-161 deploy follows that encoding's own allowed position. A binary prefix takes precedence over an embedded legacy inscription; its legacy text cannot add a second amount or operation. Assets are evaluated independently: a surplus of asset A cannot cover a deficit of asset B.

### B.3. Signature commitment and provider boundaries

The signature rows assume consensus-valid source scripts and otherwise authorized unilateral disposition. `nIn` and `nOut` are transaction input and output counts. Both direct signing and managed-action signing MUST produce the same accept/reject decision for the stated context. A rejection releases no signature or pending signature capability.

| Case                                     | Flag   | Signing input | nIn/nOut | Required result                                                                                                                                   |
| ---------------------------------------- | ------ | ------------: | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| B30 BIP143 SINGLE matched                | `0x43` |             0 | 1/1      | May sign; commits output zero after ordinary validation.                                                                                          |
| B31 BIP143 SINGLE absent                 | `0x43` |             1 | 2/1      | Reject; no corresponding output, even if that digest algorithm uses zero `hashOutputs`.                                                           |
| B32 BIP143 SINGLE/ANYONECANPAY absent    | `0xc3` |             1 | 2/1      | Reject; ANYONECANPAY does not cure the missing output.                                                                                            |
| B33 Chronicle SINGLE matched             | `0x63` |             0 | 1/1      | May sign using the original transaction digest algorithm, not BIP143 fields.                                                                      |
| B34 Chronicle SINGLE absent              | `0x63` |             1 | 2/1      | Reject; the original algorithm's missing-output result is the constant uint256 value 1.                                                           |
| B35 Chronicle SINGLE/ANYONECANPAY absent | `0xe3` |             1 | 2/1      | Reject for the same reason; no outpoint or output is committed by the degenerate digest.                                                          |
| B36 NONE with no outputs committed       | `0x42` |             0 | 1/1      | May sign only if its actual unilateral consequences or a verified safeguard satisfy authorization; the output shown in a subject is not promised. |
| B37 ALL/ANYONECANPAY conditional swap    | `0xc1` |             0 | 2/2      | Reject when a named foreign input is required consideration but uncommitted and no independently verified remedy applies.                         |
| B38 ALL/ANYONECANPAY with remedy         | `0xc1` |             0 | 2/2      | May sign only after the wallet verifies an applicable remedy under the asset system's policy; the remedy is outside this BRC.                     |

For B31–B35 the subject and source may otherwise be valid; missing output coverage alone triggers rejection. The actual signed message is computed from the selected consensus algorithm, never from a caller-supplied digest. SIGHASH\_SINGLE's constant-one result is a consensus property, not a permissible wallet signature vector.

| Case                               | Evidence or policy condition                                                                              | Required result                                                                                                                                                                       |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| B39 no caller hint                 | Authenticated token source with no `inputTopics` member                                                   | Derive asset, topic and configured provider from the source; continue only if current admission passes.                                                                               |
| B40 caller topic hint              | Otherwise valid plan or signature envelope contains `inputTopics`                                         | Reject as an unknown version-1 member; do not let it select a topic.                                                                                                                  |
| B41 differing wallet policies      | Provider P admits the source; P is trusted by wallet A but wallet B requires other evidence               | A may proceed under its configuration; B fails closed until its own configured rule is satisfied. Neither wallet can deem the other's policy authoritative.                           |
| B42 issuer convention mismatch     | Source is admitted by one wallet, but intended counterparty requires a different asset-network convention | Do not promise counterparty acceptance; apply the wallet's asset and counterparty policy before release.                                                                              |
| B43 provider disagrees or is stale | Required current evidence is insufficient under this wallet's configuration                               | Reject before signature or broadcast; an old proof cannot substitute for current spendability.                                                                                        |
| B44 delayed actor                  | A later untrusted participant omits a required signature or changes an uncommitted output                 | No loss beyond the releasing user's unconditional authorized disposition, or a verified system-specific remedy must cover the conditional loss; otherwise reject the initial release. |
| B45 unrelated controller           | Alice releases a valid signature for her input; Bob's separately controlled input remains unsigned        | Alice's signature cannot spend Bob's input or invalidate Bob's independently admitted asset.                                                                                          |

These cases set wallet safety and evidence boundaries. Provider choice, facilitator functions, negotiation, participation authorization, acceptance conventions and remedies remain choices of the applicable asset ecosystem, issuer, wallet, application and users, rather than a universal rule imposed by this BRC.

## Appendix C — Attested and protected-chain conformance

### C.0. Scope and signature vectors

These cases use an authenticated binary deploy with required profile `https://brc.dev/207/attested`, version 1, and the stated parameters. All unstated Bitcoin, script, custody, consent and companion-profile checks succeed. The graph cases describe independently established fixture facts, not claims about live TXIDs. They supplement the baseline wire examples in Appendix A. The signature-only vector below does not provide deployment, transaction or SPV evidence and cannot by itself justify token acceptance.

The public test identity private integer is 5555. Never use it for funds.

| Item                       | Exact value                                                          |
| -------------------------- | -------------------------------------------------------------------- |
| Signer identity            | `02798b81951a6a8c7ef8beda09b06a23031d7efaf37b663265e0188ab378124046` |
| BRC-43 invoice             | `2-color admission attestation-1`                                    |
| BRC-42 HMAC tweak          | `6ef21748a4f6f5034842184e25f68e8df8663e036622ad9b3d061c6dd81ea93b`   |
| Derived public signing key | `033520aaa77b6914019796e974d1737acc890c5283c0921ddcfc2d5c815db8fb90` |
| SHA-256 message digest     | `9f61b417ec979c3c057313fe56f3c6056d2962b6bceaaa8272377bb9a80d8f9d`   |

The exact message, with no newline, is:

```
1color-admit:v1:tm_color_c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0:05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931:0,2
```

Its JSON attestation is:

```json
{
  "txid": "05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931",
  "outputs": [
    0,
    2
  ],
  "signer": "02798b81951a6a8c7ef8beda09b06a23031d7efaf37b663265e0188ab378124046",
  "signature": "3045022100d1f48fc13a1ffd18196bcb6166aff936b8dc6cb7b0866fe9226ef1db89fb06be02203eff5870ae5215047d1d624733b29f563d5037660ce5c4ed0ce4ac52cc56758a"
}
```

The following high-S encoding is equally valid for the same message and key:

```
3046022100d1f48fc13a1ffd18196bcb6166aff936b8dc6cb7b0866fe9226ef1db89fb06be022100c100a78f51adeafb82e29db8cc4d60a87d5ea580a262db4eb2edb23a03dfcbb7
```

Both verify with the derived child. Neither may be verified against the raw identity key. Reordering output indices, removing index 2, changing the topic, changing key ID to `2`, or adding a newline fails. Parsing rejects an unsorted output array even if a caller separately signs that noncanonical spelling. BRC-175's retained raw-key negative vector must fail this construction.

### C.1. Deployment, authority and complete-graph decisions

Let the user's selected BRC-189 identities be P and Q. R is a valid identity absent from that list. X and Y are distinct frontier outpoints of one asset; `P(X)` means a valid default attestation from P for X. Every case starts fresh.

| Case                              | Independently established condition                                                           | Required result                                                                                                                                          |
| --------------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| C01 default                       | `params:{}`, `P(X)`, no current overlay connectivity                                          | May accept under the user's offline policy; no live lookup is required.                                                                                  |
| C02 opt-in absent                 | Same evidence, deploy has no required profile                                                 | Baseline current admission remains required; a receipt absent from its overlay fails.                                                                    |
| C03 inherited declaration         | Later output omits `req`; deploy selects the profile                                          | Apply the immutable deploy policy.                                                                                                                       |
| C04 substituted declaration       | Later output adds the profile to an undeclared asset, or changes its mode                     | Reject; no downstream opt-in or policy substitution.                                                                                                     |
| C05 unsupported schema            | Unknown version, null params, unknown parameter, own `ext`, or malformed identity key         | Reject before signing or balance mutation.                                                                                                               |
| C06 distributed any               | `any`, `P(X)` and `Q(Y)`                                                                      | Both anchors covered; same signer is not required.                                                                                                       |
| C07 missing any                   | `any`, `P(X)` and only `R(Y)`                                                                 | Reject: Y has no locally trusted coverage.                                                                                                               |
| C08 empty list                    | `any`, `same` or `all`, no selected identities                                                | Reject; unanimity over an empty set is not acceptance.                                                                                                   |
| C09 same fails                    | `same`, `P(X)` and `Q(Y)`                                                                     | Reject: intersection across the complete frontier is empty.                                                                                              |
| C10 same succeeds                 | `same`, P covers X and Y, Q also covers X                                                     | Accept coverage; P is the common signer. Q's incomplete coverage does not defeat it.                                                                     |
| C11 all fails                     | `all`, P covers X and Y, Q covers only X                                                      | Reject: Q(Y) is missing.                                                                                                                                 |
| C12 all succeeds                  | `all`, P and Q each cover X and Y                                                             | Accept coverage; weights and certificate `trustLevel` are irrelevant.                                                                                    |
| C13 fixed identity                | `identity`, `identityKey:R`, R covers X and Y                                                 | Accept coverage even with an empty local list; no hidden membership requirement.                                                                         |
| C14 wrong fixed key               | `identityKey:R`, only P and Q cover the frontier                                              | Reject despite their local trust.                                                                                                                        |
| C15 no threshold inflation        | Two copies of P(X), no Q(X), `all`                                                            | Reject; signatures are deduplicated by identity/outpoint.                                                                                                |
| C16 concurrent trust change       | Remove P while an `any` authorization relying only on P is being committed                    | Reevaluate against a coherent current snapshot; no stale release/credit.                                                                                 |
| C17 later trust loss              | Remove P after an offline payment was accepted                                                | Restrict affected value for future decisions, retain history/exposure; no claim of retroactive signature cancellation.                                   |
| C18 immutable fixed trust         | R removed from the local list after acceptance under `identityKey:R`                          | Membership removal is irrelevant to this mode; user may still decline further use of the asset.                                                          |
| C19 diamond graph                 | Two valid branches consume different outputs of one shared ancestor                           | Validate/memoize that ancestor once; do not reject merely because it is revisited.                                                                       |
| C20 competing branches            | Two distinct transactions consume the same source outpoint                                    | Reject the conflicting graph.                                                                                                                            |
| C21 omitted branch                | An input or authority branch is missing, although visible quantities seem sufficient          | Reject; apparent numerical sufficiency cannot replace complete coverage.                                                                                 |
| C22 mined only                    | An unanchored token parent has a valid Bitcoin proof                                          | Token coverage fails; mined does not mean admitted.                                                                                                      |
| C23 attested only                 | All token branches have attestations, but required Bitcoin source/SPV evidence is missing     | Reject; attestation does not prove Bitcoin validity.                                                                                                     |
| C24 partial output list           | Attestation covers output 0; the graph consumes output 2                                      | Obtain separate qualifying coverage or validate its full ancestry; never count output 0's signature as output 2 coverage. A known rejection still fails. |
| C25 no quantity bypass            | Anchored inputs total 20 units, unsigned-authority-free descendant outputs total 21           | Reject the transition despite valid anchor signatures.                                                                                                   |
| C26 authority branch              | Mint transition has a separate authority ancestor                                             | Validate that branch and authority operation/consent, not just value ancestry.                                                                           |
| C27 accepted source consumed here | An anchor is spent by its exact validated descendant in the supplied graph                    | Normal ancestry; do not mistake it for an external conflict.                                                                                             |
| C28 known conflicting spend       | Independent verified evidence shows another transaction consumed a required outpoint          | Reject/reconcile affected descendants; cannot choose a favorable signer to hide it.                                                                      |
| C29 newly issued                  | Authorized ISSUE plan uses its empty evidence leaf                                            | May construct/sign; cannot credit or use it as an anchor until completed deploy attestation coverage exists.                                             |
| C30 deployment receipt            | Receipt uses ISSUE derivation tuple but resolved-ID evidence                                  | Preserve the original key; authenticate deploy and assign final basket. No transfer may claim ISSUE to omit history.                                     |
| C31 multi-asset swap              | A uses `any`, B uses `identity`, C uses baseline admission, plus satoshis                     | Independently validate all three policies and DSAP; disclose each leg and offline risk. A's coverage does not validate B or C.                           |
| C32 resource composition          | A is attested; separately deployed B selects BRC-209                                          | May compose under their independent rules. Declaring both modes for A itself fails.                                                                      |
| C33 incomplete signatures         | Foreign unlocking script remains outstanding                                                  | Pending two-phase action only; no completed receipt or available prospective credit.                                                                     |
| C34 non-final auction             | Fully signed subject still consensus-non-final                                                | Apply processor-coordination rules; no completed receipt. Offline attestation cannot waive them.                                                         |
| C35 companion requirements        | A selected companion needs identity or state proof absent from the bundle                     | Reject; default admission attestations do not silently discharge other requirements.                                                                     |
| C36 duplicate/malformed fields    | Duplicate JSON key, unknown carrier key, bad DER, invalid point or noncanonical TXID          | Reject before signature, persistence of credit or external submission.                                                                                   |
| C37 resource bounds               | Evidence exceeds the applicable 16 MiB receipt ceiling or declared host work limit            | Explicit failure; no truncation or partial balance. The 1000-byte baseline still applies to undeclared receipts.                                         |
| C38 alternate evidence            | `evidenceProfile` names an exact supported companion that replaces signature and trust checks | Use that companion's carrier and declared rules; no default-signature fallback.                                                                          |
| C39 invalid delegation            | Missing companion, cycle, unsupported version or incompatible replacements                    | Reject. A URI is not authorization to fetch/execute code.                                                                                                |

### C.2. Offline value and protected lifecycle decisions

| Case                            | Condition                                                                                  | Required result                                                                                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| C40 accepted offline            | Complete final graph, custody and risk authorization pass, no actual overlay admission yet | Persist receipt exactly once; available unreserved units may count, with `color_offline` and accurate deferred-settlement presentation. No implicit broadcast. |
| C41 reserve and extend          | Select that output into a signed child                                                     | Reserve parent; prospective child cannot cause a duplicate available balance. Forward all required ancestor, submission and lifecycle data.                    |
| C42 missing settlement data     | Companion requires off-chain values not carried or held authoritatively                    | Reject acceptance rather than depend on an unavailable sender for eventual settlement.                                                                         |
| C43 protected never             | Received graph includes a signed `never` lifecycle statement from its responsible wallet   | Verify it and apply counterparty policy; show permanent sender disposition, not Bitcoin finality or freedom from third-party double-spends.                    |
| C44 missing lifecycle           | Known protected root has no authenticated lifecycle object                                 | Fail protected delivery; version-1 BRC-175 bundle or display note cannot assert `never`.                                                                       |
| C45 forged or changed lifecycle | Root/anchor bytes do not match, signature is wrong, or a forwarded deadline is edited      | Reject. Child creators cannot reset a relative timer or rewrite an ancestor's policy.                                                                          |
| C46 finite mixed roots          | Chain depends on one timestamp root and one height root, neither accepted                  | Evaluate both predicates in their own units; expiration of either blocks affected descendants.                                                                 |
| C47 restart after release       | Local batch lease expires or sender loses its application                                  | Recover durable bytes/reservations/exposure. Never free usable signed inputs from lease expiry.                                                                |
| C48 explicit batch send         | `sendWith` names a retained leaf                                                           | Validate authorized ancestor closure before submission; process parents first or as an accepted package. Preserve ordinary per-requested-ID results.           |
| C49 known bad closure           | Missing parent or a finite root has expired before submission                              | Fail before publishing a ready subset. No implied all-or-nothing network guarantee after a valid attempt begins.                                               |
| C50 partial settlement          | Root accepted, a later ancestor or tip refused                                             | Cancel reclaim only for independently accepted roots; keep per-TXID outcomes and affected credits/reservations accurate. No rollback of published members.     |
| C51 uncertain result            | Submission response or receiver ack is lost                                                | Retain exposure; reconcile exact TXIDs and retry idempotently. Do not create a replacement payment.                                                            |
| C52 negative ack on never       | Receiver declines a released `never` payment                                               | Keep it permanently sent/committed; no reclamation or generic input reuse.                                                                                     |
| C53 delayed finite send         | Queued batch reaches deadline before first processing acceptance                           | Queueing did not satisfy expiry; reconcile and apply finite-root reclaim rules.                                                                                |
| C54 settled graph               | Required ancestors, every asset leg and actual current admission are verified              | Remove `color_offline` for eligible settled outputs; retain other restrictions/reservations.                                                                   |
| C55 final refusal               | Offline child or ancestor loses to a verified conflicting spend                            | Remove unavailable credit, mark dependent failures, retain evidence and released exposure; accounting correction does not recover delivered goods.             |
| C56 no authenticated age        | Wallet first saw an untimed default attestation yesterday                                  | May enforce a retention policy; cannot claim the attester signed yesterday. Authenticated-age rules need a declared replacement.                               |

### C.3. Capability and balance wire examples

The wallet has enabled compatible BRC-205 version metadata, implements the optional default profile and both BRC-175 bundle versions, and supports BRC-177 protected batches. An application requests:

```json
{"type":"CWI","isInvocation":true,"id":"b2ZmbGluZTAwMQ==","call":"getVersion","args":{}}
```

One conforming response is:

```json
{"type":"CWI","isInvocation":false,"id":"b2ZmbGluZTAwMQ==","status":"success","result":{"version":"example-1.2.3; brc207-color=4; brc207-color-attested=1; brc175-offline=2; brc177-nosend=2;"}}
```

This discloses no holdings and grants no permission. Omission is not proof of non-support; actual calls still enforce supported declarations and authorization.

In an independent balance fixture, the authenticated asset below has 40 unreserved settled units, 60 accepted unreserved offline units, and 25 reserved units. All are within the caller's read scope. The caller requests:

```json
{"type":"CWI","isInvocation":true,"id":"b2ZmbGluZTAwMg==","call":"listOutputs","args":{"basket":"p color balance c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0"}}
```

The exact result under BRC-112's module-balance encoding is:

```json
{"type":"CWI","isInvocation":false,"id":"b2ZmbGluZTAwMg==","status":"success","result":{"totalOutputs":"100","outputs":[]}}
```

The 25 reserved units are excluded. The wallet's user presentation distinguishes 40 settled from 60 deferred units; this scalar is spending capacity, not a statement that all 100 are settled. If policy reevaluation makes those 60 unavailable, the result becomes `"40"` and their inventory state becomes restricted. If the wallet cannot establish the balance at all, it returns an explicit error rather than a guessed sum.


# Vault Sub-Accounts

Brandon Cryderman (<brandongcryderman@gmail.com>)

## Abstract

This standard defines how a wallet keeps any number of **accounts** under one **vault master**, the single secret the user backs up. Each account is a complete [BRC-100](/wallet/0100) wallet with its own identity key, outputs, and history. How account roots derive from the master depends on what the master is:

* **Entropy masters** ([BRC-157](/key-derivation/0157)): account `n` is BRC-157 profile `m/0'/n'`. The master never signs. New wallets SHOULD use this derivation.
* **Root-key masters** (a [BRC-75](/key-derivation/0075) key, an imported key): account 0 is the root key, unchanged, and account `n ≥ 1` is its [BRC-42](/key-derivation/0042) child under the reserved protocol `[2, "account"]`, key ID `account-n`, counterparty `self`. Deployed wallets keep their coins and identities.

On top of either derivation this standard defines the account layer: sequential allocation, discovery, recovery from the master alone, local naming, the requests a wallet refuses, and how a restore tells the derivations apart.

## Motivation

People want separate on-chain identities for separate purposes, such as a personal wallet, a shop, an issuer and a website, without keeping a separate seed phrase for each one.

BRC-157 already derives any number of profile keys from one entropy backup, at hardened BIP-32 paths. A profile key reveals nothing about the master or about any other profile, and no BRC-100 request can reach a hardened path. That is the isolation a multi-account wallet wants. BRC-157 does not say how a wallet allocates, finds, or recovers the profiles it has used, so two wallets restoring the same backup can show different accounts.

Many deployed wallets also hold a single root key that is not BRC-157 entropy:

* a BRC-75 key, which is SHA-256 of the BIP-39 seed;
* a key recovered from an older HD wallet;
* a raw private key imported without any mnemonic.

These wallets have no BIP-32 master node, so they cannot derive BRC-157 profiles, and moving them to a new root would move every coin and identity they hold.

This standard gives both kinds of wallet the same account layer. It uses BRC-157 profiles wherever the master is entropy, and a BRC-42 derivation from the root key wherever it is not.

## Specification

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

### Vault master

The **vault master** is the one secret from which every account of a vault derives. It is either:

* **BRC-157 entropy**, recovered from its mnemonic or its [BRC-140](/key-derivation/0140) shares as BRC-157 specifies; or
* **a root key**: a secp256k1 private key whose public key was the wallet's BRC-100 identity key before any account existed. This standard does not constrain how the root key was produced.

Each vault has exactly one **derivation**, `profile` for entropy masters and `self-child` for root-key masters. A wallet MUST record the derivation with the vault and MUST NOT change it: switching derivations moves every account to new keys.

New wallets SHOULD generate BRC-157 entropy (32 bytes, 24 words) and use the `profile` derivation.

### Account roots

Every account root is a secp256k1 private key and signs with ECDSA. The account's identity key is the compressed public key of its account root. From then on the account root is an ordinary BRC-100 root key. Every key the wallet derives for that account, including its baskets, its certificates and any BAP identity, is derived from the account root alone. Accounts do not share outputs, actions or permissions.

#### `profile` derivation

Account `n ≥ 0` is BRC-157 profile `n`:

```ts
const accountRoot = HD.fromSeed(Mnemonic.fromEntropy(entropy).toSeed())
  .derive(`m/0'/${n}'`).privKey
```

Account 0 is the BRC-157 root key (`m/0'/0'`). The entropy key, the seed and the BIP-32 nodes above the profiles MUST NOT be used as an account, to sign, or to encrypt. The seed uses the empty BIP-39 passphrase, as BRC-157 requires by default.

#### `self-child` derivation

Account `0` is the root key. Implementations MUST NOT derive account 0, and MUST NOT change the root key when accounts are added.

For an integer `n ≥ 1`, the account root is the BRC-42 child of the root key, derived with the [BRC-43](/key-derivation/0043) invoice number `2-account-account-n`:

| Field          | Value                                                                       |
| -------------- | --------------------------------------------------------------------------- |
| Security level | `2`                                                                         |
| Protocol name  | `account`                                                                   |
| Key ID         | `account-n` in decimal, with no leading zeros (`account-1`, `account-2`, …) |
| Counterparty   | `self`                                                                      |

In the BSV TypeScript SDK:

```ts
const accountRoot = new KeyDeriver(rootKey)
  .derivePrivateKey([2, 'account'], `account-${n}`, 'self')
```

### Reserved requests

The protocol name `account` is reserved for the wallet at every security level, as [BRC-44](/key-derivation/0044) reserves names beginning with `admin`. A wallet implementing this standard, whatever its derivation:

1. MUST refuse every application request whose `protocolID` names `account`. The name is compared after the BRC-43 normalisation (lowercase, surrounding whitespace removed). This covers `getPublicKey`, `createSignature`, `verifySignature`, `encrypt`, `decrypt`, `createHmac`, `verifyHmac`, and `revealSpecificKeyLinkage`.
2. MUST refuse `revealCounterpartyKeyLinkage` and `revealSpecificKeyLinkage` when the counterparty is the active account's own identity key or `self`, under any protocol. Counterparty linkage with itself yields the BRC-42 offset of every `self` child of the account root, and specific linkage yields one child's offset. A `self` child is its root plus its offset, so an offset together with that child's private key gives up the root. Any key the wallet exports as a `self` child (a `self-child` account root, a server key, a BAP signing key) is enough for that.
3. MUST refuse these requests without a permission prompt. A user cannot meaningfully consent to handing out another account's key or its own root.

Under `self-child` derivation, rule 1 is what keeps accounts apart. Without it, an application approved on account 0 could ask for a signature by `[2, "account"]`, `account-1`, `self` over a transaction sighash and spend account 1. Rule 1 also stops the application linking account identity keys to one vault through `getPublicKey`. Under `profile` derivation no BRC-100 request reaches another account, and rule 1 keeps applications behaving the same against every wallet. Rule 2 protects each account's own root under both derivations.

### Allocation and naming

Implementations MUST allocate indices in order. Account 0 exists from the start under both derivations, and new accounts take 1, 2, and so on. A new account takes one more than the highest index ever allocated under the vault master. An index is its key: implementations MUST NOT reassign an index to a different account, including after the user hides or archives one. After recovery, the highest discovered index counts as the highest allocated. An index allocated before recovery but never used holds no state, so handing it out again is harmless.

Account names are local metadata. They MUST NOT affect derivation. A wallet SHOULD keep `{ index, name, identityKey }` for each account, keyed by account 0's identity key.

### Recovery

The vault master alone MUST be sufficient to recover every account. No account list, name or other backup may be required to find an account or derive its keys.

1. Recover the vault master by its usual means (BRC-157 mnemonic or shares, BRC-75 mnemonic, imported key) and determine its derivation (next section). Account 0 is now known.
2. **Discover** the other accounts. Starting at index 1, derive each account's identity key in order and check it for use: any record the wallet would itself rely on to recover that account. Examples are a history backup stored for the identity key, or messages waiting for it at its messagebox. Requests to those hosts are signed by the candidate account's own root key, so no host learns any key.
   * An index is **used** when a record exists, and **unused** when every source answers that none does.
   * A wallet MUST NOT stop before five consecutive unused indices. Every index up to the highest used one is an allocated account, because allocation is sequential.
   * When a source cannot answer, the wallet MUST NOT treat the index as unused. It keeps the accounts found so far and resumes discovery from that index later.
3. Derive each account root as above and open it as its own wallet. An account that opens with empty local state recovers its history exactly as account 0 does (for example, from its history backup), then reconciles with the chain.

Account names are not recoverable from the vault master. A wallet MAY back them up beside each account's state; otherwise discovered accounts take default names.

### Telling the derivations apart

A backup does not always say what it is. The same 12 to 24 words are BRC-157 entropy, a BRC-75 phrase, or the seed of an older HD wallet. A key reconstructed from BRC-140 shares, or typed in as an emergency key, is either BRC-157 entropy or a root key. Each reading opens different accounts. A wallet restoring a backup whose derivation it does not know:

1. MUST open the reading for which this device already holds local state.
2. Otherwise MUST check account 0 of each reading for use, as discovery does. Coins at account 0's own address, and a handle bound to its identity key, MAY also count. The reading whose account 0 is used wins. If several are used, the wallet's default for that kind of backup wins among them.
3. When no reading is used and any source could not answer, the wallet MUST NOT guess. Opening the wrong reading shows an empty wallet over the real one. It reports the failure and lets the user retry.
4. When every source answered and no reading is used, nothing is at stake, and the wallet opens its default reading for that kind of backup. A 24-word phrase without a passphrase SHOULD default to BRC-157.

The wallet then records the derivation it opened, so later unlocks never ask again. A wallet that copies a vault to another device (for example, a sealed recovery copy) MUST carry the derivation with the master.

## Test vectors

### `profile` derivation

The BIP-39 test-vector entropy `7f7f…7f` (32 bytes), whose phrase is `legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title`, with the empty passphrase:

| n | Path      | Account root                                                       | Identity key                                                         |
| - | --------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
| 0 | `m/0'/0'` | `4303939e04ef918fa573d990df9897c7cf59e12820ec23b9892ce2c482d8aea0` | `03bc1b7e076b075a9c159c0b2a66ad7589585ade3a097c802f343076914c3b0586` |
| 1 | `m/0'/1'` | `2f7c7332643e37919578e6dde41ac248cbeaf25729e61b5c03c877d19893be6b` | `03c9e3d39da57af25878ddd2e434f7cbbaa14cbed08aa208ef5ae029a1c6a632d9` |
| 2 | `m/0'/2'` | `1ed4d12cbe20a087ac5a96a654f46f78dc72553bde92259d1251419f36051cfc` | `02bda138d95a4ac8dba67198700b3eeb0cf87710a792425d5e5f3a4e414d32a8d1` |

Account 0 of the 12-word phrase in BRC-157's worked example is BRC-157's own vector, `27e442c8015fc055789d6628f3b30461e8b2598aff74dc87ceef00dd8e670e55`.

### `self-child` derivation

Root key, the BRC-75 example key:

```
root key       1ad0895dd317163f0e83499c30bc593dbcc54cad96a5f57b065ce9f700513250
identity key   021c2361fa1c39e21422b1374c2a08106f99b5425ada71f46f55b8e8e9d4a932db
```

| n | Invoice number        | Account root                                                       | Identity key                                                         |
| - | --------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
| 0 | —                     | `1ad0895dd317163f0e83499c30bc593dbcc54cad96a5f57b065ce9f700513250` | `021c2361fa1c39e21422b1374c2a08106f99b5425ada71f46f55b8e8e9d4a932db` |
| 1 | `2-account-account-1` | `ba883e48fc890f89b6fee2c7f7f2ff727468c8bc525f364511ecbd5d90d619e5` | `0269b9208f87d9aefe02f7a49a5d6bd10621165d3c768755a8e2da7989fe0404fc` |
| 2 | `2-account-account-2` | `3b4cd297156d6973c51cef7913da558d675936c2d5b5483072712dd7a3f7d55d` | `0329e91b2bf9109a829e8e8217df43e6e793fa32ab45a13cf6387e85ba05e4ae4d` |

The same 32 bytes read as BRC-157 entropy open different accounts, which is why a restore has to tell the readings apart.

## Security considerations

* **The vault master controls every account.** Anyone who holds it holds them all. That is the purpose of one backup and one unlock, and wallets SHOULD say so where accounts are created.
* **How independent an account key is depends on the derivation.**
  * *`profile`:* account roots are hardened BIP-32 children. One account key, together with anything the wallet reveals, gives nothing about the master or any other account. A wallet MAY export one account's key, for example to hand that account to someone else, without exposing the rest.
  * *`self-child`:* account root `n` is the root key plus a BRC-42 offset, and only the root key can compute that offset. One exported account key plus that offset reveals the root key, and with it every account. The reserved-request rules exist to keep the offset unreachable. A wallet that exports such an account key MUST NOT describe it as isolated from the vault.
  * *Either way*, the vault master can always re-derive an account it has handed over. Exporting an account key never revokes the vault's copy; a real transfer of ownership moves the account's value and identities to keys the recipient controls.
* **Rotation.**
  * *Account roots cannot be rotated in place.* An account whose key is exposed is retired: its funds move to a new index, and the old index stays allocated (discovery still finds it).
  * *Rotating the vault master replaces every account.* A wallet that does so must move each account's value and identities to the new master's accounts; nothing carries over automatically.
  * *Keys under an account root rotate independently of this standard*, for example a BAP signing key moving from `identity-N` to `identity-N+1`. But rotation does not change a retired key's offset: an old exported key plus its offset still gives up the account root. That is why rule 2 refuses linkage outright rather than per key.
* **Signing.** Account roots sign with ECDSA. Nonces MUST be deterministic per RFC 6979, keyed by the signing key and message, as the BSV SDKs already do; BRC-100 offers no caller-supplied nonce. Account roots under one master are related keys, but each draws its nonces from its own key, so no two of them share one.
* **One account, one device.** A restored vault can be open on several devices, and two installs that open the same account are two spenders of one key: each builds transactions from its own copy of the account's state, and they double-spend each other. Outputs that only one install's state records stay invisible to the other. Sequential allocation also races: two devices that each create an account at once pick the same index, which is the same key. A wallet that lets several devices use one vault SHOULD keep each account on one device at a time and SHOULD reserve a new index with a conditional create at a store all of its devices share before using it. [BRC-249](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/0249.md) defines one way to do both. The vault master can still spend every account from anywhere, so this coordinates the owner's own devices and protects nothing against a stolen backup.
* **Discovery is visible to the hosts it queries.** A host asked about several identity keys from one client in a short time can infer that they share a vault, and restore probing does the same for each reading of a backup. Wallets SHOULD probe only hosts the user already relies on, and only during recovery.
* **Accounts are unlinkable to outsiders.** Profile keys and BRC-42 children of `self` look like unrelated public keys. No application can obtain the linkage, and the wallet MUST NOT disclose which accounts share a vault to an application.

## Rationale

* **Why BRC-157 profiles for new wallets?** Hardened derivation keeps accounts apart by construction rather than by refusing requests, the master never acts as a wallet, and any wallet that implements BRC-157 derives the same keys from the same backup.
* **Why keep `self-child` at all?** Root-key wallets have no BIP-32 master node, and their root key is already account 0 with coins and identities on it. Deriving from the root key adds accounts to those wallets without moving anything.
* **Why a BRC-42 child of `self` and not a new KDF for root keys?** Every BRC-100 wallet already implements the derivation, it needs no new primitive, and the invoice number cannot collide with any other protocol. BRC-43 names contain no hyphens, so `2-account-account-n` arises only from this protocol.
* **Why `account` and not a name under BRC-44's `admin` prefix?** The `self-child` derivation was deployed before this specification. Changing the name would move every deployed account root and strand its coins. Reserving the name explicitly gives the same protection.
* **Why record the derivation instead of always probing?** Probing needs the network and reveals the vault's keys to the hosts asked. A recorded derivation makes every unlock after the first local and certain.

## Non-goals

* Moving value between accounts. A transfer between accounts is an ordinary payment.
* Migrating a vault from one derivation to the other. That is a move of every coin and identity to a new vault.
* A second identity hierarchy. Each account's identity is its account root, and BAP, certificates and handles attach to it exactly as they would to any BRC-100 root.
* Same-identity device synchronisation. Each account syncs and backs up like any other wallet.

## Implementations

* HandCash Desktop and Mobile create BRC-157 vaults (24 words) and use the `profile` derivation for them. Vaults created earlier keep the `self-child` derivation; account 0 keeps its historical wallet database, and account `n ≥ 1` uses a database with an `-a<n>` suffix. Both derivations share one account layer, and the BRC-100 bridge refuses the reserved requests.
* After unlock, HandCash discovers accounts the device does not list. It checks each candidate for a history backup and for waiting messagebox mail, signing as that account. An account that opens empty restores from its own history backup before reconciling with the chain.
* HandCash keeps each account on one install. A new account's index is reserved with a create-only, account-signed record beside its history backup, and an account moves to the device that acts on it: the holder releases after uploading its history, or, if it does not answer, is displaced and hands over what it recorded ([BRC-249](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/0249.md)).
* On restore, HandCash reads a phrase as BRC-157, BRC-75 and the older HD scheme, and a key as BRC-157 entropy and as a root key. It checks account 0 of each reading for a history backup, mail, coins at its address and a bound handle, and stops if a host cannot answer. Sealed recovery copies for paired devices carry the derivation.

## References

* [BRC-42](/key-derivation/0042): BSV Key Derivation Scheme (BKDS)
* [BRC-43](/key-derivation/0043): Security Levels, Protocol IDs, Key IDs and Counterparties
* [BRC-44](/key-derivation/0044): Admin-reserved and Prohibited Key Derivation Protocols
* [BRC-75](/key-derivation/0075): Mnemonic For Master Private Key
* [BRC-100](/wallet/0100): Wallet-to-Application Interface
* [BRC-140](/key-derivation/0140): Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme
* [BRC-157](/key-derivation/0157): Entropy-Rooted Backup and Recovery with Mnemonics and Backup Shares
* [BRC-249](https://github.com/bsv-blockchain/BRCs/tree/master/wallet/0249.md): Vault Account Holding Across Devices (draft)


# 1Color Minimal Resource Profile

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines one required processing profile for [BRC-207](/wallet/0207) resources whose applications do not require token provenance, conserved quantities, issuer authorization or overlay admission. The wallet protects signing keys, identified transaction outputs and satoshis. It makes no fungible-token validity, scarcity or lineage claim for these resources. The profile uses the existing [BRC-162](/tokens/0162) declaration, script prefix and module interfaces.

MUST, MUST NOT, SHOULD and MAY express conformance requirements. This profile is optional. Unsupported wallets MUST fail rather than use ordinary token handling.

## Single mode declaration

The profile identifier is exactly `https://brc.dev/209`. Its descriptor is exactly `{"version":1,"params":null}` in the original deploy's `req` map:

```json
{
  "req": {
    "https://brc.dev/209": {"version": 1, "params": null}
  }
}
```

This is one mode, not a collection of independent flags. Other versions or parameters fail. The declaration MUST be in a BRC-162 binary deploy; BRC-161 JSON cannot select this mode. BRC-162's immutable requirements, strict payload parsing and binary-prefix precedence apply. Later outputs cannot introduce the mode for an identifier whose original declaration lacks it, or remove it by omitting payload data. This profile defines no `ext` state; its own `ext` entry MUST be absent. Other required profiles remain enforceable, including their history, identity or transition requirements. This profile removes only the baseline obligations listed below, never another profile's obligations. Incompatible requirements fail.

## Declaration evidence

For each resolved asset ID involved in a signature request, action plan or receipt, the caller MUST supply this profile's asset-scoped `profileData` leaf as exactly `{"declarationTx": "<lowercase raw transaction hex>"}`. Hex MUST be nonempty, even-length and decode to exactly one complete serialized Bitcoin transaction without trailing bytes. The wallet MUST:

1. Compute the transaction ID from the exact supplied serialized bytes, without normalization, and match the asset ID's TXID component.
2. Select the asset ID's actual output and require a BRC-162 binary deploy at index 0, validating its prefix and syntactic quantity/role rules.
3. Strictly decode the applicable payload and verify this exact descriptor and every other required profile. A display label or caller-supplied parsed map is not declaration evidence.
4. Match every involved source and destination's declared asset ID, and reject conflicting repeated `req` maps under BRC-162.

This authenticates bytes committed to by the identifier. The declaration transaction need not be mined, broadcast or connected to the current resource. No historical token path from it to that resource is required. The wallet MUST NOT describe this check as confirmed issuance, issuer identity, supply validation or proof that a current output descended from the declaration. Anyone can create another output naming the same identifier. Applications needing origin, uniqueness or continuity must establish those properties separately.

A new deploy uses `ISSUE` and an empty leaf `{}`; its declaration is read from the proposed transaction itself. After finalization, the actual ID and full transaction bytes replace the provisional record, retaining the original key tuple. Subsequent requests for that resolved ID supply `declarationTx` normally. An internalization request using an ISSUE remittance reads the declaration from its completed subject transaction. ISSUE never identifies an existing input in plan/evidence metadata. The preserved original ISSUE key-derivation tuple remains usable to sign that deploy output; its resolved asset ID selects the declaration evidence.

Evidence is required for every involved ID using this profile, including foreign resources and requested destinations. Repeated declaration evidence MUST agree. A wallet MAY cache verified bytes by exact ID to avoid repeated verification, but cache presence does not permit an omitted leaf in these requests. `signAction` and continuation/processing of an existing action use its retained, validated plan and evidence. Read queries use the record's authenticated requirements; they do not infer support from a basket name.

### Transport and bounds

Evidence travels only in `profileData["https://brc.dev/209"][assetId]`, or its ISSUE entry. It is not a BRC-176 packet or an extra transaction in ordinary Atomic BEEF. URLs, proof references and automatic network retrieval are not part of this profile. Duplicate or extra members, mismatched IDs, invalid hex or missing declarations fail before signing or receipt acceptance.

For `internalizeAction` basket insertions into this profile's `p color` baskets, the complete `customInstructions` string MAY exceed the ordinary 1000-byte limit. It MUST NOT exceed 16 MiB (16,777,216 UTF-8 bytes); the sum of all `p color` receipt instruction strings in one call MUST also fit that limit. This exception requires a valid, supported declaration and valid module receipt instructions; a basket label alone cannot establish eligibility. Count the actual encoded strings, including repeated evidence and JSON/hex overhead. Enforce bounds before large allocation or hex decoding. A smaller documented host or transport limit MUST produce an explicit error, never truncation or partial acceptance. Other receipts retain their ordinary limit. Signature envelopes and reconstructed action plans retain BRC-207's existing 16 MiB limits. Binary string framing is unchanged; codecs enforcing the earlier limit may explicitly reject the call.

## Processing contract

For an authenticated identifier using this profile, the following BRC-207 baseline checks and operations do not apply:

* Token ancestry or back-to-deploy validation, BRC-176 lineage packets, and historical admission of token inputs.
* Token overlays, SLAP discovery, bootstrap topics, topical membership, token spendability lookups, overlay preflight, submission and admission.
* Token quantity conservation, provenance, supply, minting authority and token mint/burn/transfer accounting, including authority continuity.

A wallet MUST NOT require these as prerequisites for this profile alone. It MUST reject a `tokenProofs` item scoped to this profile's asset unless another required profile explicitly requires or accepts it and validates it under BRC-176. This profile alone never requires one; other assets retain their proofs. Ordinary BRC-161/162 indexers and BRC-176 validators remain unchanged: they may reject these outputs, and their base validity results are not assertions made by this profile.

The asset ID, script encoding, exact uint64 field, deploy-position rules and zero-versus-positive role discriminator remain syntactically meaningful. The field is a declared quantity, not a verified conserved balance; zero is not proof of an authorized minting capability. A resource may be created without a same-ID input, change quantity, or disappear without a token mint/burn authorization. Those facts do not waive consent for a protected key or resource being used. Every encountered identifier is evaluated independently; this mode cannot propagate through a transaction to ordinary assets.

### Bitcoin evidence, signing and completion

BRC-207's ordinary Bitcoin source authentication, BEEF/SPV requirements for the subject transaction, exact source scripts and satoshi values, DSAP, signature coverage, key isolation, script validation, reservations and exposure tracking remain mandatory. The declaration-only exemption above applies only to the configuration anchor; if that transaction also supplies an input or is the receipt subject, it must independently meet the ordinary Bitcoin requirements for that role. An arbitrary raw transaction is not proof of an available UTXO.

There is no additional token-level assertion of current unspentness. Wallets use their ordinary Bitcoin conflict and processing policy, reject known spent or conflicting resources, and describe pending or uncertain state accurately. Successful processing requires normal Bitcoin acceptance; inclusion or local possession alone is not a perpetual availability guarantee.

Wallets MUST support module-derived P2PKH resources. Arbitrary contract scripts use BRC-207's verified signing-role and executed-script-path rules. No covenant business semantics are added by this profile. The wallet may rely on supported script analysis or validated execution to establish its actual signing role; a public key appearing in a script or a supplied script-code offset alone is insufficient. Wallets MUST NOT require asset-specific business validation when ordinary script execution establishes the requested signing context. This profile does not make an otherwise unresolved execution path knowable. If missing material prevents determination of the signing context, that signature fails until the material is supplied. Missing other unlocking scripts may remain pending through `signAction` under BRC-207. A completed transaction must pass all required script checks.

The wallet MUST authorize each exercise of a protected role and its actual commitment, even when the declared quantity is unchanged. A user may grant an application continuing authority for arbitrary updates of identified resources or roles. Narrower permissions require facts the wallet can establish; unknown application semantics cannot be presented as verified conditions. Claims of ownership, entitlement, authenticity or contract success require additional validation and do not follow from this declaration.

All BRC-207 signature modes and input/output position constraints remain available. Partial, `noSend`, `sendWith` and two-phase flows retain their normal meaning. The wallet's non-final processor coordination and higher-sequence replacement responsibilities remain mandatory before non-final signature release. Removing overlays does not remove that responsibility or make a released signature revocable.

### Plans, custody and receipts

Each action plan MUST contain exactly one `{assetId, operation:"resource"}` entry for each identifier using this profile, including ISSUE for a new deploy. The ordinary token operation names MUST NOT be used for those IDs. Other assets use their normal operation entries. This entry authorizes no operation by itself: actual source/output changes and signing roles require user consent. The wallet must preserve exact requested scripts and declared fields rather than manufacture token change or repair apparent non-conservation.

Resources MUST be selected explicitly. Wallets MUST NOT automatically select, merge, split or generate change for them as fungible funding. Ordinary satoshi selection and change remain available. `createAction` may auto-sign a selected resource only when it can construct and validate the unlock and has the required resource and satoshi authorization. Arbitrary foreign scripts and mixed transactions remain supported without automatic custody assumptions.

Managed resources use BRC-207's existing per-asset baskets and authenticated remittance derivation. The zero/positive discriminator chooses the authority or value basket for storage compatibility, without asserting base-token rights. Receipts require completed, consensus-final transactions, normal Bitcoin source and script validation, no known conflicting spend, verified wallet control and all applicable profile checks. They require no token overlay or lineage. Internalization does not itself broadcast a transaction.

A locally accepted unreserved resource is `spendable:false` with `color_state_restricted`; reserved or pending status takes precedence. Both its value and authority balance selectors return `"0"` for accessible existing baskets because neither trusted units nor minting capabilities are available. Inventory still exposes actual managed outpoints, scripts and declared fields under ordinary read permissions. The wallet MUST distinguish these records from validated fungible holdings and MUST NOT claim issuer-authorized supply or provenance. Spent records leave held-resource inventory normally.

### Exchanges and other assets

These resources MUST NOT enter automatic amount-based netting or satisfy an ordinary token balance/payment requirement. A user may authorize an exchange of satoshis, normal tokens or other resources for a specified resource output. The wallet must independently authorize those debits and describe the resource consideration with only the facts it has validated. Incoming resource quantities cannot reduce the stated debit of a normal asset or satisfy a quantitative spending allowance. Actual incoming satoshis follow ordinary DSAP rules.

Conditional exchanges retain BRC-207's commitment requirements. The promised resource must be identified by transaction/script conditions that remain binding under the released signatures or another validated safeguard. A matching asset ID and declared quantity alone cannot guarantee authenticity or a unique resource. Additional required profiles may establish stronger conditions; this profile never suppresses them.

## Discovery and failures

This profile requires BRC-207 capability revision `3` or a subsequently specified compatible revision. Revision 3 does not itself advertise profile support: the wallet MUST check this exact descriptor on each operation. Older or unsupported implementations fail explicitly; applications MUST NOT retry by stripping the profile or using ordinary token or generic signing fallbacks.

Failures use BRC-207's existing BRC-100 error channel. Missing or conflicting evidence, unsupported requirements, resource limits, unavailable signing context, denied consent and ordinary Bitcoin failures MUST be reported without signature release, receipt acceptance or partial balance changes. After a signature has already escaped, BRC-207's exposure rules still apply.

## Appendix A — Conformance examples

These are independent offline fixtures, not live transactions. Alice uses the public test identity scalar 11; the sender identity scalar is 1. The source coinbase is assumed mature, included and unspent under the wallet's independently validated Bitcoin chain state. The declaration is only a configuration anchor; it need not share that chain state. Raw fixture bytes alone do not establish Bitcoin inclusion. Wallets have module/profile support and the stated permissions unless a case says otherwise. Errors below use illustrative code 1 in the BRC-100 JSON comms substrate; the described rejection is normative.

The declaration has ID `c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0`. Its strict payload requires this profile and includes an advisory `note` string of 480 ASCII x characters. The complete declaration transaction, also carried inside the requests, is:

```
01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1903548701147265736f757263652d6465636c61726174696f6effffffff01e803000000000000fd340200516d4d1402a263726571a17368747470733a2f2f6272632e6465762f323039a266706172616d73f66776657273696f6e01646e6f74657901e07878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac00000000
```

The current resource is `05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931.0`, carrying declared quantity 7 and 1000 satoshis. Its transaction is unrelated to the declaration. The proposed successor carries quantity 99 and 990 satoshis; it has no token authority input. Both remainders are remittance-derived P2PKH. Source and output scripts are:

```
204fdb4e22547cf6ecc2a9aebc399af8f2cb99bb679c21016cde8bbc4619f6d0c7576d76a914b7f0e9f6b0a04401650bfb46a59b0db318a811b588ac
204fdb4e22547cf6ecc2a9aebc399af8f2cb99bb679c21016cde8bbc4619f6d0c701636d76a914517b2fb0485ba09099f236d9013a4ad48e513c5b88ac
```

### A.1. Sign without token lineage or amount preservation

Alice authorizes this exact resource update and the ten-satoshi fee. No token overlay or BRC-176 proof exists.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDAx",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,56,49,44,50,51,50,44,50,51,44,49,54,48,44,49,57,48,44,52,49,44,57,48,44,53,49,44,51,50,44,49,54,53,44,49,54,49,44,49,55,51,44,50,51,44,49,54,49,44,49,50,56,44,49,50,57,44,50,49,50,44,49,50,49,44,49,51,54,44,49,53,44,54,53,44,50,48,48,44,49,51,56,44,49,52,57,44,56,53,44,49,48,50,44,49,52,57,44,50,51,55,44,50,50,52,44,49,54,53,44,49,52,44,50,49,50,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,44,51,44,56,52,44,49,51,53,44,49,44,50,48,44,49,48,53,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,50,44,49,48,49,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,48,44,49,49,54,44,52,53,44,49,49,52,44,49,48,49,44,49,49,53,44,49,49,49,44,49,49,55,44,49,49,52,44,57,57,44,49,48,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,48,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,56,55,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,49,56,51,44,50,52,48,44,50,51,51,44,50,52,54,44,49,55,54,44,49,54,48,44,54,56,44,49,44,49,48,49,44,49,49,44,50,53,49,44,55,48,44,49,54,53,44,49,53,53,44,49,51,44,49,55,57,44,50,52,44,49,54,56,44,49,55,44,49,56,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,52,57,44,50,53,44,49,57,44,49,50,48,44,49,53,49,44,54,50,44,49,54,49,44,49,53,51,44,55,56,44,49,56,44,50,55,44,54,44,49,57,57,44,50,51,54,44,49,51,48,44,50,54,44,50,50,44,49,51,49,44,49,54,52,44,51,54,44,53,54,44,49,49,51,44,56,52,44,57,52,44,49,50,53,44,50,51,53,44,51,44,55,52,44,50,49,57,44,49,56,54,44,50,53,48,44,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,49,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,49,44,57,57,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,56,49,44,49,50,51,44,52,55,44,49,55,54,44,55,50,44,57,49,44,49,54,48,44,49,52,52,44,49,53,51,44,50,52,50,44,53,52,44,50,49,55,44,49,44,53,56,44,55,52,44,50,49,50,44,49,52,50,44,56,49,44,54,48,44,57,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,44,34,112,114,111,102,105,108,101,68,97,116,97,34,58,123,34,104,116,116,112,115,58,47,47,98,114,99,46,100,101,118,47,50,48,57,34,58,123,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,58,123,34,100,101,99,108,97,114,97,116,105,111,110,84,120,34,58,34,48,49,48,48,48,48,48,48,48,49,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,102,102,102,102,102,102,102,102,49,57,48,51,53,52,56,55,48,49,49,52,55,50,54,53,55,51,54,102,55,53,55,50,54,51,54,53,50,100,54,52,54,53,54,51,54,99,54,49,55,50,54,49,55,52,54,57,54,102,54,101,102,102,102,102,102,102,102,102,48,49,101,56,48,51,48,48,48,48,48,48,48,48,48,48,48,48,102,100,51,52,48,50,48,48,53,49,54,100,52,100,49,52,48,50,97,50,54,51,55,50,54,53,55,49,97,49,55,51,54,56,55,52,55,52,55,48,55,51,51,97,50,102,50,102,54,50,55,50,54,51,50,101,54,52,54,53,55,54,50,102,51,50,51,48,51,57,97,50,54,54,55,48,54,49,55,50,54,49,54,100,55,51,102,54,54,55,55,54,54,53,55,50,55,51,54,57,54,102,54,101,48,49,54,52,54,101,54,102,55,52,54,53,55,57,48,49,101,48,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,53,55,54,97,57,49,52,102,99,54,51,50,49,56,48,48,51,54,56,51,100,99,51,51,48,53,100,48,54,52,52,97,53,50,102,55,51,101,98,48,56,48,50,101,53,99,49,56,56,97,99,48,48,48,48,48,48,48,48,34,125,125,125,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDAx",
  "status": "success",
  "result": {
    "signature": [48,69,2,33,0,198,15,140,137,124,207,219,96,145,105,6,116,18,132,153,198,204,2,19,93,207,90,118,43,239,201,199,83,254,189,47,224,2,32,77,215,153,109,173,52,110,218,172,36,17,219,31,200,87,236,129,61,29,35,70,245,121,255,153,244,1,8,117,37,62,86]
  }
}
```

The wallet authenticates the declaration and Bitcoin source, calculates the full source-script preimage, reserves the resource and obtains consent. Quantity 7 becoming 99 is permitted. The DER signature verifies for the actual source key and requested ALL|FORKID digest; it asserts no token supply or lineage.

### A.2. Auto-sign an explicitly selected resource

The same independent resource and authorizations apply. The request supplies a resource operation and exact output script. The wallet computes the ten-satoshi fee from the final layout.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDAy",
  "call": "createAction",
  "args": {
    "description": "Update explicitly selected resource",
    "inputBEEF": [1,1,1,1,49,25,19,120,151,62,161,153,78,18,27,6,199,236,130,26,22,131,164,36,56,113,84,94,125,235,3,74,219,186,250,5,2,0,190,239,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,25,3,84,135,1,20,105,110,100,101,112,101,110,100,101,110,116,45,114,101,115,111,117,114,99,101,255,255,255,255,1,232,3,0,0,0,0,0,0,60,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,87,109,118,169,20,183,240,233,246,176,160,68,1,101,11,251,70,165,155,13,179,24,168,17,181,136,172,0,0,0,0],
    "inputs": [
      {
        "outpoint": "05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931.0",
        "inputDescription": "Selected resource",
        "unlockingScriptLength": 108
      }
    ],
    "outputs": [
      {
        "lockingScript": "204fdb4e22547cf6ecc2a9aebc399af8f2cb99bb679c21016cde8bbc4619f6d0c701636d76a914517b2fb0485ba09099f236d9013a4ad48e513c5b88ac",
        "satoshis": 990,
        "outputDescription": "Requested resource successor"
      }
    ],
    "labels": [
      "p color plan 0 20 7b2276657273696f6e223a312c22617373657473223a5b22633764306636313934366263386264653663303132313963363762623939636266326638396133396263616561396332656366363763353432323465646234665f30225d2c226f776e65644f",
      "p color plan 1 20 757470757473223a5b7b226f7574707574496e646578223a302c2261737365744964223a22633764306636313934366263386264653663303132313963363762623939636266326638396133396263616561396332656366363763353432323465646234",
      "p color plan 2 20 665f30222c2264657269766174696f6e507265666978223a2246513d3d222c2264657269766174696f6e537566666978223a2241673d3d222c2273656e6465724964656e746974794b6579223a2230323739626536363765663964636262616335356130",
      "p color plan 3 20 3632393563653837306230373032396266636462326463653238643935396632383135623136663831373938227d5d2c226f7065726174696f6e73223a5b7b2261737365744964223a226337643066363139343662633862646536633031323139633637",
      "p color plan 4 20 62623939636266326638396133396263616561396332656366363763353432323465646234665f30222c226f7065726174696f6e223a227265736f75726365227d5d2c22696e707574506f736974696f6e73223a5b5d2c226368616e6765506f73697469",
      "p color plan 5 20 6f6e73223a5b5d2c22736967686173685479706573223a5b5d2c2270726f66696c6544617461223a7b2268747470733a2f2f6272632e6465762f323039223a7b226337643066363139343662633862646536633031323139633637626239396362663266",
      "p color plan 6 20 38396133396263616561396332656366363763353432323465646234665f30223a7b226465636c61726174696f6e5478223a2230313030303030303031303030303030303030303030303030303030303030303030303030303030303030303030303030",
      "p color plan 7 20 30303030303030303030303030303030303030303030303030666666666666666631393033353438373031313437323635373336663735373236333635326436343635363336633631373236313734363936663665666666666666666630316538303330",
      "p color plan 8 20 30303030303030303030306664333430323030353136643464313430326132363337323635373161313733363837343734373037333361326632663632373236333265363436353736326633323330333961323636373036313732363136643733663636",
      "p color plan 9 20 37373636353732373336393666366530313634366536663734363537393031653037383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 10 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 11 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 12 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 13 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 14 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 15 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 16 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 17 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837",
      "p color plan 18 20 38373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837383738373837353736613931",
      "p color plan 19 20 3466633633323138303033363833646333333035643036343461353266373365623038303265356331383861633030303030303030227d7d7d7d"
    ],
    "version": 1,
    "lockTime": 0,
    "options": {
      "noSend": true,
      "randomizeOutputs": false
    }
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDAy",
  "status": "success",
  "result": {
    "txid": "1fedbe5e56b0371dd935e12ecd3cb8d6548a8fb21102fe73357256f009c3ea97",
    "tx": [1,1,1,1,151,234,195,9,240,86,114,53,115,254,2,17,178,143,138,84,214,184,60,205,46,225,53,217,29,55,176,86,94,190,237,31,2,0,190,239,0,2,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,25,3,84,135,1,20,105,110,100,101,112,101,110,100,101,110,116,45,114,101,115,111,117,114,99,101,255,255,255,255,1,232,3,0,0,0,0,0,0,60,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,87,109,118,169,20,183,240,233,246,176,160,68,1,101,11,251,70,165,155,13,179,24,168,17,181,136,172,0,0,0,0,0,1,0,0,0,1,49,25,19,120,151,62,161,153,78,18,27,6,199,236,130,26,22,131,164,36,56,113,84,94,125,235,3,74,219,186,250,5,0,0,0,0,107,72,48,69,2,33,0,198,15,140,137,124,207,219,96,145,105,6,116,18,132,153,198,204,2,19,93,207,90,118,43,239,201,199,83,254,189,47,224,2,32,77,215,153,109,173,52,110,218,172,36,17,219,31,200,87,236,129,61,29,35,70,245,121,255,153,244,1,8,117,37,62,86,65,33,2,244,212,144,138,7,210,1,118,196,161,123,151,252,17,126,28,78,188,107,149,31,248,62,42,232,174,141,44,154,252,234,122,255,255,255,255,1,222,3,0,0,0,0,0,0,61,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,1,99,109,118,169,20,81,123,47,176,72,91,160,144,153,242,54,217,1,58,74,212,142,81,60,91,136,172,0,0,0,0],
    "noSendChange": []
  }
}
```

The plan reassembles to exactly the declared resource operation and evidence. The wallet may sign the recognized P2PKH input automatically; it preserves the requested quantity 99 and creates no token change. noSend returns the completed transaction without broadcasting it, and preserves exposure.

### A.3. Receipt evidence exceeds 1000 bytes

The fully signed successor is independently accepted by the wallet's Bitcoin policy, with no known conflict. Alice permits its internalization. No token overlay exists.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDAz",
  "call": "internalizeAction",
  "args": {
    "tx": [1,1,1,1,151,234,195,9,240,86,114,53,115,254,2,17,178,143,138,84,214,184,60,205,46,225,53,217,29,55,176,86,94,190,237,31,2,0,190,239,0,2,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,25,3,84,135,1,20,105,110,100,101,112,101,110,100,101,110,116,45,114,101,115,111,117,114,99,101,255,255,255,255,1,232,3,0,0,0,0,0,0,60,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,87,109,118,169,20,183,240,233,246,176,160,68,1,101,11,251,70,165,155,13,179,24,168,17,181,136,172,0,0,0,0,0,1,0,0,0,1,49,25,19,120,151,62,161,153,78,18,27,6,199,236,130,26,22,131,164,36,56,113,84,94,125,235,3,74,219,186,250,5,0,0,0,0,107,72,48,69,2,33,0,198,15,140,137,124,207,219,96,145,105,6,116,18,132,153,198,204,2,19,93,207,90,118,43,239,201,199,83,254,189,47,224,2,32,77,215,153,109,173,52,110,218,172,36,17,219,31,200,87,236,129,61,29,35,70,245,121,255,153,244,1,8,117,37,62,86,65,33,2,244,212,144,138,7,210,1,118,196,161,123,151,252,17,126,28,78,188,107,149,31,248,62,42,232,174,141,44,154,252,234,122,255,255,255,255,1,222,3,0,0,0,0,0,0,61,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,1,99,109,118,169,20,81,123,47,176,72,91,160,144,153,242,54,217,1,58,74,212,142,81,60,91,136,172,0,0,0,0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0",
          "customInstructions": "{\"version\":1,\"assetId\":\"c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0\",\"derivationPrefix\":\"FQ==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"profileData\":{\"https://brc.dev/209\":{\"c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0\":{\"declarationTx\":\"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1903548701147265736f757263652d6465636c61726174696f6effffffff01e803000000000000fd340200516d4d1402a263726571a17368747470733a2f2f6272632e6465762f323039a266706172616d73f66776657273696f6e01646e6f74657901e07878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac00000000\"}}}}"
        }
      }
    ],
    "description": "Receive a minimal resource"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDAz",
  "status": "success",
  "result": {
    "accepted": true
  }
}
```

The customInstructions string is exactly 1665 UTF-8 bytes. Its authenticated BRC-209 declaration selects the larger bound. The wallet verifies scripts and remittance control, records the output as restricted, and credits no fungible units. The unrelated declaration is carried in profileData, not added to Atomic BEEF.

### A.4. Resource inventory remains useful

Only the original seven-unit source is held in this independently initialized wallet. Alice allows this inventory query.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA0",
  "call": "listOutputs",
  "args": {
    "basket": "p color assets",
    "include": "locking scripts",
    "includeTags": true
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA0",
  "status": "success",
  "result": {
    "totalOutputs": 1,
    "outputs": [
      {
        "outpoint": "05fabadb4a03eb7d5e54713824a483161a82ecc7061b124e99a13e9778131931.0",
        "satoshis": 1000,
        "lockingScript": "204fdb4e22547cf6ecc2a9aebc399af8f2cb99bb679c21016cde8bbc4619f6d0c7576d76a914b7f0e9f6b0a04401650bfb46a59b0db318a811b588ac",
        "spendable": false,
        "tags": [
          "color_state_restricted"
        ]
      }
    ]
  }
}
```

The record identifies an actual Bitcoin output and its literal fields. It does not assert conserved units, issuer provenance or automatic funding eligibility.

### A.5. Declared quantities are not balances

The same accessible basket contains the original resource.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA1",
  "call": "listOutputs",
  "args": {
    "basket": "p color balance c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA1",
  "status": "success",
  "result": {
    "totalOutputs": "0",
    "outputs": []
  }
}
```

The exact module balance is zero even though inventory exposes declared quantity 7. An accessible authority basket under this profile likewise reports zero available capabilities.

### A.6. Hash binding rejects a different declaration

This request changes one byte of the declaration while retaining the original asset ID. All ordinary transaction fields are unchanged.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA2",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,56,49,44,50,51,50,44,50,51,44,49,54,48,44,49,57,48,44,52,49,44,57,48,44,53,49,44,51,50,44,49,54,53,44,49,54,49,44,49,55,51,44,50,51,44,49,54,49,44,49,50,56,44,49,50,57,44,50,49,50,44,49,50,49,44,49,51,54,44,49,53,44,54,53,44,50,48,48,44,49,51,56,44,49,52,57,44,56,53,44,49,48,50,44,49,52,57,44,50,51,55,44,50,50,52,44,49,54,53,44,49,52,44,50,49,50,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,44,51,44,56,52,44,49,51,53,44,49,44,50,48,44,49,48,53,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,50,44,49,48,49,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,48,44,49,49,54,44,52,53,44,49,49,52,44,49,48,49,44,49,49,53,44,49,49,49,44,49,49,55,44,49,49,52,44,57,57,44,49,48,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,48,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,56,55,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,49,56,51,44,50,52,48,44,50,51,51,44,50,52,54,44,49,55,54,44,49,54,48,44,54,56,44,49,44,49,48,49,44,49,49,44,50,53,49,44,55,48,44,49,54,53,44,49,53,53,44,49,51,44,49,55,57,44,50,52,44,49,54,56,44,49,55,44,49,56,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,52,57,44,50,53,44,49,57,44,49,50,48,44,49,53,49,44,54,50,44,49,54,49,44,49,53,51,44,55,56,44,49,56,44,50,55,44,54,44,49,57,57,44,50,51,54,44,49,51,48,44,50,54,44,50,50,44,49,51,49,44,49,54,52,44,51,54,44,53,54,44,49,49,51,44,56,52,44,57,52,44,49,50,53,44,50,51,53,44,51,44,55,52,44,50,49,57,44,49,56,54,44,50,53,48,44,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,49,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,49,44,57,57,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,56,49,44,49,50,51,44,52,55,44,49,55,54,44,55,50,44,57,49,44,49,54,48,44,49,52,52,44,49,53,51,44,50,52,50,44,53,52,44,50,49,55,44,49,44,53,56,44,55,52,44,50,49,50,44,49,52,50,44,56,49,44,54,48,44,57,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,44,34,112,114,111,102,105,108,101,68,97,116,97,34,58,123,34,104,116,116,112,115,58,47,47,98,114,99,46,100,101,118,47,50,48,57,34,58,123,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,58,123,34,100,101,99,108,97,114,97,116,105,111,110,84,120,34,58,34,48,49,48,48,48,48,48,48,48,49,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,102,102,102,102,102,102,102,102,49,57,48,51,53,52,56,55,48,49,49,52,55,50,54,53,55,51,54,102,55,53,55,50,54,51,54,53,50,100,54,52,54,53,54,51,54,99,54,49,55,50,54,49,55,52,54,57,54,102,54,101,102,102,102,102,102,102,102,102,48,49,101,56,48,51,48,48,48,48,48,48,48,48,48,48,48,48,102,100,51,52,48,50,48,48,53,49,54,100,52,100,49,52,48,50,97,50,54,51,55,50,54,53,55,49,97,49,55,51,54,56,55,52,55,52,55,48,55,51,51,97,50,102,50,102,54,50,55,50,54,51,50,101,54,52,54,53,55,54,50,102,51,50,51,48,51,57,97,50,54,54,55,48,54,49,55,50,54,49,54,100,55,51,102,54,54,55,55,54,54,53,55,50,55,51,54,57,54,102,54,101,48,49,54,52,54,101,54,102,55,52,54,53,55,57,48,49,101,48,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,53,55,54,97,57,49,52,102,99,54,51,50,49,56,48,48,51,54,56,51,100,99,51,51,48,53,100,48,54,52,52,97,53,50,102,55,51,101,98,48,56,48,50,101,53,99,49,56,56,97,99,48,48,48,48,48,48,48,49,34,125,125,125,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA2",
  "status": "error",
  "code": 1,
  "description": "Declaration transaction does not match asset ID"
}
```

The recomputed TXID differs. The wallet releases no signature and does not interpret the request as selecting another mode.

### A.7. A remembered resource does not waive request evidence

The wallet previously saw this resource but the new request omits the required profileData leaf.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA3",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,56,49,44,50,51,50,44,50,51,44,49,54,48,44,49,57,48,44,52,49,44,57,48,44,53,49,44,51,50,44,49,54,53,44,49,54,49,44,49,55,51,44,50,51,44,49,54,49,44,49,50,56,44,49,50,57,44,50,49,50,44,49,50,49,44,49,51,54,44,49,53,44,54,53,44,50,48,48,44,49,51,56,44,49,52,57,44,56,53,44,49,48,50,44,49,52,57,44,50,51,55,44,50,50,52,44,49,54,53,44,49,52,44,50,49,50,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,44,51,44,56,52,44,49,51,53,44,49,44,50,48,44,49,48,53,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,50,44,49,48,49,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,48,44,49,49,54,44,52,53,44,49,49,52,44,49,48,49,44,49,49,53,44,49,49,49,44,49,49,55,44,49,49,52,44,57,57,44,49,48,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,48,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,56,55,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,49,56,51,44,50,52,48,44,50,51,51,44,50,52,54,44,49,55,54,44,49,54,48,44,54,56,44,49,44,49,48,49,44,49,49,44,50,53,49,44,55,48,44,49,54,53,44,49,53,53,44,49,51,44,49,55,57,44,50,52,44,49,54,56,44,49,55,44,49,56,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,52,57,44,50,53,44,49,57,44,49,50,48,44,49,53,49,44,54,50,44,49,54,49,44,49,53,51,44,55,56,44,49,56,44,50,55,44,54,44,49,57,57,44,50,51,54,44,49,51,48,44,50,54,44,50,50,44,49,51,49,44,49,54,52,44,51,54,44,53,54,44,49,49,51,44,56,52,44,57,52,44,49,50,53,44,50,51,53,44,51,44,55,52,44,50,49,57,44,49,56,54,44,50,53,48,44,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,49,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,49,44,57,57,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,56,49,44,49,50,51,44,52,55,44,49,55,54,44,55,50,44,57,49,44,49,54,48,44,49,52,52,44,49,53,51,44,50,52,50,44,53,52,44,50,49,55,44,49,44,53,56,44,55,52,44,50,49,50,44,49,52,50,44,56,49,44,54,48,44,57,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA3",
  "status": "error",
  "code": 1,
  "description": "Required declaration evidence is missing"
}
```

The caller must supply the declaration for this new request. Retained evidence remains usable only for the documented continuation and read paths.

### A.8. A protocol grant does not authorize a resource update

The wallet has a generic protocol grant and fee approval, but Alice denies the resource operation.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA4",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,56,49,44,50,51,50,44,50,51,44,49,54,48,44,49,57,48,44,52,49,44,57,48,44,53,49,44,51,50,44,49,54,53,44,49,54,49,44,49,55,51,44,50,51,44,49,54,49,44,49,50,56,44,49,50,57,44,50,49,50,44,49,50,49,44,49,51,54,44,49,53,44,54,53,44,50,48,48,44,49,51,56,44,49,52,57,44,56,53,44,49,48,50,44,49,52,57,44,50,51,55,44,50,50,52,44,49,54,53,44,49,52,44,50,49,50,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,44,51,44,56,52,44,49,51,53,44,49,44,50,48,44,49,48,53,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,50,44,49,48,49,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,48,44,49,49,54,44,52,53,44,49,49,52,44,49,48,49,44,49,49,53,44,49,49,49,44,49,49,55,44,49,49,52,44,57,57,44,49,48,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,48,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,56,55,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,49,56,51,44,50,52,48,44,50,51,51,44,50,52,54,44,49,55,54,44,49,54,48,44,54,56,44,49,44,49,48,49,44,49,49,44,50,53,49,44,55,48,44,49,54,53,44,49,53,53,44,49,51,44,49,55,57,44,50,52,44,49,54,56,44,49,55,44,49,56,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,52,57,44,50,53,44,49,57,44,49,50,48,44,49,53,49,44,54,50,44,49,54,49,44,49,53,51,44,55,56,44,49,56,44,50,55,44,54,44,49,57,57,44,50,51,54,44,49,51,48,44,50,54,44,50,50,44,49,51,49,44,49,54,52,44,51,54,44,53,54,44,49,49,51,44,56,52,44,57,52,44,49,50,53,44,50,51,53,44,51,44,55,52,44,50,49,57,44,49,56,54,44,50,53,48,44,53,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,49,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,49,44,57,57,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,56,49,44,49,50,51,44,52,55,44,49,55,54,44,55,50,44,57,49,44,49,54,48,44,49,52,52,44,49,53,51,44,50,52,50,44,53,52,44,50,49,55,44,49,44,53,56,44,55,52,44,50,49,50,44,49,52,50,44,56,49,44,54,48,44,57,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,44,34,112,114,111,102,105,108,101,68,97,116,97,34,58,123,34,104,116,116,112,115,58,47,47,98,114,99,46,100,101,118,47,50,48,57,34,58,123,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,58,123,34,100,101,99,108,97,114,97,116,105,111,110,84,120,34,58,34,48,49,48,48,48,48,48,48,48,49,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,102,102,102,102,102,102,102,102,49,57,48,51,53,52,56,55,48,49,49,52,55,50,54,53,55,51,54,102,55,53,55,50,54,51,54,53,50,100,54,52,54,53,54,51,54,99,54,49,55,50,54,49,55,52,54,57,54,102,54,101,102,102,102,102,102,102,102,102,48,49,101,56,48,51,48,48,48,48,48,48,48,48,48,48,48,48,102,100,51,52,48,50,48,48,53,49,54,100,52,100,49,52,48,50,97,50,54,51,55,50,54,53,55,49,97,49,55,51,54,56,55,52,55,52,55,48,55,51,51,97,50,102,50,102,54,50,55,50,54,51,50,101,54,52,54,53,55,54,50,102,51,50,51,48,51,57,97,50,54,54,55,48,54,49,55,50,54,49,54,100,55,51,102,54,54,55,55,54,54,53,55,50,55,51,54,57,54,102,54,101,48,49,54,52,54,101,54,102,55,52,54,53,55,57,48,49,101,48,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,53,55,54,97,57,49,52,102,99,54,51,50,49,56,48,48,51,54,56,51,100,99,51,51,48,53,100,48,54,52,52,97,53,50,102,55,51,101,98,48,56,48,50,101,53,99,49,56,56,97,99,48,48,48,48,48,48,48,48,34,125,125,125,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA4",
  "status": "error",
  "code": 1,
  "description": "Resource operation is not authorized"
}
```

Hash binding and script validity pass. No signature is returned because using the protected resource requires its own applicable authorization.

### A.9. An older module rejects the extended receipt

This wallet does not implement the required profile or larger instruction bound. The application sends the same receipt as A.3.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDA5",
  "call": "internalizeAction",
  "args": {
    "tx": [1,1,1,1,151,234,195,9,240,86,114,53,115,254,2,17,178,143,138,84,214,184,60,205,46,225,53,217,29,55,176,86,94,190,237,31,2,0,190,239,0,2,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,25,3,84,135,1,20,105,110,100,101,112,101,110,100,101,110,116,45,114,101,115,111,117,114,99,101,255,255,255,255,1,232,3,0,0,0,0,0,0,60,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,87,109,118,169,20,183,240,233,246,176,160,68,1,101,11,251,70,165,155,13,179,24,168,17,181,136,172,0,0,0,0,0,1,0,0,0,1,49,25,19,120,151,62,161,153,78,18,27,6,199,236,130,26,22,131,164,36,56,113,84,94,125,235,3,74,219,186,250,5,0,0,0,0,107,72,48,69,2,33,0,198,15,140,137,124,207,219,96,145,105,6,116,18,132,153,198,204,2,19,93,207,90,118,43,239,201,199,83,254,189,47,224,2,32,77,215,153,109,173,52,110,218,172,36,17,219,31,200,87,236,129,61,29,35,70,245,121,255,153,244,1,8,117,37,62,86,65,33,2,244,212,144,138,7,210,1,118,196,161,123,151,252,17,126,28,78,188,107,149,31,248,62,42,232,174,141,44,154,252,234,122,255,255,255,255,1,222,3,0,0,0,0,0,0,61,32,79,219,78,34,84,124,246,236,194,169,174,188,57,154,248,242,203,153,187,103,156,33,1,108,222,139,188,70,25,246,208,199,1,99,109,118,169,20,81,123,47,176,72,91,160,144,153,242,54,217,1,58,74,212,142,81,60,91,136,172,0,0,0,0],
    "outputs": [
      {
        "outputIndex": 0,
        "protocol": "basket insertion",
        "insertionRemittance": {
          "basket": "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0",
          "customInstructions": "{\"version\":1,\"assetId\":\"c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0\",\"derivationPrefix\":\"FQ==\",\"derivationSuffix\":\"Ag==\",\"senderIdentityKey\":\"0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\",\"profileData\":{\"https://brc.dev/209\":{\"c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4f_0\":{\"declarationTx\":\"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1903548701147265736f757263652d6465636c61726174696f6effffffff01e803000000000000fd340200516d4d1402a263726571a17368747470733a2f2f6272632e6465762f323039a266706172616d73f66776657273696f6e01646e6f74657901e07878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787576a914fc63218003683dc3305d0644a52f73eb0802e5c188ac00000000\"}}}}"
        }
      }
    ],
    "description": "Receive a minimal resource"
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDA5",
  "status": "error",
  "code": 1,
  "description": "Required profile or receipt bound is unsupported"
}
```

Explicit rejection is acceptable. The application must not truncate evidence or retry through a non-module basket to obtain acceptance.

### A.10. Non-final coordination is still required

The evaluated chain context is below height 100500. Alice permits the update, but the wallet cannot establish the required processor coordination.

An application originates the following request:

```json
{
  "type": "CWI",
  "isInvocation": true,
  "id": "cmVzb3VyY2UwMDEw",
  "call": "createSignature",
  "args": {
    "protocolID": [
      2,
      "p color c7d0f61946bc8bde6c01219c67bb99cbf2f89a39bcaea9c2ecf67c54224edb4fv0 46413d3d 41673d3d"
    ],
    "keyID": "color",
    "counterparty": "anyone",
    "data": [123,34,118,101,114,115,105,111,110,34,58,49,44,34,116,120,34,58,91,49,44,49,44,49,44,49,44,50,50,51,44,49,49,49,44,49,57,44,49,57,56,44,56,53,44,50,51,44,49,52,55,44,49,48,48,44,49,53,56,44,57,49,44,56,44,56,51,44,49,49,48,44,49,53,52,44,50,51,57,44,52,56,44,49,50,57,44,50,51,57,44,49,48,44,49,51,57,44,50,48,52,44,49,50,50,44,57,57,44,53,56,44,49,55,52,44,54,48,44,49,56,49,44,50,52,57,44,54,54,44,50,48,55,44,56,53,44,49,53,52,44,50,44,48,44,49,57,48,44,50,51,57,44,48,44,50,44,48,44,49,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,44,51,44,56,52,44,49,51,53,44,49,44,50,48,44,49,48,53,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,50,44,49,48,49,44,49,49,48,44,49,48,48,44,49,48,49,44,49,49,48,44,49,49,54,44,52,53,44,49,49,52,44,49,48,49,44,49,49,53,44,49,49,49,44,49,49,55,44,49,49,52,44,57,57,44,49,48,49,44,50,53,53,44,50,53,53,44,50,53,53,44,50,53,53,44,49,44,50,51,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,48,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,56,55,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,49,56,51,44,50,52,48,44,50,51,51,44,50,52,54,44,49,55,54,44,49,54,48,44,54,56,44,49,44,49,48,49,44,49,49,44,50,53,49,44,55,48,44,49,54,53,44,49,53,53,44,49,51,44,49,55,57,44,50,52,44,49,54,56,44,49,55,44,49,56,49,44,49,51,54,44,49,55,50,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,52,57,44,50,53,44,49,57,44,49,50,48,44,49,53,49,44,54,50,44,49,54,49,44,49,53,51,44,55,56,44,49,56,44,50,55,44,54,44,49,57,57,44,50,51,54,44,49,51,48,44,50,54,44,50,50,44,49,51,49,44,49,54,52,44,51,54,44,53,54,44,49,49,51,44,56,52,44,57,52,44,49,50,53,44,50,51,53,44,51,44,55,52,44,50,49,57,44,49,56,54,44,50,53,48,44,53,44,48,44,48,44,48,44,48,44,48,44,49,44,48,44,48,44,48,44,49,44,50,50,50,44,51,44,48,44,48,44,48,44,48,44,48,44,48,44,54,49,44,51,50,44,55,57,44,50,49,57,44,55,56,44,51,52,44,56,52,44,49,50,52,44,50,52,54,44,50,51,54,44,49,57,52,44,49,54,57,44,49,55,52,44,49,56,56,44,53,55,44,49,53,52,44,50,52,56,44,50,52,50,44,50,48,51,44,49,53,51,44,49,56,55,44,49,48,51,44,49,53,54,44,51,51,44,49,44,49,48,56,44,50,50,50,44,49,51,57,44,49,56,56,44,55,48,44,50,53,44,50,52,54,44,50,48,56,44,49,57,57,44,49,44,57,57,44,49,48,57,44,49,49,56,44,49,54,57,44,50,48,44,56,49,44,49,50,51,44,52,55,44,49,55,54,44,55,50,44,57,49,44,49,54,48,44,49,52,52,44,49,53,51,44,50,52,50,44,53,52,44,50,49,55,44,49,44,53,56,44,55,52,44,50,49,50,44,49,52,50,44,56,49,44,54,48,44,57,49,44,49,51,54,44,49,55,50,44,49,52,56,44,49,51,54,44,49,44,48,93,44,34,105,110,112,117,116,73,110,100,101,120,34,58,48,44,34,115,105,103,104,97,115,104,84,121,112,101,34,58,54,53,44,34,111,119,110,101,100,79,117,116,112,117,116,115,34,58,91,123,34,111,117,116,112,117,116,73,110,100,101,120,34,58,48,44,34,97,115,115,101,116,73,100,34,58,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,44,34,100,101,114,105,118,97,116,105,111,110,80,114,101,102,105,120,34,58,34,70,81,61,61,34,44,34,100,101,114,105,118,97,116,105,111,110,83,117,102,102,105,120,34,58,34,65,103,61,61,34,44,34,115,101,110,100,101,114,73,100,101,110,116,105,116,121,75,101,121,34,58,34,48,50,55,57,98,101,54,54,55,101,102,57,100,99,98,98,97,99,53,53,97,48,54,50,57,53,99,101,56,55,48,98,48,55,48,50,57,98,102,99,100,98,50,100,99,101,50,56,100,57,53,57,102,50,56,49,53,98,49,54,102,56,49,55,57,56,34,125,93,44,34,112,114,111,102,105,108,101,68,97,116,97,34,58,123,34,104,116,116,112,115,58,47,47,98,114,99,46,100,101,118,47,50,48,57,34,58,123,34,99,55,100,48,102,54,49,57,52,54,98,99,56,98,100,101,54,99,48,49,50,49,57,99,54,55,98,98,57,57,99,98,102,50,102,56,57,97,51,57,98,99,97,101,97,57,99,50,101,99,102,54,55,99,53,52,50,50,52,101,100,98,52,102,95,48,34,58,123,34,100,101,99,108,97,114,97,116,105,111,110,84,120,34,58,34,48,49,48,48,48,48,48,48,48,49,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,102,102,102,102,102,102,102,102,49,57,48,51,53,52,56,55,48,49,49,52,55,50,54,53,55,51,54,102,55,53,55,50,54,51,54,53,50,100,54,52,54,53,54,51,54,99,54,49,55,50,54,49,55,52,54,57,54,102,54,101,102,102,102,102,102,102,102,102,48,49,101,56,48,51,48,48,48,48,48,48,48,48,48,48,48,48,102,100,51,52,48,50,48,48,53,49,54,100,52,100,49,52,48,50,97,50,54,51,55,50,54,53,55,49,97,49,55,51,54,56,55,52,55,52,55,48,55,51,51,97,50,102,50,102,54,50,55,50,54,51,50,101,54,52,54,53,55,54,50,102,51,50,51,48,51,57,97,50,54,54,55,48,54,49,55,50,54,49,54,100,55,51,102,54,54,55,55,54,54,53,55,50,55,51,54,57,54,102,54,101,48,49,54,52,54,101,54,102,55,52,54,53,55,57,48,49,101,48,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,56,55,53,55,54,97,57,49,52,102,99,54,51,50,49,56,48,48,51,54,56,51,100,99,51,51,48,53,100,48,54,52,52,97,53,50,102,55,51,101,98,48,56,48,50,101,53,99,49,56,56,97,99,48,48,48,48,48,48,48,48,34,125,125,125,125]
  }
}
```

The wallet returns:

```json
{
  "type": "CWI",
  "isInvocation": false,
  "id": "cmVzb3VyY2UwMDEw",
  "status": "error",
  "code": 1,
  "description": "Required non-final processor coordination is unavailable"
}
```

The absence of token overlays does not waive non-final sequence-replacement responsibilities. No non-final signature is released.

### A.11. Additional boundary cases

Each row is an independent conformance case; existing BRC-207 message shapes and the evidence above apply.

| Condition                                                                             | Required result                                                                                                     |
| ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| A normal asset receives a profileData declaration from this resource ID               | Fail its ID/declaration check; retain normal conservation and overlay rules.                                        |
| The declaration omits this profile, even though an output repeats it                  | Do not select this mode; fail the conflicting req map.                                                              |
| A BRC-161 JSON deploy includes extra fields claiming this profile                     | It cannot select this mode; a new BRC-162 binary deployment is required.                                            |
| The descriptor uses version 2 or non-null params                                      | Unsupported descriptor; no signature or receipt acceptance.                                                         |
| Two required profiles are present and the wallet implements only this one             | Fail; partial support is insufficient.                                                                              |
| Another required profile demands historical evidence                                  | Enforce it, including any BRC-176 packet that profile explicitly accepts and validates.                             |
| This profile alone receives a same-asset tokenProofs packet                           | Reject the unnecessary, unsupported proof field scope.                                                              |
| The asset has a zero-quantity resource with no authenticated token authority ancestry | Track only verified Bitcoin control in the authority basket; available authority balance is zero.                   |
| A fresh ISSUE deploy selects this profile                                             | Read its proposed payload, require an empty ISSUE evidence leaf and a resource operation; finalize its ID normally. |
| A later spend consumes the original deploy output                                     | Use its preserved ISSUE derivation tuple, but its resolved asset ID and declarationTx in evidence.                  |
| A resource is exchanged for normal tokens                                             | Independently authorize the normal-token debit; do not net the resource's declared quantity against it.             |
| A receipt under another basket exceeds 1000 bytes                                     | Reject under its ordinary bound.                                                                                    |
| A module receipt exceeds 16 MiB, or its call exceeds the aggregate bound              | Reject before large allocation or partial acceptance.                                                               |
| A foreign unlocking script is missing in an otherwise determinable signing context    | Permit the existing pending flow; do not report a completed transaction.                                            |
| The module's own executed signing path or script code cannot be determined            | Fail that signature until sufficient material is supplied.                                                          |
| Another transaction already consumed the resource                                     | Reject its use and update inventory/exposure under ordinary Bitcoin policy.                                         |
| Two requests race to spend the same resource                                          | Apply shared atomic reservations and explicit replacement/exposure rules.                                           |
| A completed transaction has an invalid unlocking script                               | Reject completion regardless of the resource declaration.                                                           |


# 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)


# Wallet-Native Elliptic Curve Point Multiplication as a BRC-98 Module

Connor Murray (<connor.murray@bsvassociation.org>)

## Abstract

This specification defines `ecpm`, a [BRC-98](/wallet/0098) permission-module scheme for applying a wallet-derived secp256k1 scalar to an arbitrary caller-supplied curve point, or removing that scalar by applying its modular inverse.

The operation is carried over the existing [BRC-100](/wallet/0100) `getPublicKey` method. Inside the reserved `p ecpm` namespace, a supporting wallet interprets the method semantically as elliptic-curve point multiplication and returns the normal `{ publicKey }` result. No method, call code, Wallet Wire message, or optional member is added to BRC-100.

The scheme enables commutative-masking protocols such as mental poker and verifiable shuffles while keeping the derived scalar inside the wallet. Wallets that do not install the module retain BRC-98's required behavior and reject the reserved protocol.

## Motivation

A class of multi-party protocols depends on the commutativity of scalar multiplication:

```
a·(b·P) = b·(a·P)
```

Barnett-Smart mental poker is a representative use. Each participant applies a secret scalar to encoded card points, and later removes that scalar in any order. The application needs `d·P` and `d⁻¹·P` for an arbitrary point `P`, but it must not learn `d`.

Ordinary BRC-100 `getPublicKey` does not supply this operation. It performs BRC-42/43 child-key derivation and returns a derived public key. Naming `P` as the BRC-43 counterparty influences the child derivation, but does not replace the generator with `P`. Depending on `forSelf`, the result is the wallet's or counterparty's derived child public key, not the wallet's derived private scalar multiplied by the caller's point.

This distinction matters when `P` is an intermediate masked point whose discrete logarithm is intentionally unknown. From `d·G` and `P`, an application cannot compute `d·P` without solving a discrete logarithm or already knowing the scalar of `P`.

Existing key-linkage and encryption methods can be composed to emulate some forward-multiplication cases, but that is not an equivalent contract. It couples a point operation to linkage disclosure and encryption semantics, does not provide multiplication by `d⁻¹`, and grants permissions for capabilities the application did not mean to request.

The missing behavior therefore cannot be obtained from pure BRC-43 naming alone. It requires a wallet-side semantic operation, but it does not require changing BRC-100.

## Relationship to BRC-98

BRC-98 reserves protocol identifiers beginning with `p` , requires unsupported wallets to reject them, and permits a supported scheme to define its own rules for permitted operations, key IDs, counterparties, permission attributes, and execution.

The `ecpm` scheme uses that reserved dispatch point to specialize the meaning of an existing BRC-100 method. This specification does not amend BRC-98. A clarification in discussion of BRC-98 may record that semantic specialization of an existing method inside a supported module namespace is an intended use of the module system; a separate amendment to BRC-98 is not required.

The semantic boundary is safe for compatibility:

1. Outside `p ecpm`, `getPublicKey` retains its ordinary BRC-100 meaning.
2. Inside `p ecpm`, a wallet either implements this scheme or rejects the request as BRC-98 already requires.
3. Applications never infer support from a new optional wallet member. They use a configured supporting wallet or handle the standard unsupported-scheme error.

## Specification

### Scheme and method

The BRC-98 scheme ID is:

```
ecpm
```

A conforming implementation MUST accept this scheme only through the existing BRC-100 `getPublicKey` method. It MUST reject every other BRC-100 method requested under `p ecpm`. This prevents the ECPM scalar from being reused for signing, HMAC, encryption, or another cryptographic purpose.

The protocol-name component of `GetPublicKeyArgs.protocolID` has this grammar:

```
p ecpm <operation> <pointHex> <logicalProtocolID>
```

where:

* `operation` is exactly `apply` or `remove`;
* `pointHex` is a lowercase, 66-character compressed secp256k1 public-key encoding;
* `logicalProtocolID` is the application protocol whose ECPM key universe is being requested.

The outer BRC-100 call remains:

```ts
wallet.getPublicKey(
  {
    protocolID: [
      securityLevel,
      `p ecpm ${operation} ${pointHex} ${logicalProtocolID}`
    ],
    keyID,
    counterparty,
    privileged,
    privilegedReason,
    seekPermission
  },
  originator
)
```

`keyID`, `counterparty`, `privileged`, `privilegedReason`, `seekPermission`, and `originator` retain their existing BRC-100 meanings and locations. They MUST NOT be duplicated or encoded into the protocol string.

`counterparty` defaults to `self` under the existing method rules. `identityKey: true` is prohibited. `forSelf` MUST be absent or false; it does not alter ECPM semantics.

### Logical protocol ID

The logical protocol ID MUST:

* contain only lowercase ASCII letters, numbers, and single spaces;
* contain no leading, trailing, or repeated spaces;
* be at least 5 characters and no more than 273 characters; and
* not end with `protocol`.

The 273-character ceiling ensures that the canonical derivation protocol `p ecpm <logicalProtocolID>` remains within BRC-43's 280-character protocol-ID limit.

The complete outer `p ecpm` string is a BRC-98 dispatch envelope, not the protocol ID used for BRC-43 derivation. It MAY therefore exceed BRC-43's 280-character limit so that the point and operation metadata do not reduce the logical protocol namespace. It MUST be no more than 354 characters: an `apply` request can be at most 353 characters and a `remove` request can be at most 354 characters. All permitted characters are ASCII, so these character and byte counts are identical. Both bounds remain within BRC-100's 400-character protocol-string limit.

A key ID MUST satisfy the active BRC-100 `getPublicKey` key-ID limit.

### Canonical scalar derivation

Let:

* `s` be the security level in the outer `protocolID` tuple;
* `L` be `logicalProtocolID`;
* `K` be the separately supplied `keyID`; and
* `C` be the separately supplied `counterparty`, defaulting to `self`.

The module MUST derive a nonzero private scalar `d` using BRC-42/43 with:

```
protocolID  = [s, "p ecpm " + L]
keyID      = K
counterparty = C
```

The point and operation MUST NOT form part of this derivation identity.

This omission is load-bearing. If the point were part of the invoice, every point would select a different scalar. If the operation were part of the invoice, `remove` would select a different scalar from `apply`. In either case round-trip removal and commutativity would fail.

The `p ecpm` prefix in the canonical derivation protocol isolates ECPM scalars from ordinary application keys using the same logical protocol and key ID.

### Operation

After validating the point as `P`:

* `apply` returns `d·P`;
* `remove` returns `d⁻¹·P`, where the inverse is computed modulo the secp256k1 group order `n`.

The successful result MUST use the ordinary `getPublicKey` result shape:

```ts
{ publicKey: PubKeyHex }
```

The returned value MUST be the lowercase compressed encoding of the resulting point.

For identical security level, logical protocol ID, key ID, counterparty, and root-key selection:

```
remove(apply(P)) = P
```

Implementations MUST reject, rather than encode, a result at infinity.

### Privileged keys

The existing `privileged` and `privilegedReason` fields select privileged-key behavior.

When `privileged` is absent or false, the module derives `d` from the wallet's ordinary BRC-42/43 root.

When `privileged` is true:

1. `privilegedReason` MUST be present and satisfy the existing BRC-100 description constraint.
2. The wallet MUST obtain explicit authorization even at security level 0.
3. The wallet MUST derive `d` from its privileged key material using the same canonical ECPM derivation tuple.
4. The privileged root or key-derivation capability MUST NOT be exposed to the application.
5. The wallet MUST fail closed if privileged derivation is unavailable.

A wallet implementation SHOULD request access to privileged material only after the operation and reason have been authorized, and SHOULD release that access according to its existing privileged-key policy.

### Permissions

The module applies BRC-43 permission semantics to the logical ECPM protocol:

* an ordinary security-level-0 request does not require a prompt;
* a security-level-1 grant is scoped to the originator, logical protocol ID, and ordinary-versus-privileged root selection;
* a security-level-2 grant additionally distinguishes the counterparty; and
* ordinary and privileged grants MUST NOT satisfy one another.

A wallet MAY cache a successful grant according to its normal permission lifecycle. The point, operation, and key ID SHOULD be displayed or made available to the authorization UI, but they do not change the BRC-43 protocol-level grant scope.

If permission is required and no applicable grant exists:

* `seekPermission: false` MUST fail without prompting;
* otherwise the wallet MAY seek authorization through its normal trusted UI.

Concurrent equivalent requests SHOULD share one pending authorization decision so that high-volume protocols cannot produce duplicate prompts.

### Point validation

Before multiplication, a conforming implementation MUST reject the input unless all of these conditions hold:

1. The encoding is exactly 33 bytes represented by 66 lowercase hexadecimal characters.
2. The first byte is `02` or `03`.
3. The encoded x-coordinate is less than the secp256k1 field prime `p`.
4. The encoding decodes to a point satisfying `y² = x³ + 7 (mod p)`.
5. The point is finite.

The x-coordinate range check MUST occur before a parser that reduces coordinates modulo `p`. Some curve parsers accept `02` followed by 32 `ff` bytes, reduce the x-coordinate, and then report the resulting different point as valid.

The same canonical validation MUST be applied when `counterparty` is supplied as a public key. The special values `self` and `anyone` remain valid.

### Errors

A conforming wallet MUST fail without performing multiplication when:

* the installed method is not `getPublicKey`;
* the protocol grammar or any field constraint is invalid;
* identity-key or `forSelf: true` behavior is requested;
* the point or public-key counterparty is invalid;
* authorization is unavailable or denied;
* privileged derivation is requested but unavailable; or
* the operation would return infinity.

Errors SHOULD identify the unsupported scheme, invalid field, or denied capability without revealing key material.

### Conformance vectors

The vectors below pin the canonical derivation and the group operation. They were generated with the BRC-42 derivation as implemented in `bsv-blockchain/go-sdk` (`PrivateKey.DeriveChild`), which is cross-tested against the BRC-42 specification vectors.

All vectors share these parameters:

* security level: `2`
* logical protocol ID: `mental poker deal`
* key ID: `deck mask 1`
* canonical derivation protocol: `p ecpm mental poker deal`
* BRC-43 invoice number: `2-p ecpm mental poker deal-deck mask 1`

Test wallets use transparent, minimal root keys, and the base point is the public key of the scalar 5:

```
wallet A root private key:  0000000000000000000000000000000000000000000000000000000000000002
wallet A identity key:      02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5
wallet B root private key:  0000000000000000000000000000000000000000000000000000000000000003
wallet B identity key:      02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9
base point P:               022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4
```

**Vector 1 — apply, counterparty `self`.** Wallet A receives:

```
protocolID = [2, "p ecpm apply 022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4 mental poker deal"]
keyID = "deck mask 1"
counterparty = "self"
```

With counterparty `self`, the BRC-42 shared secret is computed against the wallet's own identity key. The derived scalar and result are:

```
d_A:     0d7da380de7459582c600748e94539c408742f9d4f21153473ba2c1cb812a984
d_A·P:   023414ffa833cb89ab8cdd631998921d6a7b05b77931d0c50c80740324df6ce4f2
```

**Vector 2 — remove round-trip.** Wallet A receives the vector 1 result with operation `remove` and otherwise identical arguments. The output is `P` exactly:

```
d_A⁻¹·(d_A·P) = 022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4
```

**Vector 3 — multi-party commutativity.** Wallet B, with the same arguments and counterparty `self`, derives:

```
d_B:     d205df1fd5f36ed688c66dda212d1178b87e11cad37469a98c7b3edd690f2b3b
```

Applying both masks to `P` yields the same point in either order:

```
d_B·(d_A·P) = d_A·(d_B·P) = 035eebd9a281022ce46ea790bd460f6c6320b08a9e07b560b4da36480605c0580d
```

**Vector 4 — counterparty separation.** Wallet A with `counterparty` set to wallet B's identity key, all else as in vector 1, derives a different scalar and result:

```
d_A':    c3dd11d61386d969ff40e150e3039eeab9bda24ffa13936eac29d0347ec5104b
d_A'·P:  032406ec05e51aac7d0dcb1c7f1f3143b37e6336440ab728a552d668f2e397b8a4
```

**Vector 5 — rejected encodings.** Each of these `pointHex` values MUST be rejected without multiplication:

| Input                                                                | Reason                                                                                |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` | x-coordinate not less than the field prime; must be caught before any reducing parser |
| `020000000000000000000000000000000000000000000000000000000000000005` | x³ + 7 is not a quadratic residue; no such point exists                               |
| `04…` (65-byte uncompressed encoding)                                | not the 33-byte compressed form                                                       |
| any 64-character value                                               | wrong length                                                                          |
| vector 1's point with any uppercase hex digit                        | not the canonical lowercase encoding                                                  |

Privileged derivation (root-specific) and unsupported-module rejection are behavioral requirements without portable vectors: a wallet MUST produce different scalars for ordinary and privileged requests with otherwise identical arguments, and a wallet without the module MUST reject the reserved protocol as BRC-98 requires.

Protocol-length boundaries are also normative: implementations MUST accept a 273-character logical protocol ID for both operations, including the resulting 354-character `remove` envelope, and MUST reject a 274-character logical protocol ID.

## TypeScript reference module

The TypeScript reference implementation is described in [bsv-blockchain/ts-stack#488](https://github.com/bsv-blockchain/ts-stack/pull/488) as the installable `@bsv/ecpm-permission-module` package.

It extends the Wallet Toolbox permission-module interface with an optional semantic handler:

```ts
handleRequest?: (
  request: { method: string; args: object; originator: string },
  next: (args: object) => Promise<unknown>
) => Promise<unknown>
```

A semantic handler may return a conforming BRC-100 result directly, or invoke `next` at most once to use the underlying wallet method. Existing `onRequest` and `onResponse` transformation modules remain source-compatible.

The ECPM module returns the result directly and never forwards its request to ordinary `getPublicKey`; forwarding would derive `d·G` or a BRC-42 child public key, not `d·P`.

A wallet host installs it alongside other BRC-98 modules under the `ecpm` scheme and supplies:

* its ordinary BRC-42/43 key deriver;
* its trusted authorization callback; and
* optionally, a privileged-key-deriver provider.

The key derivers and derived scalars remain inside trusted wallet/module code and are never included in the BRC-100 response.

## Security

### Key isolation

For a counterparty point `Q`, `d·Q` is an ECDH shared secret. Reusing an identity, spending, signing, HMAC, or encryption key for ECPM could therefore disclose a capability belonging to another protocol.

For this reason, the canonical `p ecpm <logicalProtocolID>` derivation namespace and the method restriction are mandatory. A wallet MUST NOT substitute its identity key, spending key, or a key from an ordinary non-ECPM protocol.

### Chosen-point requests

For a valid prime-order secp256k1 point `P`, observing `P` and `d·P` does not reveal `d` without solving the elliptic-curve discrete logarithm problem. An adaptively selected valid point does not weaken this assumption in the prime-order group.

That argument depends on strict validation. Accepting a point on another curve or a non-canonical encoding can create small-subgroup or invalid-curve attacks that leak information about `d`. Implementations must validate the encoded point before multiplication.

### Permission and denial-of-service considerations

Mental-poker and shuffle protocols can require hundreds of operations, so prompting once per point is impractical. Protocol-scoped grants allow the intended throughput. Wallets SHOULD still bound request concurrency and resource use, and SHOULD make the logical protocol, originator, operation, key ID, counterparty, point, and privileged reason available to trusted policy code.

### Capability containment

The public application interface exposes only `getPublicKey` and receives only a compressed public point. The semantic module is trusted wallet code: it may use internal key-derivation capabilities, but it MUST NOT return those capabilities, a private scalar, or privileged root material.

## Why pure BRC-43 is insufficient

BRC-43 can name the counterparty and thereby determine which shared derivation universe is used. It cannot name a replacement generator for the requested group operation.

If the arbitrary point is supplied as `counterparty`, ordinary `getPublicKey` performs BRC-42 child derivation relative to that counterparty. It does not compute the wallet's derived private scalar times that point. If `getPublicKey` returns `d·G`, combining it with an arbitrary `P` still does not yield `d·P` unless the application knows the discrete logarithm of `P`. Intermediate points in a commutative-masking protocol are constructed specifically so that no one knows that logarithm.

Pure BRC-43 therefore provides the scalar namespace and permission vocabulary used by this specification, but not the required group operation. BRC-98 supplies the missing semantic dispatch without changing BRC-100.

## References

* [BRC-2: Data Encryption and Decryption](/wallet/0002)
* [BRC-42: BSV Key Derivation Scheme](/key-derivation/0042)
* [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](/key-derivation/0043)
* [BRC-44: Admin-reserved and Prohibited Key Derivation Protocols](/key-derivation/0044)
* [BRC-73: Group Permissions for App Access](/wallet/0073)
* [BRC-98: P Protocols](/wallet/0098)
* [BRC-100: Unified Wallet-to-Application Interface](/wallet/0100)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)


# User-Initiated Browser Permission Requests for Wallet Discovery

Crumbs

## Abstract

This document specifies what an application MUST do in the seconds before a browser asks the user whether a site may reach the wallet running on their own computer.

[BRC-185](/wallet/0185) requires applications to discover wallets by probing loopback addresses. Since Chrome 142 that probe is gated by a permission, and the first probe makes the browser interrupt the user with a dialog the application did not write, cannot style, cannot reword, and gets exactly one of per origin. A user who declines it has not declined a wallet. They have declined a sentence about apps and services that never mentioned wallets, arriving at a moment they did not choose, from a site that said nothing first.

Five things are specified. **The read**: determine the permission state with `navigator.permissions.query` before probing anything, because that query distinguishes four situations demanding four different behaviours and costs nothing. **The order**: exhaust the adapters that cannot raise the prompt first, because a wallet found through an extension makes the question unnecessary. **The primer**: in the `prompt` state only, show the application's own explanation first, on a deliberate user action, naming the wallet, naming what the browser is about to ask, and offering a refusal that is a real control rather than a dismissal. **The eliciting request**: the probe that raises the dialog is one request with no deadline, because [BRC-185](/wallet/0185) section 4.1 sets a 1000 ms deadline per discovery request and a person reading a dialog takes longer. **The recovery**: in the `denied` state, do not probe, report the condition in [BRC-185](/wallet/0185)'s existing `blocked` coverage vocabulary rather than as an absent wallet, and offer both a repair path and the substrates that need no permission at all.

The design rests on one asymmetry. **Declining a primer costs nothing and declining a browser dialog costs the origin permanently.** A primer dismissed can be offered again next week in a better moment. A prompt denied removes loopback discovery from that origin until the user goes into site settings and reverses it, which almost nobody does. The primer is therefore not decoration in front of the dialog. It is the only place where a refusal is recoverable, and a standard that leaves it optional spends the irreversible budget first.

## Motivation

[BRC-185](/wallet/0185) section 4.1 tells an application to look for wallets at `http://localhost:3301`, `http://localhost:3321` and `https://localhost:2121`, and to walk offsets 0 to 63 from each base with up to 16 simultaneous requests. That is between one and 192 requests to loopback addresses, issued before the user has done anything except arrive.

Chrome 142 shipped Local Network Access, which gates exactly those requests behind a permission. Chrome later split that permission into two, `local-network` for other machines and `loopback-network` for this one, and wallet discovery is entirely the second. The first request of a discovery round now suspends and the browser shows the user a dialog. The Chromium strings are unambiguous about what it says:

```
IDS_LOOPBACK_NETWORK_PERMISSION_FRAGMENT
  "Access other apps and services on this device."

IDS_LOOPBACK_NETWORK_INFOBAR_TEXT
  "<URL> wants to access other apps and services on this device."

IDS_PERMISSION_DENY
  "Block"
```

### What the user is actually looking at

The infobar string is the one-line form. On desktop the user gets the permission bubble, composed from the fragment, and it is worth reproducing exactly, because five properties of it determine everything this document requires. The following is a real prompt raised by a real deployment, with the host replaced by a reserved example name:

```
+--------------------------------------------------------------+
|                                                              |
|   example.com wants to                                   [x] |
|                                                              |
|   [#]   Access other apps and services on this               |
|         device                                               |
|                                                              |
|                                 [  Block  ]   [  Allow  ]    |
|                                                              |
+--------------------------------------------------------------+
```

**The word wallet does not appear, and cannot be made to appear.** Neither does Bitcoin, nor keys, nor the name of the software actually listening. The glyph is a generic display icon. Nothing connects the surface to the thing the user asked for ten seconds earlier, which is why section 5.3 requires the application to make that connection in advance.

**The only identification is the serialized host.** No application name, no favicon, no description. Where that host is a platform deployment or preview name, which is common, the user has never seen it before and does not associate it with the product they believe they are using. That is why section 5.2 requires the primer to display the same string first.

**`Block` and `Allow` are peers**, equal in size and contrast. The browser does not weight the choice, and an application that weights its own is being less respectful than the security dialog it stands in front of. Section 5.4 is written to that standard.

**The refusal is called `Block` and it means it.** `IDS_PERMISSION_DENY` is a persistent denial for the origin, and the control says so. A user who presses it has not deferred anything.

**There is a third outcome.** The close control is neither. Dismissing leaves the state at `prompt` and fails the request, and browsers suppress the prompt after repeated dismissals, so an application that retries walks the user toward a state where the dialog stops appearing at all. Section 6.2 handles it separately; branching only on grant and denial is incomplete.

**The dialog follows the browser and operating system theme.** It is dark here because that user's browser is. A page cannot know this and cannot match it, which is one more reason section 5.7 prohibits imitating browser chrome rather than discouraging it.

### What we propose the user sees first

This document is about a surface, so here is the surface. The dialog above arrives with no warning. The proposal is that it arrives second, and this arrives first, on a control the user operated:

```
+--------------------------------------------------------------+
|                                                              |
|   Your browser's question is broader than this site needs    |
|                                                              |
|   It asks whether this site may reach apps and services on   |
|   this device. That covers everything running here.          |
|                                                              |
|   This site looks for one thing, your wallet, and your keys  |
|   stay inside it. The question appears near the top of your  |
|   browser window.                                            |
|                                                              |
|   [  Connect my wallet  ]   [  Other ways to connect  ]      |
|                                                              |
+--------------------------------------------------------------+
```

Forty-two words, two controls of equal weight, no technical vocabulary, and the only claim it makes about the grant is one that section 12 binds the implementation to keep. While the browser's question is open the user keeps a non-blocking line telling them where to look, and if they refuse, a panel offers the routes that need no permission at all. Those three surfaces, before, during and after, are what this document specifies. Appendix A carries the rest of the copy and the copy that fails.

The two figures above are the same moment, sixty seconds apart in a user's experience and one ordering decision apart in an implementation. This is that ordering, looping:

![Wireframe animation in two phases. In the first, labelled Today, a browser permission bubble reading "example.com wants to access other apps and services on this device" appears beneath the address bar with no warning, offering Block and Allow. In the second, labelled This Document, the user operates a Buy control low in the page, a dashed line traces the distance from that control up to the top of the window, a primer appears there reading "Your browser's question is broader than this site needs" with Connect wallet and Other ways, and only then does the browser's own question appear, alongside a non-blocking note reading "Look near the top of your browser window. No time limit."](https://378209567-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FWRMRIqu4ZsU4vODzhZwR%2Fuploads%2Fgit-blob-581c9fdc6cf66656befb86fd35abe0bed5f4e31c%2F0325-primer-sequence.svg?alt=media)

The dashed arc in the second phase is not decoration. The control the user operated is at the bottom of the page and the browser's question is drawn at the top, in chrome the page does not own, and that distance is the one thing the mobile patterns this document adapts never had to solve. Section 9 is about it.

### What the existing standards already say, and who said it

Ty Everett and Deggen anticipated this in [BRC-185](/wallet/0185). Three of their statements set the boundary this document works inside. Section 1, on what a controller may treat as a runtime fact:

> "Runtime support" means facts such as DOM availability, **permitted loopback access**, native bridge presence, or an interactive terminal.

Section 3, on discovery:

> Discovery MUST be noninteractive at the wallet-permission layer. \[...] Browser or OS permission to access a substrate is a separate state that the controller MUST explain and respect.

Section 4.1, on the loopback scan itself:

> Discovery SHOULD begin only on an application wallet-use or connect action. A controller MUST NOT repeatedly rescan in the background, **work around browser local-network permissions**, disable certificate validation, or follow discovery redirects. **A policy-blocked scan is `blocked`, not evidence of absence.** A browser UI can ask for local-network access through normal browser controls or offer another substrate.

That last paragraph contains almost all of the correct policy. It forbids circumvention, names the reporting state, ties discovery to a user action, and says a browser may ask. What it leaves open is the only variable that determines the outcome: **when**. An explanation offered after the dialog has been denied is a post mortem, and `MUST explain and respect` does not say otherwise because it was not written to.

Ty Everett's [BRC-219](/wallet/0219) supplies the principle from the other side:

> 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.

A probe is not a consent moment. BRC-219 enforces that against the wallet. Nothing enforced it against the browser, and the browser is now the party being provoked by the same probe.

### Two conflicts that the browser permission creates in the existing standards

Reading [BRC-185](/wallet/0185) and [BRC-219](/wallet/0219) against the shipped Local Network Access behaviour produces two defects that are not matters of taste.

**A blocked probe is indistinguishable from an absent wallet, using the probe alone.** The Local Network Access specification states that a denied permission makes the fetch fail, and the failure is an ordinary network error. A `fetch` to `http://localhost:3321` that the browser refused on permission grounds and a `fetch` to a port where nothing is listening produce the same rejection in the same shape. BRC-185 section 4.1 requires that a policy-blocked scan be reported as `blocked` and section 5.1 requires that it be distinguished from wallet absence, and from the probe result that requirement is unmeetable. `navigator.permissions.query` is the only thing that makes it meetable, which is why section 3.1 of this document makes the query mandatory rather than advisory.

**A pending browser prompt breaks BRC-219's central assumption.** BRC-219 states the contract plainly:

> 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.

The Local Network Access check runs after the connection is obtained and suspends the fetch while the dialog is displayed. So a loopback probe can now hang for a third reason that BRC-219 does not enumerate: the browser is asking the user whether the request may be made at all, and nothing has reached the wallet. An application that follows BRC-219's binary will attribute that hang to a wallet that has not been contacted, and a wallet that is behaving correctly will be recorded as non-compliant. Section 3.1 resolves this too: an application that read the state before probing knows which of the two is happening, and section 10.3 requires the two to be presented differently.

There is a third, narrower collision, and it is the one most likely to break a correct implementation in practice. BRC-185 section 4.1 sets **a 1000 ms deadline per discovery request**. The browser holds the request open until the user answers the dialog. A user who reads four lines of unfamiliar text and clicks `Allow` takes several seconds, by which time a conforming controller has already abandoned the request. Priming the user perfectly and then timing out their grant is a worse outcome than not priming at all. Section 6 of this document fixes it.

### The evidence, and the one finding that shapes a rule

Five studies bear on this, and the table is the whole of what they contribute.

| Source                  | Scale                                                                         | Finding                                                                                                                                                         |
| ----------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bilogrevic et al., 2021 | 300M+ Chrome users, 800M+ prompts, then an A/B across 40M users and 70k sites | Redesigning the prompt UI moved grant rates by **under 5 percent**. Baseline grant rates are 10 percent on desktop and 21 percent on Android                    |
| Felt et al., 2012       | 308 surveyed, 25 observed                                                     | 17 percent paid attention to permissions. 3 percent answered all three comprehension questions. 42 percent of lab participants did not know permissions existed |
| Akhawe and Felt, 2013   | 25M+ warning impressions                                                      | Chrome's SSL warning bypassed 70.2 percent of the time. Text arriving without context is treated as an obstacle, not a question                                 |
| Wijesekera et al., 2015 | 36 instrumented phones                                                        | At least 80 percent would have blocked a request they had granted. A 2017 follow-up across 131 participants put the figure at 60 percent                        |
| Tan et al., 2014        | 772 participants, iOS dialogs                                                 | Approval rose from 65.8 to 73.6 percent when a purpose string was present (`Z = -3.569`, `p < 0.0005`)                                                          |

The first row is the one that answers the obvious objection, which is that prompt wording is the browser's problem. Chrome ran that experiment at a scale nobody else can and published the result: **redesigning the box does almost nothing to the decision, because the decision was made before the box appeared.** On an unprimed page it was made out of no context at all.

The last row is the one that shapes a rule here, and the usual reading of it is wrong. Tan et al. also randomised 14 purpose strings for one fictitious application, from a contentless control to careful explanations of what would be stored and where. Approval ran from 38.8 to 70.2 percent, which is the source of the widely quoted claim that better wording improves grants by 81 percent. But Fisher's exact test found **no significant difference between the control string and the others** (`p < 0.495`). Participants approved more when an explanation was present regardless of what it said. Satisfaction moved with quality; behaviour moved with presence.

So **the presence of an explanation is normative in this document and the quality of its wording is not**, because mandating an effect the source paper failed to find would be unfalsifiable and easy to check. Quality still earns its rules, for the reason Tan et al. did measure: it is what leaves a user satisfied with a decision rather than merely willing to make it, and a user who feels tricked revokes the grant later.

### Mobile solved a weaker version of this, and the web has a better signal than mobile

The mobile platforms converged on two mechanisms and the loopback prompt has neither. **iOS puts a slot inside the dialog**, a purpose string rendered in the system sheet, which Tan et al. found only 19 percent of 4,400 applications used. **Android puts a signal outside it**: no purpose string, so Google's guidance is an educational interface first, with `shouldShowRequestPermissionRationale` indicating when one is warranted.

The loopback prompt has no slot and will not get one, because letting an arbitrary site write text into browser security chrome is a phishing primitive rather than a feature. So the web is in Android's position, and the explanation must be a surface of the site's own.

What the web has that Android does not is a better signal. `shouldShowRequestPermissionRationale` is a boolean largely meaning "they denied you once". `navigator.permissions.query({name: "loopback-network"})` returns `granted`, `prompt` or `denied` before a single byte goes to loopback. That is a three-state read of the exact thing about to happen, free, and available inside a click handler. Mobile best practice is a workaround for a missing signal; here the signal exists, so this document is stricter than the pattern it adapts. Not "explain when you suspect you should" but "read the state, and let the state choose among four behaviours".

### The timing rule is not new, and is already a published accessibility criterion

Everything above argues the ordering from conversion evidence and from the structure of an irreversible grant. There is a shorter argument, normative in another standards body since 2008. WCAG success criterion 3.2.5, **Change on Request**, reads in full:

> Changes of context are initiated only by user request or a mechanism is available to turn off such changes.

A change of context is defined there as a major change that, made without the user's awareness, disorients a user who cannot see the whole page at once, and the enumerated kinds include a change of **user agent**, of **viewport**, and of **focus**. A permission bubble drawn in browser chrome, raised by a probe the page issued on its own initiative, is all three and none of them was requested. Section 5.1 point 1 is the criterion's first limb and section 5.1 point 6 is its second, the mechanism to turn such changes off, which here means letting a user disable wallet discovery outright.

The criterion is Level AAA and honesty requires saying so: most products target AA and are not obliged to meet it. Two things make it the right bar anyway. This change of context is **irreversible**, unlike the new windows and auto-submitting forms the criterion was written for, where a disoriented user can close the tab. And it is satisfiable here for one API call and an ordering rule, which is rare for AAA.

Its account of who is harmed reaches three requirements the conversion literature does not. **Blind and low-vision users may not notice a context change occurred at all**, and the bubble is outside the document where the page cannot announce it, which is why section 5.7 point 3 requires the primer to speak before it hands off. **Readers with motor impairments find unexpected changes hard to recover from**, and recovery here is a settings surface the page cannot link to, which is section 8.2's problem. **Readers with reading or intellectual disabilities benefit from advance cues**, which the understanding text notes are not required by the criterion but do reduce confusion, and which section 5.2 point 1 makes mandatory.

## Prior Art

The Motivation quotes the three BRC-185 statements and the BRC-219 statement this document builds on. This section records what each source supplies and which rule it reaches.

### Where this sits

Several documents describe one arc, from a user who has no wallet to a user whose request has just failed. None of them states the arc, so it is stated here, and this document's own place in it is marked. Each is independently implementable and none depends on another having been read.

| Moment                       | Document                   | What it owns                                                                                     |
| ---------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------ |
| Arriving with no wallet      | [BRC-110](/opinions/0110)  | Zero friction, deferred onboarding, and a benefit explained in context at the point of use       |
| Being offered one            | [BRC-137](/opinions/0137)  | Device detection, catalog-resolved install paths, fallback login, and the connect decision flow  |
| Choosing among them          | BRC-300, pull request #224 | The connect control and its vocabulary, chooser neutrality, and the sponsorship behind placement |
| **The browser's permission** | **This document**          | **What precedes the loopback dialog, and the honest reporting of a refusal**                     |
| Reaching the wallet          | [BRC-185](/wallet/0185)    | Substrate discovery and selection, coverage states, and connection states                        |
| When something fails         | BRC-326, pull request #332 | What a failure says, and what an application may do with it                                      |

Documents named without a link are submitted and not yet merged. They are named rather than linked so that this document resolves correctly whatever order the set lands in.

The arc continues past a failure, into what an application may claim about a request that did succeed and into reviewing and withdrawing the grants a user has already given. This document specifies neither and forecloses neither. Its own subject ends where the browser's permission is resolved and [BRC-185](/wallet/0185) discovery can begin.

The seam this document occupies did not exist when the onboarding documents were written. It opened when Chrome 142 placed a permission between the connect action and the substrate.

### What this document changes in BRC-137

[BRC-137](/opinions/0137) section 4 specifies the connect decision flow as a two-way branch: attempt a substrate connection, and on failure detect the operating system and show install cards. Section 5 point 1 then requires that view to **state plainly that no wallet was detected**.

Both predate the permission and neither survives it unchanged.

1. The attempt is now the probe that raises the browser's dialog, so BRC-137's flow begins by spending an irreversible grant with no explanation. Sections 3 to 6 of this document go between the tap and the attempt.
2. "No substrate reachable" is now four conditions wearing one label: granted and nothing listening, blocked by policy, denied by the user, and dismissed without an answer. Section 10.1 separates them.
3. **Section 5 point 1's requirement becomes a requirement to say something false** whenever the cause was the permission rather than an absent wallet. A user whose wallet is running and whose browser refused is told their wallet was not detected. Section 7.3 of this document forbids that sentence, as does [BRC-185](/wallet/0185) section 4.1's rule that a policy-blocked scan is `blocked` and not evidence of absence.

Where the cause is a permission condition under section 10.1, this document supersedes [BRC-137](/opinions/0137) section 5 point 1 and inserts sections 3 through 6 into its section 4 flow. Everything else in BRC-137 stands, including the whole of the no-wallet view once absence is actually established.

| Source                                                                                           | What it supplies                                                                                                                                                                                                                                                                                                                                                                | Where it lands                                                                                                                                                               |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [BRC-185](/wallet/0185), Ty Everett and Deggen                                                   | The discovery round that triggers the prompt, and the whole reporting vocabulary reused here: coverage states `complete`, `partial`, `blocked`, `unsupported`; the `ConnectionError` shape with `code`, `delivery` and `cause`, including `DISCOVERY_BLOCKED` and `CONFIGURATION_ERROR`; selection modes `automatic`, `ask`, `explicit`; the section 5.1 connection-state table | Sections 4, 10 and 13. Nothing here adds a wire field, method, endpoint or error code                                                                                        |
| [BRC-219](/wallet/0219), Ty Everett                                                              | A probe must not trigger a permission prompt. A prompt must identify the requesting origin and the capability. There is no elapsed-time limit on a live permission decision                                                                                                                                                                                                     | Sections 3.1, 5.2 and 6.1, each applied one layer outward to the browser. Section 5.6 keeps the two dialogs apart                                                            |
| [BRC-73](/wallet/0073), Ty Everett                                                               | That consecutive one-off permission requests are answered worse than one coherent request: "permission prompts can become fragmented and repetitive"                                                                                                                                                                                                                            | Section 5.1 point 4. The browser's dialog cannot be grouped with anything, so it is isolated instead                                                                         |
| [BRC-184](/wallet/0184), Ty Everett                                                              | The failure mode of an opaque request, a prompt reading only `[2, "auth message signature"]`, and the rule against delaying a prompt for a slow lookup                                                                                                                                                                                                                          | Section 5.5                                                                                                                                                                  |
| [BRC-151](/opinions/0151), BitcoinSvGuide                                                        | The threat Local Network Access exists to mitigate, naming DNS rebinding and malicious localhost requests, and the copy principle: "Permission prompts should describe consequences rather than internal terminology"                                                                                                                                                           | Section 5.5 point 1, section 12, Security Considerations                                                                                                                     |
| [BRC-137](/opinions/0137)                                                                        | That a wallet-less visitor must meet install paths rather than an error                                                                                                                                                                                                                                                                                                         | Sections 4.3 and 8.2. This document covers the case BRC-137 does not reach, where a wallet is running and the browser is in the way                                          |
| [BRC-110](/opinions/0110)                                                                        | That a benefit must be explained in context, at the point of use, in at most two lines                                                                                                                                                                                                                                                                                          | Section 5.2's connect disclosure                                                                                                                                             |
| [BRC-6](/wallet/0006) and BRC-185's extension, injected-global, React Native and mobile adapters | The substrates that never raise this prompt                                                                                                                                                                                                                                                                                                                                     | Section 4.1, which requires them exhausted first                                                                                                                             |
| WCAG 3.2.5, Change on Request                                                                    | That changes of context be user-initiated, or that a mechanism exist to turn them off, plus the account of who is harmed when they are not                                                                                                                                                                                                                                      | Section 5.1 points 1 and 6, section 5.7 point 3, section 8.2                                                                                                                 |
| Nielsen Norman Group, Rosala 2019                                                                | Copy, timing and decision reversal, and the dark-pattern catalogue                                                                                                                                                                                                                                                                                                              | Sections 5.1, 5.4, 5.5 and 9.2                                                                                                                                               |
| Nielsen, *Response Times: The 3 Important Limits*, 1993                                          | The 0.1, 1 and 10 second limits, from Miller 1968 and Card et al. 1991, and what each one obliges                                                                                                                                                                                                                                                                               | Section 6.1, which explains where BRC-185's 1000 ms deadline comes from and why it does not reach a human decision, and section 9.5, which is written to the ten-second case |
| Android educational interfaces, Apple purpose strings                                            | The two mobile mechanisms the primer adapts, neither of which the loopback prompt provides                                                                                                                                                                                                                                                                                      | Section 5, for the reasons in the Motivation                                                                                                                                 |

## Specification

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

### 1. Terminology

Terms defined in [BRC-185](/wallet/0185) section 1, in particular provider, substrate, adapter, candidate, controller, chooser, wrapper, application origin and coverage, are used as defined there.

* **Loopback request**: a request from the document's origin to a loopback address, meaning `localhost`, an address in `127.0.0.0/8`, or `[::1]`, matching [BRC-185](/wallet/0185) section 1's definition of loopback for this profile. The discovery addresses of [BRC-185](/wallet/0185) section 4.1 are loopback addresses.
* **Loopback access prompt**: the browser's own permission dialog, shown on the first loopback request from an origin whose permission state is `prompt`. Its text, styling, button labels, button order and settings label belong to the browser. In current desktop Chrome it is the permission bubble reproduced in the Motivation, composed from `IDS_LOOPBACK_NETWORK_PERMISSION_FRAGMENT` with the controls `Allow` and `IDS_PERMISSION_DENY`, plus a close control.
* **Dismissal**: closure of the loopback access prompt without choosing either control, through its close affordance, the Escape key, or loss of the anchoring surface. A dismissal is neither a grant nor a denial and leaves the permission state at `prompt`.
* **Permission state**: the value of `PermissionStatus.state` for the `loopback-network` descriptor, one of `granted`, `prompt` or `denied`, or one of the two distinct conditions `unsupported` and `exempt` defined in sections 3.3 and 3.4.
* **Primer**: a surface belonging to the application, shown before the loopback access prompt, which explains what the browser is about to ask and obtains the user's decision to proceed. The term is taken from the mobile literature, where it is also called a pre-permission prompt or an educational interface.
* **Probe**: a single loopback request issued as part of a [BRC-185](/wallet/0185) section 4.1 discovery round.
* **Eliciting request**: the single loopback request issued for the purpose of raising the loopback access prompt, defined in section 6. It is not a probe and is not part of a discovery round.
* **Connect surface**: the control through which a user asks the application to connect a wallet, together with the surface carrying it. This document does not define that control. [BRC-137](/opinions/0137) section 1 governs its place in a single login surface, the Sponsored Onboarding and Wallet Choice proposal governs its label and what it must say once, and [BRC-185](/wallet/0185) sections 5 and 7 govern the chooser and any application-supplied replacement. Where this document says connect surface it means whichever of those the application has built, and under section 5.1 point 1 a value control that needs a wallet counts as one.
* **Recovery surface**: a surface belonging to the application, shown when the permission state is `denied`, which explains the state and offers both repair and alternatives.
* **Affirmative response**: a user action on the primer that unambiguously selects proceeding. Closing, dismissing, pressing Escape, clicking away, navigating, or allowing a timer to expire are not affirmative responses.

### 2. Scope

2.1 This document applies to any document that will issue a loopback request in order to discover or connect a wallet, whether through [BRC-185](/wallet/0185) section 4.1 discovery, a legacy [BRC-5](/wallet/0005) substrate address, a `getVersion` readiness probe against a legacy default endpoint, or an application-configured loopback endpoint.

2.2 It does not apply to the `local-network` permission, to the extension, injected-global, [BRC-6](/wallet/0006) cross-document-messaging, React Native or encrypted mobile substrates, or to any wallet-layer permission governed by [BRC-116](/wallet/0116), [BRC-181](/wallet/0181) or [BRC-219](/wallet/0219).

2.3 It adds no wire format, no payload, no endpoint, no protocol identifier, no [BRC-185](/wallet/0185) error code and no cryptographic value. It constrains the order and content of user-facing surfaces, one call to a browser API, and the reporting of one condition into vocabulary [BRC-185](/wallet/0185) already defines. Conformance is therefore defined by the state table in section 9 rather than by test vectors.

### 3. Reading the permission state before probing

#### 3.1 The query is mandatory and comes first

1. Before issuing its first probe or eliciting request, a conforming controller MUST determine the permission state by calling `navigator.permissions.query` with the descriptor name `loopback-network`.
2. If that call rejects, the controller MUST retry once with the legacy combined descriptor name `local-network-access`, which browsers that predate the split expose as an alias of `local-network` and `loopback-network`.
3. The controller MUST NOT issue a loopback request in order to discover the permission state. A probe issued to find out whether probes are allowed will itself raise the prompt, at a moment the application chose and the user did not, spending the single irreversible grant to learn something the Permissions API answers for free. This is [BRC-219](/wallet/0219)'s rule that a readiness probe must not trigger a permission prompt, applied to the browser rather than the wallet.
4. The controller MUST NOT infer the state from the result of a loopback request. A denied permission makes the fetch fail with an ordinary network error, which is the same result as an unoccupied port, so the inference cannot be made and [BRC-185](/wallet/0185) section 4.1's requirement to report a blocked scan as `blocked` cannot be met without this query.
5. The controller MUST NOT substitute a stored flag, a heuristic, a user-agent test or a prior session's outcome for the live query.
6. A `granted` state does not imply that a wallet exists, and a `denied` state does not imply that one does not.

#### 3.2 Four states, four behaviours

| State         | Primer                     | Loopback requests                                                     | Surface shown                     |
| ------------- | -------------------------- | --------------------------------------------------------------------- | --------------------------------- |
| `unsupported` | MUST NOT show              | MAY probe immediately                                                 | ordinary discovery                |
| `exempt`      | MUST NOT show              | MAY probe immediately                                                 | ordinary discovery                |
| `granted`     | MUST NOT show              | MUST probe without interruption                                       | ordinary discovery                |
| `prompt`      | MUST show, under section 5 | eliciting request only after an affirmative response, under section 6 | primer, then ordinary discovery   |
| `denied`      | MUST NOT show              | MUST NOT issue any                                                    | recovery surface, under section 8 |

#### 3.3 Unsupported browsers

1. Where both descriptor names in section 3.1 reject with a `TypeError`, the browser does not implement this permission, the loopback access prompt will not appear, and the state is `unsupported`.
2. In the `unsupported` state a controller MUST NOT show a primer. A primer in front of a dialog that will never appear is a self-inflicted interruption, and it teaches the user that this application interrupts them for nothing. This mirrors [BRC-185](/wallet/0185) section 4.3's treatment of an absent parent window or native bridge, which reports `unsupported` without sending a message rather than waiting on a nonexistent channel.
3. A controller in the `unsupported` state MUST still handle a failed probe, because a browser may block loopback requests by a mechanism other than this permission. It MUST surface such a failure as `blocked` coverage under section 10.1, not as an absent wallet.

#### 3.4 Documents served from a loopback origin

1. A document whose own origin is a loopback origin is not making a local network request when it reaches another loopback address, and no prompt will appear. The state is `exempt` and a controller MUST NOT show a primer.
2. Implementers are warned that this is the condition under which wallet integrations are normally developed. A local development server at `http://localhost:3000` will never see the dialog this document is about, and an implementation tested only there is untested. Conformance testing MUST exercise the `prompt` and `denied` states from a non-loopback origin.
3. [BRC-185](/wallet/0185) section 8.2 permits a canonical HTTP loopback origin as a pairing `origin` for explicit local development. That allowance is unaffected and does not extend to production.

#### 3.5 Watching for change

1. A controller that holds a `PermissionStatus` SHOULD subscribe to its `change` event and re-evaluate section 3.2 when the state changes.
2. A controller showing a recovery surface MUST re-evaluate the state when the document becomes visible again, so that a user who repairs the permission in browser settings and returns is not left looking at an instruction they have already followed. Requiring a reload after a successful repair is a conformance failure.
3. On a transition from `denied` or `prompt` to `granted`, the controller MUST run a fresh discovery round and update the candidate snapshot required by [BRC-185](/wallet/0185) section 2.

### 4. Ordering against the other substrates

#### 4.1 Adapters that cannot raise the prompt go first

1. A controller MUST attempt every configured adapter that cannot raise the loopback access prompt before issuing an eliciting request. At minimum these are the extension announcement channel and legacy `window.CWI` of [BRC-185](/wallet/0185) section 4.2, the [BRC-6](/wallet/0006) and XDM adapter of section 4.3, React Native, an application-supplied embedded object, and any established mobile session.
2. Where those adapters yield a candidate that satisfies the controller's selection policy under [BRC-185](/wallet/0185) section 5, the controller MUST NOT show a primer and MUST NOT issue an eliciting request. The permission is a single irreversible resource and MUST NOT be spent to complete an inventory that has already answered the user's question.
3. The loopback adapter's coverage MUST then be reported as `blocked` under section 10.1 with the cause defined there, so that [BRC-185](/wallet/0185) section 4.1's requirement never to claim that all ports or all installed wallets were searched is satisfied honestly.
4. Where the user explicitly opens the chooser, requests a refresh, or asks to change wallet, the controller MAY offer the primer so that the loopback adapter can be included. That is a user-initiated action on the connect surface and satisfies section 5.1.

#### 4.2 Automatic mode and the missing user activation

[BRC-185](/wallet/0185) section 2 requires an upgraded `WalletClient` in `automatic` mode to delegate to the connection provider on its first connection attempt. That attempt can occur with no user gesture at all, which is precisely when a primer cannot be shown and the browser dialog would therefore be raised unprimed.

1. In the `prompt` state, where no transient user activation is available, a controller MUST NOT issue an eliciting request and MUST NOT probe loopback.
2. It MUST complete the adapters of section 4.1, report the loopback adapter's coverage as `blocked` with cause `permission-required` under section 10.1, and resolve under [BRC-185](/wallet/0185) section 5 on the candidates it has.
3. Where section 5 of that document then requires a chooser, or where no candidate was found, the resulting surface is a connect surface and the user's action on it supplies the activation the primer needs.
4. Where no user interface adapter exists, [BRC-185](/wallet/0185) section 5's existing `SELECTION_REQUIRED` applies unchanged, carrying the coverage state. This document introduces no new error code for the condition.

#### 4.3 Readers who may not have a wallet at all

The worst sequence this document can produce is a user with no wallet who is shown a browser security dialog, allows it, and is then told to go and install something. They have paid a trust cost for nothing, and the origin's single irreversible grant has been spent to establish an absence.

1. Where the controller has positive evidence that the user is unlikely to have a loopback wallet, it SHOULD present the install and pairing routes **before or alongside** the primer, rather than after a discovery round that finds nothing. Evidence includes no prior connection remembered for this origin, no extension or injected provider announced under [BRC-185](/wallet/0185) section 4.2, and no established mobile session.
2. The controller MUST NOT treat that evidence as proof. It cannot know what is listening without looking, and section 7.3's prohibition on reporting absence still applies.
3. This document does not name, rank, recommend or describe any wallet, and a conforming implementation of it does not acquire a wallet recommendation. [BRC-137](/opinions/0137) owns operating system detection and catalog-resolved install paths. [BRC-185](/wallet/0185) section 5 owns neutral presentation and forbids ordering candidates by vendor or commercial relationship. Wallet choice and its commercial arrangements are a separate subject with a separate document. The division is that this document governs **when the permission may be spent on a user who may not have a wallet**, and those documents govern **what that user is shown instead**.

#### 4.4 One component owns the interaction

[BRC-185](/wallet/0185) section 7 requires that one component own a given connection or onboarding interaction, and forbids a controller and a wrapper from each opening a competing missing-wallet modal. The same rule applies here: a controller and a wrapper MUST NOT each show a primer or a recovery surface for the same resolution, and integration documentation MUST state which component owns them.

### 5. The primer

This section applies only in the `prompt` state.

#### 5.1 Timing

1. A primer MUST be shown only in response to a deliberate user action that needs a wallet. Two shapes qualify: operating a connect control, whose evident purpose is connecting a wallet, or operating a value control whose completion needs one, such as buying, posting, unlocking or signing. This is the stricter form of [BRC-185](/wallet/0185) section 4.1's "Discovery SHOULD begin only on an application wallet-use or connect action." It is also WCAG success criterion 3.2.5 applied to this change of context: the browser's dialog is a change of user agent, viewport and focus, and it must be initiated by the user's request rather than by the page's initiative.
2. **Where both shapes are available, an application SHOULD use the value control.** A user who has just pressed Buy is holding the reason for the question, and a user who has just pressed Connect wallet is holding only a mechanism. This is Nielsen Norman Group's distinction between a context-related request, which arrives attached to something the user wanted, and a system-initiated one, which arrives attached to nothing, and the finding is that the first is the easier of the two to answer. A standard that recognised only a dedicated connect step would push implementers toward the shape that performs worse, and most products have no dedicated connect step in the path anyway. Section 9.3 gives the matching presentation.
3. A primer MUST NOT be shown on page load, on first scroll, on a timer, on exit intent, on hover, or as part of a sequence of unrelated onboarding dialogs.
4. A primer MUST NOT be shown alongside, immediately before, or immediately after any other permission request, consent dialog or cookie banner. [BRC-73](/wallet/0073) records that fragmented and repetitive permission requests are answered worse than one coherent request; the browser's dialog cannot be grouped with anything, so it must be isolated instead.
5. An application that can deliver any of its value without a wallet SHOULD deliver some of it before offering the connect surface at all.
6. An application SHOULD offer the user a way to disable wallet discovery for the application entirely, reachable from its ordinary settings. This satisfies the second limb of WCAG 3.2.5, a mechanism to turn such changes off, and it gives a user who wants this application without this capability something better than refusing a security dialog they did not understand. Disabling discovery MUST NOT be presented as disabling the application.

#### 5.2 Required content

A primer MUST communicate five things, and SHOULD communicate a sixth. Beyond these and the two disclosures below, it SHOULD communicate nothing at all.

1. **That the browser is about to ask.** The user is about to see a dialog that is not the application's. Saying so converts an ambush into an expected step. It is the one element with no equivalent in the mobile patterns this section adapts, because on mobile the system sheet is familiar and this one is not, and it is mandatory here rather than advisory because the change it precedes cannot be undone by the person it disorients.
2. **What is actually being reached.** A wallet running on this computer. The primer MUST name the wallet where the controller holds a verified descriptor `name` from [BRC-185](/wallet/0185) section 3, rendered as inert text as that section requires, and MUST otherwise say that it is a wallet.
3. **Why it has to be reached locally.** Keys stay in the wallet and the site never receives them. This is the benefit that justifies the access, and the one the browser's wording obscures.
4. **What the user gets.** The specific thing they were trying to do, in the words they used to try to do it.
5. **That the browser's question is broader than what this site will do.** `Access other apps and services on this device` describes a capability over the whole machine. What the application wants is one wallet on one of a handful of known addresses, and section 12 binds it to that. A user not told this reads the dialog as a website asking to get into their computer, which is a fair reading of the sentence in front of them and a wrong description of what is being asked for. The primer is the only place the mismatch can be named, because the application has no voice once the dialog appears.
6. **Where the question will appear.** The prompt is drawn at an anchor the page does not choose, and on desktop that anchor is nowhere near the control the user just operated. A user told to expect a question and not told where to find it has been prepared for the wrong half of the problem. This is a SHOULD only because the position cannot always be determined: section 9.4 point 3 forbids claiming one more precise than the application can establish, and silence beats a false statement.

[BRC-219](/wallet/0219) requires a wallet prompt to identify the requesting origin and the capability. A primer SHOULD likewise show its own serialized origin, as [BRC-185](/wallet/0185) section 1 requires when asking for approval, warning that a friendly name does not replace it. Where the host is not the name the user associates with the application, which includes platform subdomains and preview deployments, the primer MUST display it. The browser is about to identify the requester by that string and nothing else, and a user meeting an unfamiliar host for the first time inside a security dialog has been handed a reason to refuse that one line would have removed.

A primer MUST NOT state or imply that the dialog is a formality, that allowing is required to use the site when it is not, that the application has vetted or vouched for the grant, or that the application can undo it. Only the user can, in browser settings.

**Why a wallet rather than an email address, and where that argument belongs.** A user meeting this dialog for the first time is often also meeting the idea of connecting a wallet for the first time, and the question underneath their hesitation is usually not about permissions at all. It is why they are doing this instead of the sign-in they already know.

That argument is owned elsewhere and MUST NOT be rebuilt here. [BRC-110](/opinions/0110) section 6 requires applications to communicate the benefit in context, at the point of use, in at most two lines, on the finding that users retain such explanations only at the moment of relevance. The Sponsored Onboarding and Wallet Choice proposal requires the connect surface to carry a plain statement that the account is the user's and the application does not hold it, visible without navigation, and forbids `self-custody`, `non-custodial`, `sovereign` and `decentralized` as substitutes for saying what they mean.

Two consequences for this document:

1. Where a connect surface carried that statement, the primer MUST NOT repeat it. The user has read it, and a primer that re-argues the case has become a pitch attached to a security question.
2. Where the ask was bound to a value control under section 5.1 point 2, there may have been no connect surface and no statement. The primer MUST then carry it, within the two-line bound [BRC-110](/opinions/0110) section 6 sets, in the same plain terms and under the same prohibited vocabulary. This case exists because section 5.1 point 2 prefers the value control, and preferring it must not quietly remove the one explanation a newcomer was owed.

A primer MUST NOT state or imply that the grant extends beyond this origin, or that the user will not meet the same question again. It will not and they will. The permission is granted to one origin, so a user who uses three applications in this ecosystem answers this question three times. An application that says "you will only be asked once" has told the truth about itself and a falsehood about the user's week, and the second application to raise the dialog inherits the disbelief.

#### 5.3 Translating the browser's words

1. A primer MUST relate the browser's own phrasing to the wallet, so that the user recognises the dialog rather than meeting it cold.
2. An application MUST NOT hard-code the browser's exact sentence as though it were stable. Chrome has already renamed this permission once, from a local-network phrasing to the loopback phrasing, and renamed the settings entry to `Apps on device`. An application SHOULD paraphrase the category the browser will name, and MUST ensure the paraphrase remains true if the browser rewords the dialog.
3. The paraphrase MUST NOT contradict the dialog. Telling the user they will be asked about a wallet, when they will be asked about apps and services on this device, produces a mismatch that reads as deception at exactly the moment trust is being established.
4. **A primer MUST distinguish the breadth of the question from the narrowness of the use, and MUST NOT collapse the two.** It MAY state what the application will do with the grant, which section 12 confines to wallet discovery and wallet communication. It MUST NOT state or imply that the grant itself is narrow, that the browser is overstating the case, that the question is wider than it needs to be, or that the browser has erred in asking it. The grant genuinely does reach every service listening on the machine. A primer that says otherwise has told the user a falsehood about a security control in order to get past it, which is the conduct section 12 exists to make nameable and the Security Considerations describe as using an honest explanation to obtain a dishonest capability.
5. The narrowness claim is checkable, and a primer MUST NOT make it unless the implementation keeps it. Section 12 is what makes "this site looks only for your wallet" a conformance statement rather than a reassurance. An implementation that makes the claim in its primer and scans unrelated ports is non-conforming twice, once for the scan and once for the sentence.
6. A primer MUST NOT quote the browser's control labels. The dialog is drawn in the browser's user interface language, which is frequently not the document's. A page that tells its user to look for a particular word sends everyone with a differently localised browser hunting for text that is not on screen, and does it at the one moment the user is deciding whether this site can be trusted. A primer MAY instead describe the controls by role, for example that the question offers one control to allow and one to refuse, which stays true in every locale. It MUST NOT instruct the user to choose either one, MUST NOT describe choosing refusal as an error, and MUST NOT characterise the browser's question as unnecessary, routine or already answered.

#### 5.4 Two controls, equally weighted

1. A primer MUST offer exactly two controls: one that proceeds, one that declines.
2. Both MUST be real controls, reachable by pointer, keyboard and assistive technology.
3. Both MUST carry visible labels, and accessible names, that name the outcome. `Continue`, `OK`, `Next`, `Submit` and a bare `Cancel` name no outcome, and a user who scans only the controls, which is most users, learns nothing from them. Two labels that name two different outcomes can be chosen between without reading the body at all, which is the only form of this surface that survives a user who does not read.
4. The declining control MUST NOT be weaker than the proceeding control in size, contrast, placement or labelling. A filled button beside a low-contrast text link is a weighting, and Nielsen Norman Group identifies it among the dark patterns that raise short-term acceptance and cost long-term trust. The standard to meet is the browser's own: `Block` and `Allow` are adjacent peers of equal size and contrast, and a primer that weights its choice is less respectful than the security dialog it precedes.
5. Neither control may carry a recommendation badge, a default-selected indicator, a count of other users who chose it, or a pre-checked box.
6. The declining control MUST describe declining the primer, not blocking in the browser, and MUST NOT borrow the browser's refusal wording. Declining a primer is reversible and `Block` is not, so a primer labelled `Block` or `Deny` misrepresents a recoverable refusal as a permanent one and discards the only advantage the primer has.
7. The declining control MUST produce the behaviour in section 7. It MUST NOT merely close the primer and leave the user on the surface they started from with no explanation and no alternative.
8. Escape, the backdrop, and any close affordance MUST be treated as declining, never as proceeding.

#### 5.5 Language

1. A primer MUST NOT use the words `loopback`, `localhost`, `port`, `substrate`, `origin`, `CORS`, `permission policy`, or the name or number of any BRC, in text shown to the user. It MUST NOT use `self-custody`, `non-custodial`, `sovereign` or `decentralized` without saying what they mean alongside, and SHOULD avoid them entirely. That second list is the Sponsored Onboarding and Wallet Choice proposal's, adopted here unchanged so that a user who meets the connect surface and the primer does not meet two vocabularies. [BRC-151](/opinions/0151) states the principle for this corpus: permission prompts should describe consequences rather than internal terminology. [BRC-184](/wallet/0184) states the failure mode: a prompt carrying only `[2, "auth message signature"]` is hard to understand.
2. A primer SHOULD state at most two reasons. Evidence that long rationales outperform short ones does not exist; evidence that users stop reading does.
3. A primer SHOULD use active voice and name the benefit rather than the mechanism, following the pattern `[the wallet] so that you can [the thing you were doing]`.
4. A primer SHOULD NOT claim a better experience, a smoother journey, or any benefit the user cannot check. Vague benefit claims are distrusted, and they occupy the space a checkable one would fill.
5. Applications SHOULD test primer copy with users who do not work in this industry. The evidence in the Motivation is that presence drives the decision and quality drives satisfaction, so copy testing is a satisfaction and comprehension exercise, not a conversion exercise.
6. **One name for each thing, and sentences short enough to survive an interruption.** A primer MUST use one consistent term for the application throughout, one for the wallet, and one for the browser. Alternating between the site, this page, the app and we forces the user to re-resolve the subject of every sentence, and the user is already being asked to resolve an unfamiliar security question. A primer MUST NOT refer to the application in the first person in one sentence and the third in another. Sentences SHOULD be at most 20 words, and the body SHOULD be at most 50. Semicolons, parenthetical asides, and subordinate clauses stacked more than one deep SHOULD be rewritten as separate sentences. These numbers are a testable proxy and no study supports the specific figures; what is supported, and is set out in the Motivation, is that a user about to be interrupted reads less than a user who is not.
7. **Say what declining costs.** A primer MUST state, either in its body or in the declining control's own label, what happens if the user declines. Under section 7 the answer is that nothing of consequence happens and other routes remain open, and the user has no way to know that unless they are told. A surface that offers a choice while disclosing the outcome of only one branch is asking for agreement to something undisclosed, whatever its tone, and it wastes the one genuine advantage this document has over the dialog it precedes.

#### 5.6 Not at the same time as the wallet's own prompt

A primer MUST NOT be shown while a [BRC-219](/wallet/0219) wallet permission prompt is pending for the same interaction. The two dialogs ask different questions of the user, one on behalf of the browser and one on behalf of the wallet, and presenting them together produces a user who grants or denies both without distinguishing them. Loopback access is resolved first, because the wallet cannot be reached to ask its question until it is. [BRC-185](/wallet/0185) section 8.5 states the same separation for pairing confirmation.

#### 5.7 Presentation and accessibility

1. A primer MUST NOT imitate browser chrome, a browser dialog, an operating system dialog, or the loopback access prompt itself. Beyond being the disguised-dialog dark pattern, a site that successfully impersonates browser security UI is training its own users to trust an impersonation of browser security UI.
2. A primer MUST meet the accessibility requirements [BRC-185](/wallet/0185) section 5 sets for the chooser: keyboard access, visible focus, accessible names, screen-reader status updates, and a reachable cancel or back action. It MUST carry the role appropriate to the form it takes under section 9, MUST place focus within itself on open, and MUST return focus to the control that summoned it when it closes without handing off. This document does not require that form to be a modal dialog, and section 9 explains why a modal is rarely the best of the available choices.
3. **A primer MUST say what is about to happen before it hands off, and MUST then get out of the way.** The prompt is drawn in browser chrome, outside the document. The application cannot announce it, cannot describe it, cannot label its controls and cannot move focus to it. WCAG 3.2.5's understanding text records that people who are blind or have low vision may not notice that a change of context has occurred; here the application is the only party that can give notice, and it can only do so beforehand. The announcement MUST be made by the primer, as the last thing it does.
4. On an affirmative response the primer MUST release any focus trap it holds and MUST NOT retain a modal barrier over the document while the browser's decision is outstanding. The decision has left the page, and a page-level focus trap running against browser chrome fights the user agent for control of a dialog the page does not own. A waiting state under section 6.1 point 5 MAY remain, and MUST be reachable rather than trapping.
5. A primer MUST NOT auto-dismiss, MUST NOT carry a countdown, and MUST NOT be re-presented because the user has not yet answered.

### 6. The eliciting request

#### 6.1 One request, with no deadline

1. The eliciting request MUST be issued as a direct consequence of an affirmative response to the primer, within the same user activation, with no intervening navigation and no further interstitial. There MUST be nothing between the affirmative response and the browser's dialog. A loading state, a secondary confirmation or a marketing step breaks the link the primer just established and leaves the dialog looking unprompted again. This is the first of Nielsen's limits: under a tenth of a second the user feels they acted on the thing directly and no feedback is owed beyond the result, and the result owed here is the browser's question.
2. The controller MUST issue exactly one loopback request for the purpose of raising the prompt. It MUST NOT begin a [BRC-185](/wallet/0185) section 4.1 discovery round, and MUST NOT issue the 16 simultaneous requests that section permits, because every request beyond the first is wasted while the dialog is open and a fan-out can leave a trail of failures attributable to nothing.
3. The eliciting request SHOULD address the first candidate of one configured base, and MUST be an ordinary discovery request to `GET /.well-known/brc-wallet` or, for a legacy default endpoint, the unchanged `getVersion({})` probe that [BRC-185](/wallet/0185) section 4.1 permits. It introduces no new endpoint.
4. A controller MUST NOT issue an eliciting request in order to find out whether the user would have wanted the prompt, MUST NOT issue one speculatively during idle time or prefetch, and MUST NOT issue one from a code path the user did not initiate. A declined primer is a decision the controller may revisit. A denied prompt is one it cannot revisit at all.
5. **The 1000 ms per-request discovery deadline of** [**BRC-185**](/wallet/0185) **section 4.1 MUST NOT be applied to the eliciting request.** That deadline bounds a machine's response. The eliciting request is bounded by a person reading an unfamiliar dialog, and 1000 ms is shorter than that by an order of magnitude. A controller that applies it will abandon the request while the user is still deciding, and will then receive a grant it has already stopped waiting for.
6. This is [BRC-219](/wallet/0219)'s rule applied one layer out. That document states there is no elapsed-time limit on a live permission decision and requires wallets and substrates not to abort a permission-required request because wall-clock time elapsed. The same holds when the party deciding is answering the browser instead of the wallet.
7. **The 1000 ms figure is a response-time limit, and response-time limits bound machines.** Nielsen's three limits, from Miller and from Card et al., are 0.1 seconds for a reaction that feels instantaneous, 1 second for the user's flow of thought to survive, and 10 seconds for their attention to stay on the task. [BRC-185](/wallet/0185) section 4.1's per-request deadline sits exactly on the second of these and is correct there: a discovery round is a machine answering, and a round that overruns a second has cost the user their train of thought for nothing.

   A request the browser is holding open while a person reads four lines of unfamiliar security text is not a machine answering. It is a human decision, and it belongs past the third limit rather than at the second. Applying a flow-of-thought bound to it is a category error, and the cost of the error is that the user allows and the controller has already stopped listening. The same three limits say what to do instead, and section 9.5 does it: past one second, show that something is happening; past ten, expect the user to look away, give them a way to interrupt, and expect them to need reorienting when they come back.
8. A controller MAY bound the eliciting request by a user-cancellable waiting state, and MUST NOT bound it by a fixed timer. Section 9.5 governs that surface, which is the one the user is looking at while the browser holds the decision.

#### 6.2 Three outcomes, not two

The prompt has three exits, and an implementation that branches only on grant and denial mishandles the third. A controller MUST re-read the permission state after the eliciting request settles rather than inferring the outcome from the request result, because two of the three outcomes fail the request identically.

**Granted.** The state is `granted`. The controller MUST run the full [BRC-185](/wallet/0185) section 4.1 discovery round with that section's ordinary deadlines and concurrency. The result of the eliciting request MAY be retained as the first probe result if it completed; it MUST NOT be treated as a complete round, and coverage MUST NOT be reported as `complete` on the strength of an eliciting request alone.

**Blocked.** The state is `denied` and section 8 applies. The controller MUST NOT retry the eliciting request, MUST NOT issue any probe, and MUST NOT present the denial as a transport failure.

**Dismissed.** The state remains `prompt` and the request has failed. The user has answered neither question, usually because the prompt arrived while their attention was elsewhere.

1. A controller MUST NOT issue another eliciting request in response to a dismissal.
2. It MUST treat the interaction as declined, applying section 7 in full, and MUST NOT describe the outcome as a refusal of the wallet.
3. It MAY offer the primer again only under section 7.3, which requires a fresh user action on the connect surface.
4. Coverage is reported as `blocked` with cause `permission-declined` under section 10.1.

The prohibition on retrying is not politeness. Browsers place an origin under embargo after repeated dismissals, suppressing the prompt without notifying the page and without changing the permission state away from `prompt`. Chromium's `PermissionDecisionAutoBlocker` carries the thresholds: three dismissals, or four occasions on which the user simply ignores the prompt, put the origin under a seven-day embargo, and under the quiet user interface those fall to one dismissal and two ignores. Both counts and the embargo duration are field-trial parameters rather than constants of the platform.

A controller that retries therefore spends the user's tolerance and arrives at a condition in which the dialog no longer appears, the state still reads `prompt`, and every branch in section 3.2 is wrong. Because the thresholds are configurable and differ between the standard and quiet interfaces, a controller MUST NOT depend on any particular count, MUST NOT attempt to determine the threshold in force, and MUST NOT issue eliciting requests in order to discover it. One dismissal is enough to be the last one.

Note also that **ignoring the prompt counts**, not only dismissing it. A primer that hands off and then leaves the user with nothing to look at, contrary to section 9.5, is manufacturing exactly the ignores that drive an origin toward embargo.

#### 6.3 Returning the user to what they were doing

By the time a grant resolves, the user has crossed up to five surfaces: the control they operated, the primer, the browser's dialog, possibly the chooser of [BRC-185](/wallet/0185) section 5, and possibly a [BRC-219](/wallet/0219) wallet prompt. The action that started it was none of these.

1. On a grant, the controller MUST return the user to the action that caused the primer to be shown, in the state it was in. A user who pressed Buy and then answered three questions MUST arrive back at Buy, not at a connected-wallet confirmation with no onward path.
2. Where the action cannot be resumed automatically, the application MUST say what the user was doing and offer to resume it. It MUST NOT leave the user on a success screen whose only content is that a wallet is connected.
3. Connection is not an accomplishment from the user's point of view. [BRC-185](/wallet/0185) section 5.1 makes the related point in its own terms, that connection MUST NOT be presented as account authentication or as approval of a wallet operation; it is equally not the completion of the user's task. [BRC-137](/opinions/0137) section 9 states the same principle one layer up, requiring every successful login to resolve to one application session of the same shape so that application code never branches on how the user arrived. This section is that rule applied to the user rather than to the code: the session should not branch, and neither should the person.
4. This applies to a refusal too. Section 7.1 already requires the connect surface to survive a decline, and the same obligation to preserve the task applies: a user who declines MUST NOT lose the thing they were part-way through.

#### 6.4 Cancellation

1. Where the application aborts the connection request through the `AbortSignal` of [BRC-185](/wallet/0185) section 2 while the prompt is displayed, the controller MUST NOT report the abort as a user denial. The browser dialog cannot be withdrawn by the page, and the user may still answer it.
2. A grant that arrives after an abort MUST be honoured as a permission state change under section 3.5. A denial that arrives after an abort MUST be recorded as `denied`.
3. An aborted resolution rejects waiting calls with `USER_CANCELLED` and `delivery: 'not-sent'` as [BRC-185](/wallet/0185) section 6 already requires. No wallet method was dispatched, because no wallet was reached.

### 7. Declining is not a dead end

This section applies to a declined primer and to a dismissed prompt alike.

#### 7.1 Return the user to what they were doing

The controller MUST return the user to the surface they came from, with the connect surface still present and still usable.

#### 7.2 Offer the substrates that need no permission

The controller MUST offer the substrates of section 4.1 that remain available, and the encrypted mobile pairing path of [BRC-185](/wallet/0185) section 8, which reaches a wallet on the user's phone and never touches this permission. Where no candidate exists at all, the device-aware install paths of [BRC-137](/opinions/0137) and the application-supplied onboarding of [BRC-185](/wallet/0185) section 7 apply. [BRC-185](/wallet/0185) section 5 requires that pairing and installation remain distinguishable from discovered-provider rows; they are actions, not fictitious wallets.

#### 7.3 Name the state honestly

A controller MUST NOT describe a declined primer, a dismissed prompt, a denied permission or a blocked probe as the absence of a wallet, in its interface, in its coverage snapshot, or in a typed error handed to an application. This is [BRC-185](/wallet/0185) section 4.1's rule that a policy-blocked scan is `blocked` and not evidence of absence, and section 5.1's rule that an empty pending snapshot must not be described as a completed search.

#### 7.4 Re-asking

1. After a decline, a controller MUST NOT re-present the primer in the same session except in response to a fresh user action on the connect surface.
2. A controller MAY present the primer again in a later session, in response to such an action. It MUST NOT present it on an escalating schedule, and MUST NOT present it more often because the user declined.
3. A controller MUST NOT record a decline in a way that makes the connect surface unavailable later. The user declined a browser dialog, not a wallet.
4. A decline MUST NOT be stored in the selection memory of [BRC-185](/wallet/0185) section 5. It is not a provider choice.

#### 7.5 Do not penalise the refusal

A controller MUST NOT degrade, delay or withhold functionality that does not depend on a wallet because the user declined. Nielsen Norman Group's third consideration is that a permission decision must be reversible without penalty; penalising it converts a reversible refusal into a permanent one.

#### 7.6 A dismissal is not a decline, and the copy differs

A declined primer and a dismissed prompt reach the same place by different routes, and the surface cannot be the same for both. After a decline the user has not seen the browser's dialog. After a dismissal they have.

1. A dismissal surface MUST NOT say that the browser will ask, or is about to ask. It already did, and a surface that says otherwise has told the user something they can see is untrue.
2. It MUST NOT describe the browser's question again in full. The user either read it or chose not to, and restating it implies they failed to understand rather than declined to answer.
3. It MUST NOT assert why the prompt went unanswered. A dismissal is indistinguishable at the page from a user who closed the dialog deliberately, a user whose attention was taken by something else, and a user who never noticed it. The honest surface names the state, not the act, and "you dismissed" and "you declined" are both guesses.
4. It SHOULD state that nothing was shared and nothing changed. A user who closed a security dialog without answering it generally wants to know whether closing it was sufficient, and it was.
5. It MUST offer the alternatives of section 7.2, and MAY offer one further attempt, which section 7.4 point 1 already restricts to a fresh user action on the connect surface.
6. It MUST NOT count down, retry by itself, or reappear unprompted. Section 6.2 gives the reason: the browser's embargo counter does not distinguish a dismissal the user intended from one the application provoked, and the application cannot see the counter.

### 8. The denied state and recovery

#### 8.1 Detection

1. In the `denied` state a controller MUST NOT probe and MUST NOT issue an eliciting request. Neither can succeed, neither produces a dialog, and both yield only a failure the controller already knew about.
2. A controller MUST also reach this section when a loopback request fails in a manner consistent with a blocked request while the state is `unsupported`, per section 3.3 point 3.

#### 8.2 What recovery must offer

1. The recovery surface MUST state what is blocked and what the consequence is, in the user's vocabulary, under the constraints of section 5.5.
2. It MUST name the browser's own control path as the browser currently labels it, and MUST NOT invent a label. In current Chrome the relevant settings entry is `Apps on device`, which is `IDS_SITE_SETTINGS_TYPE_LOOPBACK_NETWORK` in Chromium's site settings strings, reachable from the control at the left of the address bar and from site settings. Where the application cannot determine the current label with confidence, it MUST describe the location rather than guess the wording.
3. It MUST, in the same surface, offer the alternatives of section 7.2, and those alternatives SHOULD be the primary action. The settings path SHOULD be secondary.

   This inverts the usual advice deliberately. Nielsen Norman Group's decision-reversal guidance is to link the user directly to the exact settings location, and it is good advice for the native applications it was written for, which can deep-link into operating system settings. **A web page cannot link to its own browser's settings.** It can only describe where they are and hope. So the one mechanism that makes reversal work is unavailable here, and a recovery surface that leads with an unlinkable procedure leads with the branch most likely to fail. The user who wants to repair the permission will find the instructions wherever they sit. The user who does not want to edit browser settings, which is most users, needs the other route in front of them.
4. It MUST detect repair without a reload, per section 3.5. A recovery surface MUST NOT require the user to tell it that they changed the setting, and MUST NOT offer a control whose only function is to say so.
5. A recovery surface MAY print a settings address as text for the user to copy, and MUST NOT render one as a link. A page cannot navigate to a `chrome://` address, so the link would do nothing when pressed, and a control that silently does nothing is worse than printed text that at least looks like something to copy.
6. It MUST satisfy [BRC-185](/wallet/0185) section 5.1's requirements for the "Discovery blocked or partial" state: explain the access or coverage limitation, distinguish it from wallet absence, and offer retry, phone pairing and application-supplied onboarding or install paths.

#### 8.3 What recovery must not do

1. It MUST NOT instruct the user to enable a browser flag, to disable Local Network Access checks, or to change any setting that applies to sites other than this one. That advice trades a single site's inconvenience for every site's protection. It is also the plain reading of [BRC-185](/wallet/0185) section 4.1's prohibition on working around browser local-network permissions: telling the user to disable the check is the controller working around it by proxy.
2. It MUST NOT present an unexplained error, a retry control that cannot succeed, or an indefinite spinner.
3. It MUST NOT imply that the user made a mistake. They answered a question the application failed to prepare them for.

#### 8.4 Where the permission lives, and where it does not

A recovery surface that sends the user to the wrong settings screen is worse than one that sends them nowhere. The user arrives somewhere plausible, finds no such setting, and concludes the application is broken rather than that the instruction was. The common wrong turn is the operating system, and for wallet discovery it is always the wrong turn.

**Loopback is not the local network, and the operating systems agree.** [BRC-185](/wallet/0185)'s loopback substrates are ports on the machine the browser is running on. Apple's stated position is that `127.0.0.1` and `::1` are exempt from the Local Network privacy restriction, because traffic to them never leaves the device; the restriction covers multicast, broadcast, and unicast to the local subnet. So macOS and iOS hold no second gate over a wallet on `localhost`. Windows and desktop Linux have never gated loopback for an ordinary desktop browser. On the desktop the browser's permission is the only one in the way, and every recovery path below is a browser path.

| Platform and browser                | Where the user re-grants                                                                                                                                                                                                               | Second gate in the operating system           |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Chrome or Edge, desktop             | The control at the left of the address bar, then the `Apps on device` entry. The same control is at `chrome://settings/content/loopbackNetwork`, and per site under `chrome://settings/content/siteDetails`                            | None for loopback                             |
| Chrome, Android                     | The control at the left of the address bar, then Permissions                                                                                                                                                                           | Unsettled, see below and section 11.5 point 2 |
| Safari or Firefox                   | Neither shipped this permission at the time of writing, so the state is `unsupported` and section 3.3 governs rather than this section. A controller reads the state and never this table                                              | None                                          |
| Any browser under enterprise policy | An administrator may have fixed the state, in which case the user cannot change it at all and the surface MUST NOT tell them they can. Section 8.2 point 1's statement of the consequence is the whole of what can honestly be offered | Not applicable                                |

The `Apps on device` wording is `IDS_SITE_SETTINGS_TYPE_LOOPBACK_NETWORK` in Chromium's site settings strings, distinct from `Local network`, which is the other half of the split permission and not the one wallet discovery needs. Section 8.2 point 2 governs: name it as the browser currently labels it, or describe the location and do not guess.

**Android is the one platform where this document cannot give an answer.** Section 11.5 point 2 sets out why: Chrome for Android is itself an application holding operating system permissions, it declares `android.permission.ACCESS_LOCAL_NETWORK`, and Chromium carries a distinct prompt for the case where it lacks one, reading `Connect to nearby devices?`. Android's own documentation defines the restriction over local network addresses and does not state whether loopback is among them, so whether a `loopback-network` grant on Android can be defeated by a missing operating system permission is not established here. An implementation MUST NOT assume either answer.

Section 8.2 point 4 is what covers the gap. A controller that watches the permission state and re-evaluates on change behaves correctly whichever way the Android question resolves, because it never reasons about the platform in the first place. A controller that branches on a guess about Android will be wrong on half the devices under one answer and all of them under the other.

### 9. Presentation forms

[BRC-185](/wallet/0185) section 5 declines to prescribe a form for the chooser, in those words: "Modal styling is not prescribed." Its section 5.1 permits "a modal, panel, page, or native surface" and requires only that equivalent presentations preserve that document's equal treatment, accessibility, cancellation and selection policy. This document prescribes no form either. It ranks them, because one variable dominates and it is not aesthetic.

**The browser draws its question in its own chrome, at an anchor the page does not control and cannot move.** In current desktop Chrome that anchor is beneath the address bar. The application's surface is in the viewport, below it and usually centred. Every candidate form is better or worse according to two things: how far the user's attention must travel from the application's surface to that anchor, and how many layers the application interposed on the way. A centre-screen modal maximises both.

That is also where the mobile priming literature this document adapts stops transferring. On iOS and Android the system sheet and the primer occupy the same region of the same screen, so the question of where the user looks next never arises. On desktop it is the main question, and a standard that imports the mobile pattern without noticing produces applications that explain themselves well and then point the user at the wrong half of the window.

#### 9.1 Three surfaces, three problems

| Surface          | When                     | Defined in          | What the form must protect                        |
| ---------------- | ------------------------ | ------------------- | ------------------------------------------------- |
| Primer           | Before the prompt        | Section 5           | Attention, and a handoff the user can follow      |
| Waiting state    | While the prompt is open | Section 6.1 point 5 | Orientation, with the page no longer in control   |
| Recovery surface | After a refusal          | Section 8           | Dignity, and a route that is not browser settings |

These are three different problems and an application MUST NOT adopt one form for all three without reference to this section. They differ in whether the page owns the user's attention at all: before the handoff it does, during the handoff it does not, and after a refusal it has been told it should not.

#### 9.2 Forms for the primer

| Form                                                              | Verdict                                                   | When it is right                                                                                                                                                                                   |
| ----------------------------------------------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Anchored popover**, placed toward the browser's prompt anchor   | SHOULD, default on pointer and desktop                    | The ordinary case. It shortens the attention distance to near zero by placing the explanation where the question will appear                                                                       |
| **Inline panel** inside the connect surface or chooser            | SHOULD, whenever one is already open                      | Adds no layer at all. A chooser under [BRC-185](/wallet/0185) section 5 is already the surface the user is attending to                                                                            |
| **Inline disclosure** at the value control the user just operated | SHOULD, when the ask follows Buy, Post, Unlock or similar | Keeps the explanation attached to the reason for it, which is the strongest timing the evidence supports                                                                                           |
| **Bottom sheet**                                                  | SHOULD, on mobile browsers                                | Mobile browsers commonly draw permission UI low. Co-locate with it rather than with the desktop convention                                                                                         |
| **Modal dialog**                                                  | MAY                                                       | Acceptable where connecting is the user's whole task and nothing else is on screen. It costs the maximum attention distance and one full layer                                                     |
| **Full-page view or dedicated route**                             | SHOULD NOT                                                | A navigation is itself a change of context under WCAG 3.2.5, and it discards the task the user was in the middle of. Acceptable only where the user deliberately navigated to a connect route      |
| **Toast, snackbar, banner, or any auto-dismissing surface**       | MUST NOT                                                  | It auto-dismisses, against section 5.7 point 5. It cannot carry two equally weighted controls, against section 5.4. And nothing establishes that it was read before the browser's question arrived |

#### 9.3 Choosing among them

Apply in order. The first matching row governs.

1. A connect surface or chooser is already open: inline panel within it.
2. The ask follows the user's operation of a value control: inline disclosure at that control.
3. Pointer or desktop presentation: anchored popover.
4. Mobile browser presentation: bottom sheet.
5. None of the above: modal dialog.

A full-page view is permitted only where the user navigated to a connect route of their own accord. An auto-dismissing surface is permitted in no case.

#### 9.4 The anchored popover, and the rule it pulls against

Recommending proximity to browser chrome and prohibiting imitation of browser chrome pull in opposite directions, and the recommended form is the one most exposed to the prohibition. Proximity is for attention. It is not for borrowed authority.

1. An anchored popover MUST read as content of the page: the page's typography, the page's surface treatment, the page's colours.
2. It MUST NOT carry a frame, pointer, arrow or border that implies continuity with the browser's own interface, and MUST NOT be positioned so as to appear contiguous with it.
3. It MUST NOT assert a position for the browser's question more precise than the application can determine. Browsers differ, and the same browser differs between desktop and mobile. Describe the region, or say nothing about position.
4. Section 5.7 point 1 bears more heavily the closer the form sits to chrome, not less. An implementation that cannot keep an anchored popover visibly distinct from browser UI MUST use an inline panel or a modal instead.

#### 9.5 The waiting state

The user is now past Nielsen's ten-second limit, which is where attention leaves the task, other work begins, and a returning user has to reorient. Each rule below follows from that.

1. The waiting state MUST be non-modal and MUST NOT block interaction with the rest of the document. The decision has left the page under section 5.7 point 4, and a page that blocks itself while browser chrome is deciding has locked a door it is not standing behind. Past ten seconds a user will want to do something else, and a blocking overlay punishes them for a delay the page did not cause and cannot end.
2. It MUST persist until the permission state resolves or the user stops it, and MUST NOT auto-dismiss. It MUST NOT be a toast, for the reasons in section 13.2.
3. It SHOULD be positioned toward the browser's prompt anchor, subject to section 9.4 point 3.
4. It MUST carry a control that stops waiting, labelled under section 5.4 point 3, and that control MUST NOT be presented as refusing the browser's question. The page cannot answer on the user's behalf. An interruptible path out is the standing requirement for any operation that runs past ten seconds.
5. It MUST remain legible to a user who has looked away and come back, which is the ordinary case at this duration. Stating where the question is, as section 5.2 point 6 requires of the primer, is what that means in practice.
6. It MUST NOT display a countdown, a progress bar implying completion, or any other representation of a deadline. There is none, under section 6.1. A percent-done indicator is the right answer for a long machine operation and the wrong one here, because nothing is progressing: a person is deciding.

#### 9.6 The recovery surface

1. The recovery surface SHOULD be an inline panel, in place of or beside the connect surface.
2. It MUST NOT be modal. A modal shown to somebody who has just refused re-opens a decision they have made, in the one form they cannot ignore, moments after being told their refusal would be respected. Nothing in its copy will repair what its form has said.
3. It MUST remain dismissible, and dismissal MUST NOT put the alternatives of section 7.2 out of reach.
4. It MUST NOT be re-presented automatically during the same visit after it has been dismissed.

#### 9.7 What the form does not change

Sections 5.2 through 5.7 apply whatever form is chosen: the four content elements, two controls of equal weight, labels that name outcomes, the language rules, the handoff announcement, and the accessibility requirements [BRC-185](/wallet/0185) section 5 sets for the chooser. That document's rule holds here in its own words: equivalent presentations MUST preserve equal treatment, accessibility and cancellation. **A form is a container, and no container relaxes a requirement.** An application that moves the primer from a modal into an inline disclosure to make it less conspicuous has not chosen a better form, it has chosen a quieter way to fail section 5.4.

### 10. Reporting

This document introduces no new error code, coverage state or connection state. It specifies how the browser permission maps onto the ones [BRC-185](/wallet/0185) already defines.

#### 10.1 Coverage

The loopback adapter's per-adapter coverage, required by [BRC-185](/wallet/0185) section 2, MUST be reported as follows. The `cause` strings below are `ConnectionError.cause` values, which [BRC-185](/wallet/0185) section 6 explicitly permits libraries to add without replacing its required fields.

| Condition                                                | Coverage                | `cause`                                         |
| -------------------------------------------------------- | ----------------------- | ----------------------------------------------- |
| `granted`, round completed over the configured scope     | `complete`              | none                                            |
| `granted`, round partially completed or probes timed out | `partial`               | as [BRC-185](/wallet/0185) section 4.1 requires |
| `prompt`, no user activation available                   | `blocked`               | `permission-required`                           |
| `prompt`, candidate already found by another adapter     | `blocked`               | `permission-not-requested`                      |
| `prompt`, primer declined                                | `blocked`               | `permission-declined`                           |
| `denied`                                                 | `blocked`               | `permission-denied`                             |
| Non-secure context                                       | `unsupported`           | `insecure-context`                              |
| Cross-origin frame without delegation                    | `unsupported`           | `permission-policy`                             |
| `unsupported`, requests not blocked                      | as the round determines | none                                            |
| `exempt`                                                 | as the round determines | none                                            |

In every `blocked` row the snapshot MUST disclose that the loopback address space was not searched, as [BRC-185](/wallet/0185) section 4.1 requires, and MUST NOT claim to have searched all ports or all installed wallets.

#### 10.2 Errors

1. Where a resolution fails and the loopback adapter's coverage is `blocked` for any permission cause, the controller MUST use the existing `DISCOVERY_BLOCKED` code with `delivery: 'not-sent'` and the `cause` from section 10.1. Delivery is always `not-sent` for these conditions, because no request reached a wallet.
2. A non-secure context and an undelegated cross-origin frame are configuration faults and MUST use the existing `CONFIGURATION_ERROR` code, reported to the embedding application or operator rather than rendered to the user as a wallet problem.
3. A permission condition MUST NOT be reported as `PROVIDER_CHANGED`, `DISCONNECTED`, `CONNECTION_CLOSED` or `PROTOCOL_ERROR`. No provider changed, no connection existed, and no protocol was violated.

#### 10.3 Connection state

[BRC-185](/wallet/0185) section 6 requires an application to be able to distinguish discovering, choosing, pairing, connected, locked or authentication required, waiting for wallet approval, suspended, disconnected and closed.

1. A controller MUST additionally distinguish **waiting for browser permission** from **waiting for wallet approval**, and MUST NOT present one as the other. They differ in who is being asked, in what has been reached, and in what a refusal costs.
2. This distinction is what repairs [BRC-219](/wallet/0219)'s binary. That document allows an application to assume that a hanging request means the wallet is asking the user. While an eliciting request is outstanding, that assumption is false and the controller is the only component that knows it. A controller MUST NOT surface a pending eliciting request to the application in a form that invites the BRC-219 inference.
3. A pending eliciting request MUST NOT be presented as a locked wallet, an unresponsive wallet, or a slow network.

#### 10.4 Remembered selections

1. A remembered loopback provider that cannot be reached because the permission state is `denied` MUST NOT be treated as missing, changed, rotated or unverifiable under [BRC-185](/wallet/0185) section 5. The provider has not changed; the route to it is closed.
2. The controller MUST preserve the remembered preference, as that section requires preferences be preserved until replaced or forgotten, and MUST NOT fall through to a different candidate on the strength of a permission failure. [BRC-185](/wallet/0185) section 5 already states that lock state is not absence and forbids silently moving to another wallet because the selected one is unavailable; the same reasoning applies.

### 11. Embedding, delegation, workers and secure context

#### 11.1 Cross-origin frames

1. Where the connect surface is in a cross-origin frame, the embedder MUST delegate the permission to that frame's origin through Permissions Policy, using the `loopback-network` feature, or the combined `local-network-access` feature on browsers that predate the split. The default allowlist is `self`, so an undelegated frame cannot obtain the permission and the prompt will never appear.
2. The embedder MUST delegate to named origins. It MUST NOT use a wildcard allowlist for this feature.
3. A frame that detects it has not been delegated to MUST NOT show a primer, because the primer would precede a dialog that cannot appear. It MUST report the condition under section 10.1 and section 10.2 point 2.

#### 11.2 Workers

Service workers and shared workers cannot raise this prompt on an origin that has not already been granted the permission. A controller that performs discovery from a worker MUST obtain the grant from a document first, under sections 3 to 7, before delegating any loopback request to a worker.

#### 11.3 Secure context

The permission is available only in a secure context. An application served over a non-secure, non-loopback context MUST NOT show a primer, because no grant is obtainable and the primer would promise a dialog that will not come. This is a deployment fault and MUST be surfaced under section 10.2 point 2, not to the user as a wallet problem.

#### 11.4 Non-browser hosts

[BRC-185](/wallet/0185) section 1 requires non-web hosts to supply an explicit application context and states that a headless caller cannot acquire browser-origin authenticity by supplying a string. Node, terminal and headless controllers have no loopback access prompt and no Permissions API. They are in the `unsupported` state of section 3.3 and MUST NOT show a primer. The reasoning of [BRC-219](/wallet/0219)'s non-interactive wallet section applies: where there is no user to ask, behaviour must be deterministic policy rather than a hanging wait.

#### 11.5 Mobile browsers

This document is shaped by the desktop case, because that is where loopback wallets run. Three things differ on a mobile browser and an implementation that ignores them will be wrong there.

1. **The prompt is drawn elsewhere.** Mobile browsers commonly place permission UI low on the screen rather than beneath the address bar, and some place the address bar itself at the bottom. Section 5.2 point 6 and section 9.4 point 3 apply: state the position for the platform actually in use, or state nothing.
2. **There is a second permission, and the page cannot see it.** On Android the browser is itself an application holding operating system permissions. Chromium's Android manifest declares `android.permission.ACCESS_LOCAL_NETWORK`, and the browser may not hold it, in which case it cannot honour a grant the user has already given the site. Chromium carries a distinct string for that case, `IDS_MESSAGE_MISSING_LOCAL_NETWORK_PERMISSION_TITLE`, reading `Connect to nearby devices?`.

   The page has no access to operating system permission state, so it cannot query this, cannot request it, and cannot distinguish it from any other failure: the Permissions API reports the site's own grant and nothing about the browser's. A controller MUST NOT present a failure arising from the browser's own missing operating system permission as the user's refusal or as an absent wallet. Where the site's own state reads `granted` and loopback requests still fail, a controller SHOULD say that the browser may need permission of its own, which is the only honest account available to it.
3. **Loopback is usually the wrong substrate here anyway.** A mobile browser reaching a wallet on the same handset is possible and uncommon; the ordinary mobile path is the encrypted pairing of [BRC-185](/wallet/0185) section 8, which needs none of this. A controller on a mobile browser SHOULD order pairing ahead of loopback under section 4.1, and SHOULD NOT spend a permission on a substrate it does not expect to find.

### 12. The grant is for wallet discovery

12.1 An origin granted loopback access can reach every service listening on the user's own machine, which is precisely the capability Local Network Access exists to restrict and precisely the fingerprinting surface it exists to close. [BRC-151](/opinions/0151) already names malicious localhost requests and DNS rebinding among the threats implementations must defend against. The grant obtained under this document is obtained by telling a user it is for their wallet.

12.2 A conforming controller MUST confine its loopback requests to wallet discovery and wallet communication: the address space of [BRC-185](/wallet/0185) section 4.1, any endpoint the user or application explicitly configured, and the connection established from them. It MUST honour that section's requirement to deduplicate identical scheme, host and port probes, which now also bounds what the grant is used for.

12.3 A conforming controller MUST NOT use the grant to enumerate unrelated local services, to profile installed software, to detect other applications, or to derive any identifier from the result of such a scan, whether or not the result is transmitted. [BRC-185](/wallet/0185) section 3 already requires the discovery record and candidate inventory to remain local to the application unless the user explicitly requests sharing them.

12.4 An application that needs loopback access for any other purpose MUST obtain it through its own surface, with its own explanation, and MUST NOT present that purpose as part of connecting a wallet.

### 13. Conformance

#### 13.1 State table

An implementation conforms if, for every entry, it exhibits the behaviour given.

| Condition                                                                   | Required behaviour                                                                                                           |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Before first loopback request                                               | `navigator.permissions.query({name: "loopback-network"})`, falling back to `local-network-access`                            |
| Query rejects both names                                                    | State `unsupported`; no primer; probing permitted; a blocked request surfaced as `blocked` coverage                          |
| Document origin is loopback                                                 | State `exempt`; no primer; probing permitted                                                                                 |
| State `granted`                                                             | No primer; round proceeds with BRC-185 deadlines; no interruption                                                            |
| State `prompt`, non-loopback adapter already yielded a selectable candidate | No primer; no eliciting request; coverage `blocked`, cause `permission-not-requested`                                        |
| State `prompt`, no user activation                                          | No primer; no eliciting request; coverage `blocked`, cause `permission-required`; resolve on other adapters                  |
| State `prompt`, user unlikely to hold a wallet                              | Install and pairing routes offered before or alongside the primer                                                            |
| State `prompt`, user operates a connect or value control                    | Primer, per section 5; value control preferred where both exist                                                              |
| Primer content                                                              | Names the gap between the browser's wording and the use; claims no narrowness in the grant                                   |
| Value-control path, user has never connected                                | Primer carries the two-line connect explanation the connect surface would have                                               |
| Primer form chosen                                                          | Per section 9.3's ordered rule; never an auto-dismissing surface                                                             |
| Primer, before handing off                                                  | States that the browser is about to ask; announcement is the last thing the primer does                                      |
| Prompt outstanding                                                          | Waiting state non-blocking, persistent, cancellable, no deadline shown                                                       |
| Recovery surface form                                                       | Inline panel, never modal, dismissible without losing the alternatives                                                       |
| Primer, affirmative response                                                | One eliciting request, same user activation, no 1000 ms deadline, no fan-out; focus trap released, no modal barrier retained |
| Eliciting request settled                                                   | State re-read, not inferred from the request result                                                                          |
| Prompt granted                                                              | Run full BRC-185 round; do not report `complete` from the eliciting request alone                                            |
| Grant resolved                                                              | User returned to the action that caused the primer, in the state it was in                                                   |
| Prompt blocked                                                              | State `denied`; no retry; not presented as a transport failure                                                               |
| Prompt dismissed                                                            | State still `prompt`; no second eliciting request; treated as declined; coverage `blocked`, cause `permission-declined`      |
| Dismissal surface                                                           | Does not say the browser will ask, does not restate the question, does not assert why it went unanswered                     |
| Recovery surface, settings path                                             | Named as the browser labels it or described by location; printed as text, never as a link                                    |
| Recovery surface, repair                                                    | Noticed without a reload and without the user asserting it; no control whose only function is to say the setting changed     |
| Recovery surface, policy-fixed state                                        | Does not tell the user they can change what an administrator has fixed                                                       |
| Primer declined                                                             | No eliciting request; connect surface retained; alternatives offered; coverage `blocked`, cause `permission-declined`        |
| Abort while prompt displayed                                                | Not reported as denial; later grant honoured; `USER_CANCELLED`, `delivery: 'not-sent'`                                       |
| State `denied`                                                              | No requests; no primer; recovery surface with repair path and alternatives; coverage `blocked`, cause `permission-denied`    |
| State `denied`, remembered loopback provider                                | Preference preserved; not treated as missing, changed or unverifiable                                                        |
| Mobile browser, grant held but requests still fail                          | Not reported as refusal or absence; browser may lack its own OS permission                                                   |
| Repair detected while page open                                             | State re-evaluated and round re-run without reload                                                                           |
| Eliciting request outstanding                                               | Presented as waiting for browser permission, never as waiting for wallet approval                                            |
| Cross-origin frame, not delegated                                           | No primer; coverage `unsupported`, cause `permission-policy`; `CONFIGURATION_ERROR`                                          |
| Non-secure, non-loopback context                                            | No primer; coverage `unsupported`, cause `insecure-context`; `CONFIGURATION_ERROR`                                           |
| Any state                                                                   | No loopback request outside the wallet discovery address space                                                               |

#### 13.2 Surfaces that fail conformance

Each of the following is a conformance failure on its own. The list is not exhaustive of section 13.1, which is normative; these are the ones that are non-obvious, or that are already deployed somewhere.

1. A discovery round on page load, raising the prompt before the user has acted.
2. An eliciting request subject to the 1000 ms discovery deadline, abandoning the user's grant while they are still reading.
3. A dismissed prompt answered with a second eliciting request, walking the origin toward silent suppression.
4. An outcome inferred from the failed request rather than re-read from the permission state, which cannot distinguish a block from a dismissal.
5. A primer shown when the state is `granted`, when the query is unsupported, or from a loopback-origin development server, interrupting a user for a dialog that will not appear.
6. A primer that leaves the browser's wording unaddressed, so the user decides on `other apps and services on this device` with nothing to compare it against.
7. A primer claiming the grant is narrow, that the browser is overstating the case, or that its question may be disregarded.
8. A primer claiming the application looks only for a wallet, in an implementation that scans anything else.
9. A primer that quotes the browser's control labels, which are absent in any browser localised differently from the page.
10. A primer whose controls are labelled `Continue` and `Cancel`, or any pair that names no outcome.
11. A dismissal surface telling the user their browser is about to ask, when it already did and they closed it.
12. A dismissal surface asserting that the user declined, when the page cannot tell a deliberate close from a user who never saw the dialog.
13. A recovery surface sending the user to operating system settings for a loopback permission the operating system does not hold.
14. A `chrome://` settings address rendered as a link, which does nothing when pressed because a page may not navigate there.
15. A recovery surface with an `I have changed the setting` control, in place of the detection section 8.2 point 4 requires.
16. A primer whose decline control is a text link beside a filled proceed button, or is labelled `Block` or `Deny`, presenting a reversible refusal as a permanent one.
17. A primer that reproduces the browser's dialog styling, or is framed or positioned to appear contiguous with browser chrome. The recommended anchored form makes this easy.
18. A primer that hands off without having said that the browser is about to ask, leaving a screen-reader user with a dialog that closes and nothing else.
19. A primer that holds a focus trap or a modal barrier over the document while the browser's dialog is outstanding.
20. A toast, snackbar, banner or any other auto-dismissing surface used as a primer.
21. A value-control primer shown to a user who has never connected a wallet and was never told what connecting is, because no connect surface appeared on that path.
22. A newcomer with no wallet taken through the browser's dialog and then told to install one.
23. A grant that lands the user on a connected-wallet confirmation instead of the action they started.
24. A denied state rendered as `No wallet available over any communication substrate`, as `complete` coverage, or as `PROVIDER_CHANGED` or `DISCONNECTED`.
25. A pending eliciting request presented as a wallet awaiting approval.
26. A recovery surface that tells the user to open `chrome://flags`, or that leads with browser settings instructions the page cannot link to ahead of a route the user can take.
27. A mobile failure caused by the browser's own missing operating system permission, reported as the user's refusal or as an absent wallet.
28. A grant obtained for a wallet and reused to scan other local ports.

## What Is Checkable, and What Is Taken on Trust

This document defines no cryptographic value, so nothing in it is checkable in the sense the rest of this corpus usually means. What it defines is an order of events and the content of three surfaces, and both are checkable by observing an implementation rather than by verifying a number. That is a weaker guarantee and it is worth saying which claims it does and does not reach.

| Fact                                                              | Who can check it             | How                                                                                                |
| ----------------------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------- |
| No loopback request preceded the permission query                 | Anybody with developer tools | The network panel, against section 3.1                                                             |
| The prompt followed a user action rather than page load           | Anybody                      | Load the page and wait. Nothing should happen                                                      |
| A primer preceded the prompt                                      | Anybody                      | The order of the two surfaces                                                                      |
| The eliciting request was one request, not a fan-out              | Anybody with developer tools | One entry, not sixteen, against section 6.1 point 1                                                |
| The discovery deadline was not applied to it                      | Anybody                      | Hold the dialog open past 1000 ms. The grant must still produce a round                            |
| The grant is used only for wallet discovery                       | Anybody with developer tools | Every loopback request addresses the [BRC-185](/wallet/0185) section 4.1 space, against section 12 |
| A blocked scan was reported as `blocked`, not as absence          | The embedding application    | The coverage snapshot, against section 10.1                                                        |
| A denial produced `DISCOVERY_BLOCKED` with `delivery: 'not-sent'` | The embedding application    | The typed error, against section 10.2                                                              |
| The decline control is a peer of the proceed control              | Anybody                      | Rendered size, contrast and placement, against section 5.4 point 4                                 |
| No browser control label was quoted                               | Anybody, in any locale       | The primer's text, against section 5.3 point 6                                                     |

| Claim                                                            | Why it cannot be checked                                                                                                                    | What stands in for a check                                                            |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| The user understood the question                                 | No mechanism reaches a mind                                                                                                                 | The content rules of section 5.2 and the comprehension testing of section 5.5 point 5 |
| The user was not persuaded past their own judgement              | Tan et al. found that the presence of an explanation raises approval regardless of what it says, so a grant cannot be read as comprehension | Sections 5.3 and 5.4, which constrain the means rather than the outcome               |
| The stated position of the browser's question is true here       | The page cannot read browser chrome, and browsers differ                                                                                    | Section 9.4 point 3, which permits saying nothing instead                             |
| The grant will not be abused after it is obtained                | A conforming discovery scan and an abusive one are the same API call made twice                                                             | Section 12, which makes the abuse nameable, and the devtools check above              |
| The browser holds its own operating system permission on Android | The page cannot query it and the Permissions API keeps reporting the site's state                                                           | Section 11.5 point 2's reporting rule                                                 |
| The prompt was dismissed rather than blocked                     | Both fail the request identically and neither is visible to the page                                                                        | Re-reading the permission state, section 6.2                                          |
| A wallet exists that discovery did not find                      | Coverage is completion of a configured scope, not proof of absence                                                                          | [BRC-185](/wallet/0185)'s coverage vocabulary, used unchanged in section 10.1         |
| The primer's copy is comprehensible to this user                 | Readability is a property of a user, not of a text                                                                                          | Section 5.5's constraints, which are a proxy and are labelled as one                  |

The first table is what a reviewer can establish about a deployed implementation in an afternoon. The second is what no reviewer can establish about any implementation, and the honest position is that this document converts several of those into obligations that can be audited rather than into facts that can be proved.

## What It Buys, and What It Costs

| Party         | What they get                                                                                                                          | What it costs them                                                                                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User          | A question asked at a moment they chose, in words that name what is actually wanted, and a refusal that costs them nothing             | One more surface before the browser's, and a grant that is genuinely broader than the use it was explained by                                                                     |
| Application   | A grant obtained rather than spent, discovery failures it can describe truthfully, and users who do not leave at an unexplained dialog | A Permissions API dependency, three surfaces to build and maintain, discovery that may not begin on load, and some users who decline the primer and would have allowed the dialog |
| Wallet vendor | Reachability that survives a browser release which would otherwise have ended loopback discovery quietly                               | Nothing. No wallet changes                                                                                                                                                        |
| Browser       | Its permission answered by a prepared user rather than an ambushed one                                                                 | Nothing                                                                                                                                                                           |
| Ecosystem     | Fewer reflexive refusals, and one less source of the habituation every site draws on                                                   | Nothing this document adds                                                                                                                                                        |

Two of these deserve saying plainly.

**The user pays a surface to avoid an ambush, and that is a real cost.** This document adds a step to a flow that had none, in a corpus whose onboarding documents exist to remove steps. [BRC-110](/opinions/0110) is about zero friction and [BRC-137](/opinions/0137) is about not meeting a visitor with an obstacle. The defence is that the step being added is small and recoverable and the step being avoided is neither, and that a user who refuses an unexplained dialog has not been spared friction but handed a permanent one. A user who thinks a site should simply probe and take its chances is disagreeing with a decision made on purpose.

**The application pays most, and some of what it pays is grants it would have had.** A primer gives a user a free way to say no, and some who would have pressed the browser's allow will press the primer's decline instead. That is the mechanism working rather than failing, because those users remain askable next week and a browser-level refusal would not have been. But it is a cost, it falls on the party doing the work, and a document that claimed priming is free in both directions would be arguing from only half of its own evidence.

## Security Considerations

**A primer raises grant rates, and that is the hazard as well as the purpose.** Tan et al. measured a significant increase in approvals from the mere presence of an explanation, including explanations that conveyed nothing. A standard that mandates explanation therefore mandates a technique that works on users who have not understood. The mitigations here are structural rather than exhortative: section 5.2 fixes what may be said and forbids claims the application cannot support, section 5.4 forbids weighting the choice, section 4.1 forbids spending the permission at all when another adapter has already answered, and section 12 confines what the resulting grant may be used for. None of them prevents a determined bad actor from writing a persuasive primer. The permission itself remains the security control; the primer is a comprehension control, and this document does not claim otherwise.

**Imitating browser chrome is the serious attack this pattern invites.** A primer that looks like a browser dialog is more effective, and an ecosystem whose standard permits it trains users to accept browser-looking dialogs drawn by web pages. That is a transferable harm: the user so trained is more vulnerable on every other site. Section 5.7 point 1 is a prohibition rather than a recommendation for that reason.

**The grant is broader than the ask.** Loopback access reaches every listening service on the machine, not only a wallet. [BRC-151](/opinions/0151) names malicious localhost requests and DNS rebinding as threats wallet implementations must defend against, and Local Network Access is the browser-side counterpart of that defence. The user is told the grant is for their wallet because that is the truthful reason the application wants it, and section 12 binds the application to that reason. An application that honours sections 1 through 11 and ignores section 12 has used an honest explanation to obtain a dishonest capability, and is non-conforming.

**Denial must stay cheap for the user and expensive for nobody.** Section 7.4 forbids penalising a refusal. The reason is security-relevant, not merely courteous: a user who learns that refusing a permission breaks unrelated functionality learns to grant permissions reflexively, which is the habituation that Akhawe and Felt measured at 70.2 percent clickthrough on Chrome's SSL warning. Every application that penalises refusal spends down a resource shared by every other application.

**Retrying a dismissed prompt is an availability attack an application runs against itself.** Dismissal leaves the permission state at `prompt`, so a naive controller reads a state that says "you may ask" and asks again. Browsers suppress the prompt after repeated dismissals without notifying the page and without changing that state, so the end condition is an origin that can never raise the dialog, reports `prompt`, and takes every `prompt` branch in section 3.2 incorrectly. The application has made itself permanently unable to reach a wallet that is running, and has no signal telling it so. Section 6.2 forbids the retry, and section 7.3 requires a fresh user action before the primer returns.

**The eliciting request has no deadline, and that is a deliberate denial-of-service tradeoff.** Section 6.1 removes the only timer bounding a request that the browser holds open. A page that issues an eliciting request and never resolves it holds a connection and a waiting state indefinitely. The mitigation is that the waiting state MUST be user-cancellable, that only one eliciting request may be outstanding, and that the alternative, a fixed timer, discards real user grants. [BRC-219](/wallet/0219) made the same tradeoff for wallet prompts and for the same reason.

## Privacy Considerations

**Priming does not make loopback discovery safe for the user's privacy.** [BRC-185](/wallet/0185) section 4.1 discovery enumerates up to 192 addresses and the result is a fingerprinting signal about which wallet software is installed. That property is unchanged by this document. What changes is that the user agreed to it having been told what it was for, which is the difference between a disclosed surface and an undisclosed one.

**The permission state is a stable signal about the user.** A `denied` state persists across sessions and is readable by any site. This document does not create that signal; the Permissions API does. Controllers SHOULD NOT transmit or persist the state beyond what the session requires, MUST NOT combine it with other signals for identification, and MUST NOT place it in the selection memory of [BRC-185](/wallet/0185) section 5, which that section restricts to provider identity material.

## Implementations

This document is implementable today against shipped APIs, with no new dependency and no change to any wallet.

**The read.** `navigator.permissions.query({name: "loopback-network"})` returns a `PermissionStatus` whose `state` is `granted`, `prompt` or `denied`, and whose `change` event satisfies section 3.5. An unrecognised descriptor name rejects with a `TypeError`, which is the feature detection section 3.3 requires. Browsers that shipped Local Network Access before the permission was split into `local-network` and `loopback-network` expose the combined `local-network-access` name, which reports `denied` if either granular permission is denied; the two-step query in section 3.1 covers both generations.

Checked against Google Chrome 155.0.8059.80: the query resolves rather than rejecting and reports `prompt`; `loopback-network`, `local-network` and the legacy `local-network-access` are all accepted as descriptor names while an invented name is rejected as an invalid descriptor; and the `change` event fires on every transition, including the `denied` to `prompt` transition that section 3.5 point 2 depends on, through both the `onchange` property and `addEventListener`.

**The request.** Ordinary [BRC-185](/wallet/0185) section 4.1 discovery over `fetch`. Where a configured endpoint is a public hostname that resolves to a loopback address, the request must set `targetAddressSpace: "loopback"`, without which an HTTPS document's request to an HTTP loopback endpoint fails mixed content checks before the permission is ever consulted. Private IP literals and `.local` names do not need it.

**No preflight header is required, and stale guidance says otherwise.** Local Network Access replaced Private Network Access specifically by gating on a permission rather than on a preflight. A wallet listener does not need to emit `Access-Control-Allow-Private-Network`, and [BRC-185](/wallet/0185) section 4.1's existing CORS rules, including that preflight is noninteractive and that the exact permitted origin is returned, are unchanged. Implementers who find documentation requiring that header are reading Private Network Access material.

**Delegation.** `Permissions-Policy: loopback-network=("https://wallet-connect.example")` as a response header, or `allow="loopback-network"` on an iframe, satisfies section 11.1. The default allowlist is `self`.

**Where this belongs in a stack.** The natural home is the external connection controller of [BRC-185](/wallet/0185) section 2, which already owns adapter coverage, the candidate snapshot, typed connection errors and the no-wallet surfaces, and is already required by section 5.1 to distinguish blocked discovery from wallet absence. Sections 3, 4, 6 and 10 of this document are controller logic. Sections 5, 8 and 9 are two presentation components, a primer dialog and a recovery panel, which belong beside the chooser as replaceable adapters under [BRC-185](/wallet/0185) section 7. A wrapper that supplies its own connect experience, including the commercial onboarding wrappers that section anticipates and the chooser described in the Sponsored Onboarding and Wallet Choice proposal, inherits these requirements for the surfaces it replaces, and section 4.4 applies: controller and wrapper must not each show a primer.

**Verification.** There are no cryptographic values and no wire format here, so there is nothing to publish as test vectors. An implementation is checked against the state table in section 13.1, which is directly automatable. A headless browser with the permission pre-set to each of `granted`, `prompt` and `denied` covers three rows; a build with the descriptor name removed covers `unsupported`; serving the page from a loopback origin covers `exempt`; and an undelegated cross-origin frame and a plain HTTP origin cover the two configuration faults. The deadline row of section 13.1 is tested by holding the dialog open for longer than 1000 ms and asserting that the subsequent grant still produces a discovery round.

## Failure Modes

The claim running through this document is that every bad outcome here is attributable to a named party, and that most of them are attributable to the controller. This is that claim written out. The last column carries the argument: in only one row is the user implicated, and in that row nothing went wrong.

| What went wrong                                             | How it shows                                                                              | Detected by                                    | Attributable to                                |
| ----------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- |
| The prompt was raised on page load                          | A security dialog with no preceding action                                                | The user, and anybody with developer tools     | The controller                                 |
| The permission state was never read                         | A blocked scan is reported as an absent wallet                                            | The embedding application                      | The controller                                 |
| The discovery deadline was applied to the eliciting request | The user allows, and nothing connects                                                     | The user, who has no idea why                  | The controller                                 |
| A dismissal was retried                                     | The dialog stops appearing while the state still reads `prompt`                           | Nobody, until discovery fails silently forever | The controller                                 |
| A block and a dismissal were not distinguished              | A recovery surface shown to somebody who dismissed, or none shown to somebody who blocked | Nobody from the page                           | The controller, for inferring from the request |
| The browser's prompt was attributed to the wallet           | A conforming wallet is reported as hanging, against [BRC-219](/wallet/0219)               | The application, and the wallet vendor accused | The controller                                 |
| A cross-origin connect frame was not delegated to           | No prompt ever appears and discovery simply fails                                         | The embedder, eventually                       | The embedding application                      |
| The document was served over a non-secure context           | The same, with no grant obtainable                                                        | The operator                                   | Deployment                                     |
| The browser lacks its own Android permission                | A grant is held and requests still fail                                                   | Nobody from the page                           | The browser. Not the user, and not the wallet  |
| The primer imitated browser chrome                          | A user trusts a page-drawn security dialog somewhere else, later                          | Nobody, ever                                   | The application, and the whole ecosystem pays  |
| The grant was reused to scan unrelated ports                | Nothing the user can see                                                                  | Anybody with developer tools                   | The application                                |
| The user refused                                            | Nothing went wrong                                                                        | Everybody                                      | Nobody. This is the system working             |

## Appendix A: Worked Copy

These examples illustrate sections 5.2 to 5.5. They are not a required wording, and section 5.5 point 5 applies: test with real users rather than adopting these as given.

Three conventions run through all of them, and each is a rule rather than a preference. **This site** is the only term used for the application, never this page, the app, we or us, per section 5.5 point 6. **No browser control is quoted by label**, only described by role, per section 5.3 point 6, because the dialog is rendered in the browser's language and not the document's. And every control names an outcome, per section 5.4 point 3.

**A primer in the `prompt` state, where no wallet is yet known.**

> **Your browser's question is broader than this site needs**
>
> It asks whether this site may reach apps and services on this device. That covers everything running here.
>
> This site looks for one thing, your wallet, and your keys stay inside it. The question appears near the top of your browser window.
>
> `[ Connect my wallet ]` `[ Other ways to connect ]`

That is section 5.2 point 5 carrying the weight. The heading names the mismatch, the second paragraph states what the browser's sentence covers, and the third states what this site does with it. Nothing claims the grant is narrow, which it is not, and nothing suggests the browser is overstating the case, which section 5.3 point 4 forbids. The claim in the third paragraph is only available because section 12 binds the implementation to it.

**A primer where the document is served from a platform or preview host, per section 5.2.** The address is shown because the browser is about to show it and nothing else.

> **Next, your browser will ask about apps on this device**
>
> Your wallet runs on this computer. This site never sees your keys.
>
> Your browser will ask whether `checkout-9f2c.preview.example`, which is this site, may reach apps running here. The question appears near the top of your browser window.
>
> Refusing there lasts until you change your browser settings. Declining costs nothing.
>
> `[ Connect my wallet ]` `[ Other ways to connect ]`

That last line is section 5.5 point 7 doing its work, in nine words. The two refusals available to the user have different consequences, one lasting and one not, and nobody can infer which is which from the surface alone. The whole body is 49 words against section 5.5 point 6's ceiling of 50, which is the budget this surface actually has.

**A primer where the wallet is known from a verified descriptor.**

> **Reconnect Metanet Explorer?**
>
> Metanet Explorer is running on this computer, and your keys stay inside it.
>
> Your browser will ask whether this site may reach apps and services here. The only one this site wants is Metanet Explorer. The question appears near the top of your browser window.
>
> `[ Reconnect Metanet Explorer ]` `[ Use my phone instead ]`

**A primer on the value-control path, where no connect surface carried the explanation.** Section 5.2's second disclosure applies: the user pressed Buy, has never connected a wallet, and was never told what connecting is. The two lines are [BRC-110](/opinions/0110) section 6's bound.

> **Your browser's question is broader than this site needs**
>
> Paying here uses a wallet you hold. The account is yours and this site does not keep it.
>
> Your browser will ask whether this site may reach apps and services on this device. The only one this site wants is your wallet.
>
> `[ Pay with my wallet ]` `[ Other ways to pay ]`

The second paragraph is the whole of the argument for a wallet rather than an email address, and it is two lines because that is all the evidence supports carrying here. The controls name the user's task, not the plumbing, which is section 5.1 point 2 and section 5.4 point 3 agreeing.

**A waiting state while the eliciting request is outstanding.** The heading is the one thing the user has to do, so it is the heading.

> **Look near the top of your browser window**
>
> Your browser is asking whether this site may reach apps on this device. The question sits just under the address bar.
>
> Take as long as you need. There is no time limit.
>
> `[ Stop waiting ]`

**A recovery surface in the `denied` state.** This is a section 8 surface and not a primer, so section 5.5 point 6's 50-word ceiling does not bind it. It is longer because it has to teach a procedure, and the sentence discipline still applies.

> **Your browser is blocking this site from reaching your wallet**
>
> A wallet may be running on this computer, but this site cannot see it. Your browser blocks that until you allow it.
>
> You can change this in your browser settings for this site. Look for `Apps on device`. The control is at the left of the address bar.
>
> You do not have to. A wallet on your phone works instead, and nothing else on this site is affected.
>
> `[ Connect with my phone ]` `[ How to change this setting ]`

Two cautions on the examples above. The stated position of the browser's question is accurate for current desktop Chrome and is close enough for desktop Firefox and Safari, and it is wrong on several mobile browsers, which draw the question low rather than high. An application MUST say where the question will actually appear on the platform it is running on, or say nothing about position. `Apps on device` is likewise the label current Chrome uses, and section 8.2 point 2 forbids guessing it where it cannot be determined.

**A surface after the prompt was dismissed.** Section 7.6 applies. The user has already seen the browser's question, so it is not described again, and nothing claims to know why it went unanswered.

> **Your browser's question is still unanswered**
>
> Nothing was shared and nothing changed. You can answer it, or connect another way.
>
> `[ Ask me again ]` `[ Use my phone instead ]`

Still unanswered is the state rather than the act, per section 7.6 point 3. The second line is section 7.6 point 4: a user who closed a security dialog wants to know whether closing it was enough, and it was. Neither control is weighted, because the application has no view on which the user should pick.

**A recovery surface in the `denied` state.** Section 8.2 point 3 puts the alternative first and the settings path second, which reverses the usual advice for the reason given there.

> **This site cannot reach a wallet on this computer**
>
> Your browser is set to block it. Connecting from your phone works instead, and needs nothing changed here.
>
> To undo the block, open the control at the left of the address bar and turn on apps on device.
>
> `[ Use my phone instead ]` `[ Not now ]`

No control claims to open settings, because no control can. The instruction is a sentence rather than a link, per section 8.2 point 5. There is no control saying the setting has been changed, because section 8.2 point 4 requires the surface to notice by itself. And the phone route is first because it is the one that works without the user leaving the page, which is section 8.2 point 3's whole argument.

**Copy that fails this document.**

> `Allow localhost access to enable BRC-100 substrate discovery on ports 3301-3364.`

Fails section 5.5 point 1 four times over, and tells the user nothing about what they get.

> `We need this permission for a better experience. [Allow (Recommended)] Not now`

Fails section 5.2 by promising an uncheckable benefit, section 5.4 point 5 by badging a recommendation, and section 5.5 point 4.

> `No wallet available over any communication substrate.`

Fails section 7.3. The controller knows the permission was denied, and says something else.

> **Connect your wallet** \[...] `[ Continue ]` `[ Cancel ]`

Fails section 5.4 point 3. Neither control names an outcome, so a user who scans the buttons, which is most users, learns nothing from the surface at all and has to read the body to discover what the two branches are. `Cancel` is the worse of the two, because it does not distinguish cancelling the browser's question from cancelling the connection from cancelling whatever the user was doing before any of this started.

> `Your browser will warn you about accessing other apps. Ignore it, we only need your wallet.`

Fails section 5.3 point 4 twice and section 5.2's forbidden claims once. It tells the user the browser is overstating the case and invites them to disregard a security control, and "we only need your wallet" is true of the use and false of the grant, which is the collapse that point forbids. The honest form of the same sentence names both: the question covers everything running here, and this site looks only for your wallet.

> `Click Allow to continue. Blocking is permanent.`

Fails three ways. It quotes a control label that will not be present in a browser localised differently from the page, against section 5.3 point 6. It instructs the user which branch to take, against the same point. And it is false: a blocked permission is reversible in browser settings, so a primer that calls it permanent has frightened the user out of a choice that was theirs to make and will not be believed on anything else it said.

> **Connect your wallet** \[...] `[ Allow ]` `[ Block ]`

Fails section 5.4 point 6 and section 5.7 point 1 together. It borrows both of the browser's control labels and so reads as the browser's dialog, while the refusal it offers is reversible and the one it imitates is not. The user who presses this `Block` believes they have done something they have not, and the user who presses the real one a moment later believes they are repeating themselves.

## References

* [BRC-5: HTTP Wallet Communications Substrate](/wallet/0005)
* [BRC-6: XDM Wallet Communications Substrate](/wallet/0006)
* [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)
* [BRC-110: Zero-Friction, Mobile-First Onboarding for MetaNet-Enabled Apps](/opinions/0110)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)
* [BRC-137: Device-Aware Wallet Onboarding and Fallback Login for BRC-100 Applications](/opinions/0137)
* [BRC-151: BRC-100 Risk Assessment and Best Integration Practices](/opinions/0151)
* [BRC-181: Signed Spending Policies for Autonomous Agents](/wallet/0181)
* [BRC-184: Optional Metadata Registries and Their Stewardship](/wallet/0184)
* [BRC-185: Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections](/wallet/0185)
* [BRC-219: Wallet Permission Prompt Liveness Contract](/wallet/0219)
* BRC-300: Sponsored Onboarding and Wallet Choice. Not yet merged; [pull request #224](https://github.com/bsv-blockchain/BRCs/pull/224)
* BRC-326: Wallet Failure Presentation. Not yet merged; [pull request #332](https://github.com/bsv-blockchain/BRCs/pull/332)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [WCAG 2.2 Success Criterion 3.2.5: Change on Request](https://www.w3.org/WAI/WCAG22/quickref/#change-on-request). W3C Web Content Accessibility Guidelines, Level AAA. `Changes of context are initiated only by user request or a mechanism is available to turn off such changes.`
* [Understanding SC 3.2.5: Change on Request](https://www.w3.org/WAI/WCAG22/Understanding/change-on-request.html). W3C. The definition of a change of context, the affected user groups, and the note that advance warning reduces confusion.
* [Local Network Access](https://wicg.github.io/local-network-access/). Web Incubator Community Group. The permission replaces Private Network Access preflights; a denied permission fails the fetch with a network error.
* [Local network access](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Local_network_access). MDN Web Docs. Permission names, Permissions Policy features, and `targetAddressSpace`.
* [New permission prompt for Local Network Access](https://developer.chrome.com/blog/local-network-access). Chrome for Developers, 9 June 2025, updated 29 September 2025.
* [components/permissions\_strings.grdp](https://chromium.googlesource.com/chromium/src/+/HEAD/components/permissions_strings.grdp). Chromium source. `IDS_LOOPBACK_NETWORK_PERMISSION_FRAGMENT`, `IDS_LOOPBACK_NETWORK_INFOBAR_TEXT` and `IDS_PERMISSION_DENY`.
* [components/site\_settings\_strings.grdp](https://chromium.googlesource.com/chromium/src/+/HEAD/components/site_settings_strings.grdp). Chromium source. `IDS_SITE_SETTINGS_TYPE_LOOPBACK_NETWORK`, which is the `Apps on device` label.
* [components/permissions/permission\_decision\_auto\_blocker.cc](https://chromium.googlesource.com/chromium/src/+/HEAD/components/permissions/permission_decision_auto_blocker.cc). Chromium source. The embargo thresholds: three dismissals or four ignores, seven days, and one and two under the quiet interface, all field-trial configurable.
* [chrome/android/java/AndroidManifest.xml](https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/AndroidManifest.xml). Chromium source. Chrome's declaration of `android.permission.ACCESS_LOCAL_NETWORK`.
* [Permissions: query() method](https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query). MDN Web Docs. `TypeError` on an unsupported descriptor name.
* [Is the local networks privacy impacting 127.0.0.1?](https://developer.apple.com/forums/thread/650810). Apple Developer Forums. An Apple engineer states that loopback addresses are exempt from the Local Network restriction because that traffic does not leave the device, while multicast, broadcast and local-subnet unicast remain restricted.
* [Local network permission](https://developer.android.com/privacy-and-security/local-network-permission). Android Developers. `ACCESS_LOCAL_NETWORK`, enforced for apps targeting Android 17 and above. Defines the restriction over local network addresses and does not state whether loopback is among them.
* Maria Rosala. [3 Design Considerations for Effective Mobile-App Permission Requests](https://www.nngroup.com/articles/permission-requests/). Nielsen Norman Group, 28 April 2019.
* Jakob Nielsen. [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/). Nielsen Norman Group, 1 January 1993, updated 2014. Excerpted from *Usability Engineering*, chapter 5, crediting Miller (1968) and Card et al. (1991).
* Joshua Tan, Khanh Nguyen, Michael Theodorides, Heidi Negron-Arroyo, Christopher Thompson, Serge Egelman, and David Wagner. [The Effect of Developer-Specified Explanations for Permission Requests on Smartphone User Behavior](https://people.eecs.berkeley.edu/~daw/papers/perm-chi14.pdf). CHI 2014, pages 91 to 100.
* Adrienne Porter Felt, Elizabeth Ha, Serge Egelman, Ariel Haney, Erika Chin, and David Wagner. [Android Permissions: User Attention, Comprehension, and Behavior](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2012/EECS-2012-26.pdf). SOUPS 2012.
* Devdatta Akhawe and Adrienne Porter Felt. [Alice in Warningland: A Large-Scale Field Study of Browser Security Warning Effectiveness](https://research.google.com/pubs/archive/41323.pdf). USENIX Security 2013, pages 257 to 272.
* Primal Wijesekera, Arjun Baokar, Ashkan Hosseini, Serge Egelman, David Wagner, and Konstantin Beznosov. [Android Permissions Remystified: A Field Study on Contextual Integrity](https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/wijesekera). USENIX Security 2015.
* Igor Bilogrevic, Balazs Engedy, Judson Porter, Nina Taft, Kamila Hasanbega, Andrew Paseltiner, Hwi Lee, Edward Jung, Meggyn Watkins, PJ McLachlan, and Jason James. [Shhh...be Quiet! Reducing the Unwanted Interruptions of Notification Permission Prompts on Chrome](https://www.usenix.org/conference/usenixsecurity21/presentation/bilogrevic). USENIX Security 2021.
* [Google Play services and runtime permissions](https://developers.google.com/android/guides/permissions). Android Developers. `shouldShowRequestPermissionRationale` and the educational interface pattern.
* [Cart Abandonment Rate Statistics](https://baymard.com/lists/cart-abandonment-rate). Baymard Institute. The cost of an unexplained demand made before value is delivered.


# Wallet Failure Presentation

Crumbs

## Abstract

This document specifies what a wallet MUST say when a request fails, and what an application MUST do with it.

[BRC-1](/wallet/0001) requires every wallet error to carry a human-readable `description` so that "the application can represent the error for the user", and never says what representing one involves. [BRC-100](/wallet/0100) fixes the envelope as `{status, code, description}`. **That is the entire specification of failure in the corpus.** The consequence is that every wallet vendor writes its own prose for the same conditions, and every application pipes a string it did not write, cannot localise and cannot shorten, straight to a user at the worst moment of their session.

This document closes the gap the way [BRC-184](/wallet/0184) closed it one layer over, where a prompt reading `[2, "auth message signature"]` was identified as a thing a user cannot act on. It defines a **closed core vocabulary of eight failure classes**, the three machine-readable fields that carry them, and the rule that an application MUST NOT display a raw `description` without the structure around it. The vocabulary is closed and extended at reserved dispatch points in the manner of [BRC-98](/wallet/0098), [BRC-99](/wallet/0099) and [BRC-111](/wallet/0111), so that it can grow without a registry and without a second document to widen it.

One class has no equivalent in any wallet today: **uncertain delivery**, meaning the request may have taken effect and nobody can establish whether it did. [BRC-185](/wallet/0185) already distinguishes `not-sent` from `unknown`, because a network timeout cannot prove no transaction was created. Almost no wallet surfaces that distinction, so a user whose payment may or may not have happened is shown the same screen as a user whose payment definitely did not.

## Motivation

Failure is the most frequent user-facing event in a wallet that is not a success, and it is the only one with no standard at all.

### What the corpus currently requires

[BRC-1](/wallet/0001) states it in one line:

> All errors must have a human-readable `description` field that describes the error. This allows the application to represent the error for the user.

That sentence contains the whole problem, and the operative word is **represent**. Representing an error for somebody is an authoring act: choosing the words, the length, the register, and the moment. [BRC-1](/wallet/0001) asks the application to perform it, and hands over a finished sentence in the wallet's voice, which is the one input that makes authoring impossible. The field gets relayed instead, because relaying is the only thing a bare string supports.

Nothing says what that string contains, who it addresses, what register it is written in, how long it may be, whether it may name internal machinery, or whether the application may show it unmodified. Every wallet answers differently, and the application, which owns the screen, has no basis on which to edit what it was given.

### Why the vocabulary can be closed

[BRC-184](/wallet/0184) exists because an identifier that is exact and meaningless produces a prompt a user cannot act on. Its solution was not to rename the identifier but to supply a description alongside it, with rules about failing open, about never presenting an absent description as danger, and about always retaining the exact value underneath.

Errors are the same shape of problem, arriving more often and at worse moments, and nothing equivalent exists. The difference is that [BRC-184](/wallet/0184) needed a registry because protocol identifiers are open-ended and invented by third parties. **Failure classes are not open-ended.** A wallet can fail for a small number of structurally distinct reasons, and that set is enumerable, so the vocabulary can be closed and no registry or steward is needed to maintain it.

### The four questions a user actually has

Tim Neusesser and Evan Sunwall's error-message guidelines for Nielsen Norman Group ask that a message describe the issue concisely and precisely, offer a remedy rather than merely stating the problem, sit close to the error's source, take a positive tone without blaming the user, and use human-readable language with error codes hidden or shown only for diagnosis. Translated into a wallet, a user meeting a failure has four questions and today's `description` answers at most one:

1. What happened?
2. Whose fault was it, and specifically, was it mine?
3. What can I do now?
4. Is it safe to try again?

The fourth is the one that matters most and is almost never answered, because answering it requires the wallet to know whether the request reached anything. [BRC-185](/wallet/0185) section 6 already carries that knowledge in its `delivery` field, which is `not-sent` before dispatch and `unknown` where dispatch may have occurred and no authoritative result exists. That document says plainly that a network timeout cannot prove no transaction was created.

**A user who is told "payment failed" when the truth is "we do not know" has been told something false about their money.** They will retry, and they may pay twice.

### What we propose the user sees

One class carries the argument for the whole document, so it is the one shown first. A wallet has dispatched a payment, the network went quiet, and no authoritative result came back. There is no vocabulary for that state today, so it is reported as the nearest tidy one.

> **Payment failed**
>
> Error: transaction broadcast timeout. Please try again.
>
> `[ Try again ]`

Every line of that is a defect. The operation is not known to have failed. The sentence is the wallet's, in the wallet's language, at the wallet's length, on a screen the wallet has never seen. And the control is how a user pays twice.

Under this document the wallet returns `class: uncertain-delivery`, `fault: none` and `retry: check-first`, and the application writes this.

> **We do not know whether this payment went through**
>
> Your wallet sent it and the network did not answer. It may have arrived.
>
> Check your balance before sending it again.
>
> `[ Check my balance ]` `[ Close ]`

Three things changed. The heading names the state the wallet is in rather than a neater one next to it. The remedy is a check rather than a repeat, which is section 6.3's rule for `check-first`. And no control on the surface sends the payment again, because none may until the user knows.

### What this costs if it stays unspecified

Two wallets fail the same way and produce two different sentences, so a user cannot learn what a recoverable failure looks like. An application cannot localise, because it is relaying a string in whatever language the wallet chose. A support request contains a sentence no one on the support side wrote. And the uncertain case is presented as a definite one, which converts an ambiguity into a double spend by the user's own hand.

## Prior Art

| Source                                                                              | What it supplies                                                                                                                               | Where it lands                                                                                               |
| ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| [BRC-1](/wallet/0001), Ty Everett                                                   | The `description` field and the instruction that the application represent the error for the user, which is the clause this document completes | Section 6.1, and the note below on what representing requires                                                |
| [BRC-100](/wallet/0100), Ty Everett                                                 | The `{status, code, description}` envelope that carries all of this, unchanged here                                                            | Section 3.1                                                                                                  |
| [BRC-184](/wallet/0184), Ty Everett                                                 | The precedent and the template: a description alongside an exact identifier, failing open, never presenting absence as danger                  | The whole approach, and section 5.3                                                                          |
| [BRC-185](/wallet/0185) section 6, Ty Everett and Deggen                            | The `delivery` distinction between `not-sent` and `unknown`, and the statement that a network timeout cannot prove no transaction was created  | Section 4.7, the uncertain-delivery class                                                                    |
| [BRC-98](/wallet/0098), [BRC-99](/wallet/0099), [BRC-111](/wallet/0111), Ty Everett | The reserved dispatch-point pattern that lets a closed vocabulary be extended without a registry                                               | Section 7                                                                                                    |
| [BRC-219](/wallet/0219), Ty Everett                                                 | That a refusal is an explicit error rather than a hang, and that a denial completes the original request                                       | Sections 4.3 and 8.1                                                                                         |
| [BRC-151](/opinions/0151), BitcoinSvGuide                                           | That permission surfaces should describe consequences rather than internal terminology                                                         | Section 5.1                                                                                                  |
| Tim Neusesser and Evan Sunwall, Nielsen Norman Group                                | Describe the issue precisely, offer a remedy, place it close to its source, do not blame the user, hide error codes                            | Sections 5.1, 5.3 and 6.7                                                                                    |
| Jakob Nielsen, response-time limits                                                 | That ten seconds is the limit of a user's attention on a task                                                                                  | Section 4.7, the deadline past which a wallet owes the uncertain class                                       |
| WCAG 3.3.1, Error Identification                                                    | That an error be identified and **described to the user in text**                                                                              | Section 5.1, which says what that text contains, and sections 6.8 to 6.10, which say how it reaches the user |

### Where this sits

Several documents describe one arc, from a user who has no wallet to a user whose request has just failed. None of them states the arc, so it is stated here, and this document's own place in it is marked. Each is independently implementable and none depends on another having been read.

| Moment                   | Document                   | What it owns                                                                                     |
| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------ |
| Arriving with no wallet  | [BRC-110](/opinions/0110)  | Zero friction, deferred onboarding, and a benefit explained in context at the point of use       |
| Being offered one        | [BRC-137](/opinions/0137)  | Device detection, catalog-resolved install paths, fallback login, and the connect decision flow  |
| Choosing among them      | BRC-300, pull request #224 | The connect control and its vocabulary, chooser neutrality, and the sponsorship behind placement |
| The browser's permission | BRC-325, pull request #330 | What precedes the loopback dialog, and the honest reporting of a refusal                         |
| Reaching the wallet      | [BRC-185](/wallet/0185)    | Substrate discovery and selection, coverage states, and connection states                        |
| **When something fails** | **This document**          | **What a failure says, and what an application may do with it**                                  |

Documents named without a link are submitted and not yet merged. They are named rather than linked so that this document resolves correctly whatever order the set lands in.

The arc continues past this document in both directions and this document does not reach into either. Before a failure there is the confirmation the wallet shows and the user answers, which is what produces the refusal section 4.3 classifies. After one there is the question of what an application may claim about a payment that did go through, and separately the question of reviewing and withdrawing the grants a user has already given. Nothing here specifies any of those, and nothing here forecloses them: a later document that does will find the classes of section 4 already in place and will need no change to them.

### What BRC-1 asks for, and what this supplies

[BRC-1](/wallet/0001) gives one reason for the `description` field: "This allows the application to represent the error for the user." The verb is **represent**, not relay, and the distance between the two is this document.

Representing an error means writing it: in the application's language, at its length, in its register, for a screen it can see. That is the work [BRC-1](/wallet/0001) assigns to the application. What the application receives is a finished sentence in the wallet's voice, which supports forwarding and discarding and nothing in between.

So this document does not contradict [BRC-1](/wallet/0001). It supplies the inputs that clause has always required and never named: a class, a fault party, and a retry safety. Section 6.1's prohibition on displaying a raw `description` is that same instruction read strictly, and section 6.2 keeps the wallet's sentence available alongside, attributed, for the application that wants it.

Nothing in [BRC-1](/wallet/0001) or [BRC-100](/wallet/0100) changes. The envelope is the same, `code` is untouched, and a wallet that adds none of the three fields remains conforming to both.

## Specification

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

### 1. Terminology

* **Failure**: any [BRC-100](/wallet/0100) response whose `status` is `error`.
* **Class**: one of the eight structural categories in section 4, carried in the failure's machine-readable form.
* **Fault party**: the party whose state or decision produced the failure. One of `user`, `application`, `wallet`, `counterparty`, `network`, or `none`. `none` means nobody erred, which is true of most failures, and `counterparty` is available only on the class in section 4.8.
* **Retry safety**: whether repeating the identical request is safe, unsafe, or safe only after a check.
* **Presentation**: what an application shows a user as a result of a failure.

### 2. Scope

2.1 This document governs failures of [BRC-100](/wallet/0100) calls, and the presentation of those failures by the application that made them.

2.2 It does not govern transport failures below the wallet interface, which [BRC-185](/wallet/0185) section 6 already types, except where such a failure surfaces as a [BRC-100](/wallet/0100) error.

2.3 It adds no method, no endpoint and no wire format. It constrains the content of one existing field, adds three optional sibling fields, and places obligations on the application that receives them.

### 3. The envelope

#### 3.1 What a wallet returns

A failure MUST carry the [BRC-100](/wallet/0100) fields unchanged, and SHOULD carry three more:

| Field         | Required      | Content                                                                   |
| ------------- | ------------- | ------------------------------------------------------------------------- |
| `status`      | Yes, existing | `error`                                                                   |
| `code`        | Yes, existing | The wallet's machine-readable code, unchanged by this document            |
| `description` | Yes, existing | Governed by section 5                                                     |
| `class`       | SHOULD        | One of section 4's eight values, or an extension under section 7          |
| `fault`       | SHOULD        | One of `user`, `application`, `wallet`, `counterparty`, `network`, `none` |
| `retry`       | SHOULD        | One of `safe`, `unsafe`, `check-first`                                    |

3.2 A wallet that omits `class`, `fault` or `retry` is conforming but has left the application unable to meet section 6, and the application's fallback under section 6.4 is the result.

3.3 `code` remains the wallet's own. This document does not standardise it, because existing codes are deployed and a renaming would break consumers for no user-visible gain.

### 4. The eight classes

Each class below states what it means, the usual `fault`, and the usual `retry`. A wallet MUST NOT use a class whose meaning does not fit merely because no other fits better; section 4.8 exists for that.

| Class                | Fault                      | Retry                        | A presentation says                                                            | It MUST NOT say                                           |
| -------------------- | -------------------------- | ---------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------------------- |
| `insufficient-funds` | `none`                     | `check-first`                | There is not enough to cover this, and the shortfall where the wallet gives it | Anything implying the user erred                          |
| `locked`             | `none`                     | `safe`                       | The wallet is there and needs unlocking                                        | Missing. Disconnected. Not installed                      |
| `permission-denied`  | `user`                     | `unsafe`                     | The request was declined, and what the user can still do                       | That declining was a mistake, or anything that asks again |
| `policy-blocked`     | `none`                     | `unsafe`                     | A standing rule refused this, and where that rule lives                        | That the user declined. That trying again will help       |
| `unreachable`        | `network` or `none`        | `safe`                       | The wallet could not be reached just now                                       | Missing. Absent. Not installed                            |
| `invalid-request`    | `application`              | `unsafe`                     | This site sent a request the wallet could not accept                           | Anything asking the user to correct it                    |
| `uncertain-delivery` | `none`                     | `check-first`                | The outcome is unknown, and the way to find out                                | Failed. Succeeded. Anything offering a bare retry         |
| `unavailable`        | `wallet` or `counterparty` | varies, and MUST be supplied | The wallet cannot do this now, and whether waiting helps                       | Any of the other seven                                    |

The table is normative and the subsections below carry the reasoning. Where the two appear to differ, the subsection governs.

#### 4.1 `insufficient-funds`

The wallet holds less spendable value than the request needs. Fault `none`: the user has not erred, and neither has anybody else. Retry `check-first`, since retrying after funding succeeds and retrying before it does not.

A presentation MUST NOT imply the user did something wrong, and SHOULD state the shortfall where the wallet discloses it.

#### 4.2 `locked`

The wallet is present and will not act until the user authenticates to it. Fault `none`. Retry `safe`.

[BRC-185](/wallet/0185) section 5 states that lock state is not absence. A presentation MUST NOT describe a locked wallet as missing, disconnected or uninstalled.

#### 4.3 `permission-denied`

The user was asked and refused, which is the denial [BRC-219](/wallet/0219) requires the wallet to return as an error on the original request. Fault `user`, in the sense of authorship rather than blame. Retry `unsafe` within the same interaction.

A presentation MUST NOT re-ask automatically, MUST NOT describe the refusal as an error on the user's part, and MUST NOT retry. The user answered the question.

#### 4.4 `policy-blocked`

A standing policy refused before any user was asked. [BRC-181](/wallet/0181) spend policies, [BRC-204](/wallet/0204) allowances, [BRC-179](/wallet/0179) capability manifests and [BRC-116](/wallet/0116) grants are the mechanisms. Fault `none`. Retry `unsafe` until the policy changes.

A presentation MUST distinguish this from `permission-denied`. Nobody was asked, so telling a user they declined something is false, and telling them to try again is useless.

#### 4.5 `unreachable`

The wallet could not be contacted. Fault `network` or `none`. Retry `safe`.

A presentation MUST NOT describe an unreachable wallet as an absent one, which is [BRC-185](/wallet/0185) section 4.1's rule that a blocked scan is not evidence of absence, and BRC-325 ([pull request #330](https://github.com/bsv-blockchain/BRCs/pull/330)), whose whole subject is the browser side of this.

#### 4.6 `invalid-request`

The request was malformed or asked for something the interface does not define. Fault `application`. Retry `unsafe`.

This is the one class whose presentation is primarily for a developer. A presentation MUST NOT ask the user to correct it, and SHOULD say plainly that the application made a bad request, because that is true and because the alternative is a user trying to fix something they cannot reach.

#### 4.7 `uncertain-delivery`

**The request may have taken effect and the wallet cannot establish whether it did.** Fault `none`. Retry `check-first`, always.

This is [BRC-185](/wallet/0185) section 6's `delivery: 'unknown'` raised to the user-facing surface. A presentation MUST NOT describe the operation as failed, MUST NOT describe it as succeeded, and MUST NOT offer a retry control that repeats the request without a check. It MUST tell the user that the outcome is unknown and name the way to find out: a balance, a list of actions, a transaction identifier if one exists.

A wallet MUST use this class wherever it cannot prove the request did not take effect. The temptation is to report a clean failure because it produces a tidier screen, and a tidier screen that causes a second payment is the worst outcome in this document.

A wallet MUST NOT hold a request open indefinitely in order to avoid reaching this class. Where a dispatch deadline passes with no authoritative result, the wallet MUST return `uncertain-delivery` rather than continue to wait, and SHOULD do so within ten seconds. That is Jakob Nielsen's limit for holding a user's attention on a task: past it the user has already concluded that something went wrong, and a wallet still waiting has left them to guess which something. Waiting longer does not make the honest class less true, and it is available at any point.

#### 4.8 `unavailable`

The wallet understood the request, holds no objection to it, and cannot perform it now for a reason outside the other seven: a resource limit, a dependency that is down, a feature not implemented in this build. Fault `wallet`, or `counterparty` where the thing that failed is a party to the request rather than part of the wallet, such as a peer under [BRC-185](/wallet/0185) that refused or could not complete its side. This is the only class that carries `counterparty`, because it is the only one whose failure can belong to somebody who is neither of the two parties on the screen.

Retry varies with the reason, so a wallet supplying `class: unavailable` MUST supply `retry` with it. Section 3.1 makes that field a SHOULD in general; here the class says nothing on its own, and without `retry` the application's fallback under section 6.4 returns the value it was just handed.

A presentation MUST NOT dress this as any of the other seven. A wallet that reports a missing feature as `insufficient-funds` has lied in a way the user will act on.

### 5. The description

#### 5.1 What it must do

The `description` MUST answer, in the user's vocabulary, what happened and what can be done now. It MUST NOT:

1. name internal machinery: method names, class names, field names, stack frames, HTTP status codes, or the wallet's own `code`;
2. imply the user erred, except where `fault` is `user` and the error is an actual choice they made;
3. assert an outcome the wallet has not established, which is section 4.7's prohibition stated once more because this is where it is violated;
4. exceed two sentences.

[BRC-151](/opinions/0151) states the principle for this corpus: describe consequences rather than internal terminology.

#### 5.2 What it is not

The `description` is not the presentation. It is the wallet's best sentence for a surface it cannot see, written without knowing the application's language, layout, audience or how much room there is. Section 6 governs what the application does with it.

#### 5.3 Absence

A wallet that cannot produce a meaningful description MUST supply the class and MUST NOT supply a filler string. "An error occurred" is worse than nothing, because it occupies the space a real sentence would have had and tells the application the field is populated. This follows [BRC-184](/wallet/0184)'s rule that a neutral indication of absence is appropriate and a fabricated one is not, and the Nielsen Norman Group guidance names that same string as its example of a message with no context.

### 6. What the application does with it

6.1 An application MUST NOT display a raw `description` as the whole of its presentation. It is a wallet's sentence about a surface it has never seen.

6.2 An application that has `class`, `fault` and `retry` MUST compose its own presentation from them, in its own language and register, and MAY show the wallet's `description` alongside, attributed to the wallet.

6.3 A presentation MUST reflect `retry`:

* `safe`: a retry control MAY be offered and MAY be prominent.
* `check-first`: a retry control MUST NOT be offered without the check beside it, and the check MUST be the primary action.
* `unsafe`: no retry control.

6.4 An application that receives no `class` MUST treat the failure as `unavailable` with `retry: check-first`, and MUST NOT infer a class by matching on `code` or on the text of `description`. String matching on another vendor's prose is how a localisation change becomes a double spend.

6.5 A presentation MUST NOT be a toast or any other auto-dismissing surface where `retry` is `check-first` or `unsafe`. A user who looked away has lost the only notice that their money may be in an uncertain state.

6.6 An application MUST NOT retry automatically where `retry` is `unsafe` or `check-first`, regardless of what its own backoff logic would otherwise do. [BRC-185](/wallet/0185) section 6 already states this one layer down, where an adapter "MUST NOT retry a wallet method automatically after uncertain delivery". The rule is restated here because the layer that actually holds the retry logic is usually the application's, and a transport that obeys the rule underneath an application that does not has achieved nothing.

6.7 A presentation MUST appear where the user acted. A failed payment belongs at the payment, not in a notification tray, a console, or a screen the user has to navigate to, which is the error-message guidance that a message sit close to its source. Where the action had no single location, the presentation belongs where the user's attention already is.

6.8 A presentation MUST be identified to assistive technology when it appears. WCAG 3.3.1 requires that an error be identified and described in text, and a message that is merely drawn is not identified to somebody who is not looking at that region. A surface that appears without taking focus MUST carry a live-region role; one that takes focus MUST be labelled by its own heading.

6.9 A presentation MUST take focus where `retry` is `check-first` or `unsafe`. Those are the two cases in which the user has something to decide and the wrong default is to carry on, and a message that can be scrolled past is a message that will be.

6.10 A presentation MUST NOT convey the class by colour, icon or position alone. The class determines what the user may safely do next, and a user who cannot distinguish the red surface from the grey one is the user most likely to press the control that sends a payment twice.

### 7. Extension

7.1 The eight classes are closed. A wallet MUST NOT invent a ninth value for `class`.

7.2 A future specification MAY define additional classes at a reserved dispatch point, in the manner of [BRC-98](/wallet/0098), [BRC-99](/wallet/0099) and [BRC-111](/wallet/0111): the value `p` followed by a space-free scheme identifier and the scheme-defined remainder.

7.3 An application that does not recognise an extension class MUST treat it as `unavailable` under section 6.4. An extension that is unsafe to treat that way is not a valid extension.

7.4 An extension MUST NOT redefine, narrow or override any of the eight. In particular it MUST NOT provide a tidier alternative to `uncertain-delivery`.

### 8. Relationships

8.1 [BRC-219](/wallet/0219) requires that a wallet present a prompt or return an error, and never leave a permission-required request silently pending. The error it returns on denial is this document's `permission-denied`, and the liveness rules of that document are unchanged.

8.2 This document governs what happens when the answer to a confirmation is no, when the wallet never got as far as asking, or when the request could not be completed after a yes. A failure and the confirmation that preceded it are the same surface at different moments, and SHOULD be presented in the same place. This document places no requirement on the content of a confirmation, which is a separate subject.

8.3 [BRC-185](/wallet/0185) section 6's `ConnectionError` is below this interface. A controller translating one to the other MUST map `delivery: 'unknown'` to `uncertain-delivery` and MUST NOT map it to anything else.

### 9. Conformance

| Condition                                         | Required behaviour                                                                                  |
| ------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Any failure                                       | `{status, code, description}` as today, SHOULD add `class`, `fault`, `retry`                        |
| Request may have taken effect                     | `class: uncertain-delivery`, `retry: check-first`, never reported as failed or succeeded            |
| User refused                                      | `class: permission-denied`, no automatic re-ask, no retry                                           |
| Policy refused before asking                      | `class: policy-blocked`, distinguished from a refusal                                               |
| Wallet locked                                     | `class: locked`, never presented as absent                                                          |
| Wallet unreachable                                | `class: unreachable`, never presented as absent                                                     |
| Malformed request                                 | `class: invalid-request`, fault `application`, user not asked to fix it                             |
| No class fits                                     | `class: unavailable`, never a misfitting one of the other seven                                     |
| `class: unavailable`                              | `retry` supplied with it, since the class alone says nothing                                        |
| Dispatch deadline passed, no authoritative result | `uncertain-delivery` returned rather than held open, within ten seconds                             |
| Description                                       | Two sentences at most, no internal machinery, no asserted outcome                                   |
| No meaningful description available               | Class supplied, description omitted, no filler                                                      |
| Application has class                             | Composes its own presentation; wallet description attributed if shown                               |
| Application has no class                          | Treats as `unavailable` with `check-first`; MUST NOT match on `code` or text                        |
| `retry: check-first`                              | Check is the primary action, retry not offered alone                                                |
| `retry: unsafe`                                   | No retry control, no automatic retry                                                                |
| `check-first` or `unsafe`                         | Not presented in an auto-dismissing surface, and takes focus                                        |
| Any presentation                                  | Appears where the user acted, identified to assistive technology, class not carried by colour alone |
| Unrecognised extension class                      | Treated as `unavailable`                                                                            |

### 10. Surfaces that fail conformance

1. A wallet's `description` shown as the entire error message.
2. An operation whose outcome is unknown presented as failed.
3. A retry button on an `uncertain-delivery`.
4. A toast carrying an uncertain delivery.
5. A locked or unreachable wallet described as not installed.
6. A policy refusal worded as though the user declined.
7. A user asked to correct an `invalid-request`.
8. A missing feature reported as `insufficient-funds`.
9. `An error occurred` as a `description`.
10. An application inferring a class by matching on another vendor's error text.
11. An automatic retry of an `unsafe` failure, because the application's generic HTTP layer did not know this was different.
12. A ninth class invented by a wallet rather than defined at the reserved dispatch point.
13. A failure carried by a red border and nothing else.
14. An uncertain delivery drawn on screen and announced nowhere, so a screen-reader user hears nothing.
15. A wallet that waits rather than admit it does not know.

## What Is Checkable, and What Is Taken on Trust

| Fact                                                 | Who can check it | How                            |
| ---------------------------------------------------- | ---------------- | ------------------------------ |
| A failure carried a class                            | The application  | The response                   |
| An uncertain delivery was not presented as a failure | Anybody          | Induce one and read the screen |
| No retry control appeared on an unsafe failure       | Anybody          | Deny a permission and look     |
| The description names no internal machinery          | Anybody          | Read it                        |
| The presentation was announced                       | Anybody          | A screen reader                |
| The class survives without colour                    | Anybody          | Greyscale, or a screen reader  |
| The application did not string-match on `code`       | A reviewer       | The source                     |

| Claim                                     | Why it cannot be checked                                                          | What stands in for a check                                                                                      |
| ----------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| The class is the right one                | Only the wallet knows why it failed                                               | Section 4.8, which gives an honest place to put the ones that do not fit                                        |
| An uncertain delivery really is uncertain | A wallet could report certainty it does not have, or uncertainty it does not have | Nothing. A wallet that misreports its own certainty is constrained by no interface standard, including this one |
| The user understood what to do next       | No mechanism reaches a mind                                                       | Section 5.1's content rules                                                                                     |
| The retry was actually safe               | Safety is a property of the wallet's state at that moment                         | `check-first` exists because the honest answer is often "look before you do"                                    |

## What It Buys, and What It Costs

| Party         | What they get                                                                                                                       | What it costs them                                                                                               |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| User          | A failure that says whose fault it was and whether it is safe to try again, and an uncertain outcome that is described as uncertain | Nothing                                                                                                          |
| Application   | Three machine-readable fields instead of a foreign-language sentence, so failures can be localised, shortened and routed            | Composing presentations for eight classes, where one `alert(description)` was sufficient before                  |
| Wallet vendor | Its error prose stops being the user interface of every application that calls it                                                   | Classifying its own failures honestly, including admitting uncertainty it currently hides behind a clean failure |
| Support       | A failure a user can describe over a phone                                                                                          | Nothing                                                                                                          |

One thing deserves saying plainly. **The honest classification is the expensive one.** A wallet that reports `uncertain-delivery` is telling its user that it does not know what happened to their money, which reads as incompetence and is in fact the opposite. The clean failure is cheaper, tidier, and causes the double spend. An ecosystem that is not prepared to show the uncertain case should not adopt this document, because the other seven classes are tidying and this one is the point.

## Security Considerations

**An error message is an oracle.** A `description` that explains precisely why an operation was refused can tell an attacker things about a wallet's state, its balances, its policies and its grants. Section 5.1's two-sentence bound limits the damage by accident; wallets MUST consider it deliberately, and SHOULD prefer a less specific description where the specific one discloses a balance or a policy to a caller that has not been granted it.

**`fault: user` is a label, not a judgement, and will be read as one.** A presentation built from it can easily imply the user erred when all they did was decline. Section 5.1 point 2 and section 4.3 exist because that implication is both unkind and inaccurate, and because a user who is told that refusing was a mistake refuses less carefully next time.

**String matching on another vendor's `description` is a supply chain vulnerability.** An application that branches on the text of an error inherits its control flow from whoever writes that wallet's copy, including whoever translates it. Section 6.4 forbids it and names the failure mode, because this is the shortcut every integrator reaches for when `class` is absent.

**An attacker benefits from a tidy failure.** Where a request may have taken effect, an adversary who can induce a clean "failed" message can induce a second attempt. Section 4.7 is a security requirement in that light, not only an honesty one.

## Privacy Considerations

**A failure discloses wallet state to the application that caused it.** `insufficient-funds` reveals a bound on the balance. `policy-blocked` reveals that a policy exists and roughly what it covers. Wallets SHOULD disclose the class without the particulars where the caller has not been granted the underlying information, and MUST NOT include a balance, a policy body or a grant list in a `description`.

**An application's error telemetry inherits all of this.** An application that logs failures verbatim is logging wallet state. Applications SHOULD log the class and withhold the description.

## Implementations

No new method, no endpoint, and no change to the existing envelope.

**Wallet side.** Three optional sibling fields on the existing error object, and a classification at the point each failure is raised. A wallet that already distinguishes its internal failure reasons has the information; what it lacks is the obligation to express it in a shared vocabulary.

**Application side.** A switch on eight values, and a presentation per class. The important line is the default case, which section 6.4 requires to be `unavailable` with `check-first` rather than a guess.

**The translation from BRC-185.** A controller sitting between the two maps `ConnectionError` to this envelope. `DISCOVERY_BLOCKED` becomes `unreachable`, `USER_CANCELLED` becomes `permission-denied`, `CONFIGURATION_ERROR` becomes `invalid-request`, and anything carrying `delivery: 'unknown'` becomes `uncertain-delivery` regardless of its code.

**Verification.** No cryptographic values and no wire format, so there is nothing to publish as vectors. Section 9's table is the conformance surface, and a wallet can be exercised against most of it by inducing each condition.

## Failure Modes

| What went wrong                                        | How it shows                                                   | Detected by                 | Attributable to                                             |
| ------------------------------------------------------ | -------------------------------------------------------------- | --------------------------- | ----------------------------------------------------------- |
| Uncertain delivery reported as failure                 | A user retries and pays twice                                  | The user, on their balance  | The wallet                                                  |
| Retry offered on an uncertain delivery                 | The same, faster                                               | The user                    | The application                                             |
| Application matched on error text                      | A wallet update or a translation changes application behaviour | Nobody, until it breaks     | The application                                             |
| Locked wallet shown as absent                          | A user is told to install software they already have           | The user, with irritation   | The application, or the wallet that gave no class           |
| Policy refusal worded as a user refusal                | A user tries to change a decision they never made              | The user                    | The application                                             |
| Description carried internal machinery                 | A user reads a method name and learns nothing                  | Anybody                     | The wallet                                                  |
| Description disclosed a balance to an ungranted caller | Nothing visible                                                | Nobody                      | The wallet                                                  |
| Automatic retry of an unsafe failure                   | Duplicate effects                                              | Sometimes nobody            | The application's generic transport layer, which never knew |
| The wallet admitted it did not know                    | Nothing went wrong                                             | The user, who can now check | Nobody. This is the system working                          |

## References

* [BRC-1: Transaction Creation](/wallet/0001)
* [BRC-98: P Protocols: Allowing Future Wallet Protocol Permission Schemes](/wallet/0098)
* [BRC-99: P Baskets: Allowing Future Wallet Basket and Digital Asset Permission Schemes](/wallet/0099)
* [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](/wallet/0100)
* [BRC-110: Zero-Friction, Mobile-First Onboarding for MetaNet-Enabled Apps](/opinions/0110)
* [BRC-111: P Labels: Allowing Future Wallet Action Label Permission Schemes](/wallet/0111)
* [BRC-116: Wallet Permissions and Counterparty Trust](/wallet/0116)
* [BRC-137: Device-Aware Wallet Onboarding and Fallback Login for BRC-100 Applications](/opinions/0137)
* [BRC-151: BRC-100 Risk Assessment and Best Integration Practices](/opinions/0151)
* [BRC-179: Signed Capability Manifest for Wallet and Tool-Server Method Exposure](/wallet/0179)
* [BRC-181: Wallet-Enforced Autonomous-Agent Spend Policy](/wallet/0181)
* [BRC-184: Optional Metadata Registries and Their Stewardship](/wallet/0184)
* [BRC-185: Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections](/wallet/0185)
* [BRC-204: Agent Allowances](/wallet/0204)
* [BRC-219: Wallet Permission Prompt Liveness Contract](/wallet/0219)
* BRC-300: Sponsored Onboarding and Wallet Choice. Not yet merged; [pull request #224](https://github.com/bsv-blockchain/BRCs/pull/224)
* BRC-325: User-Initiated Browser Permission Requests for Wallet Discovery. Not yet merged; [pull request #330](https://github.com/bsv-blockchain/BRCs/pull/330)
* [RFC 2119: Key words for use in RFCs to Indicate Requirement Levels](https://www.rfc-editor.org/rfc/rfc2119)
* [WCAG 2.2 Success Criterion 3.3.1: Error Identification](https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html). W3C, Level A. Requires that an error be identified and described to the user in text.
* Tim Neusesser and Evan Sunwall. [Error-Message Guidelines](https://www.nngroup.com/articles/error-message-guidelines/). Nielsen Norman Group, 14 May 2023. Describe the issue concisely and precisely, offer a remedy, place the message close to its source, do not blame the user, and keep error codes out of sight.
* Jakob Nielsen. [Response Times: The 3 Important Limits](https://www.nngroup.com/articles/response-times-3-important-limits/). Nielsen Norman Group, 1 January 1993, updated 2014. The ten-second limit on holding a user's attention.


# 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 specification) 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 additional 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 specification 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**                                              | **8 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 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 specification 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 specification 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 specification 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 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`, with `Preference` assigned by position in the configured list so that the operator's ordering is preserved among seeds: the first seed takes the highest preference and each subsequent seed one lower. A single shared preference would leave seeds tied under the sort below, making the fallback order arbitrary.

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 (per class)   | Retry endpoint | Frames expire naturally; bounds retransmit window. The TTL is set per frame class, since a block or subtree stays useful far longer than a transaction |
| `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                                                                                      |
| `MsgTypeTHROTTLED`      | 19         | `0x13`       | THROTTLED congestion 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 cache TTL for transaction frames (s); other frame classes carry their own longer defaults |
| `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. Deliver the datagram to the SubtreeGroupAnnounce control group on the configured multicast scope(s). A sender on the multicast fabric sends it as a UDP packet directly. A sender that is not on the fabric, or that has no multicast egress of its own, MAY instead submit the same 64 bytes to an ingress proxy over its submission transport and have the proxy forward them to the control group unchanged. The datagram on the control group is identical either way, so a listener cannot tell the two apart and needs no knowledge of which was used.
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 `0x0000000000EF` 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. Implementations MUST NOT join or transmit to addresses in this range unless a BRC explicitly assigns them.

[BRC-148](/transactions/0148) is such a BRC: it partitions this range into object planes by the high nibble of the index and allocates the first of them. This section is superseded to that extent. Every other provision of this document, including the IANA allocation, the group-id field and the control-plane indices, is unchanged by it.

### 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 MAY verify the reassembled payload against that identifier 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 canonical transaction identifier of the complete unfragmented payload. For a standard (BRC-12 or BRC-124) payload this is the double-SHA256 of the payload bytes:

```
TxID = SHA256(SHA256(reassembled_payload))
```

For an Extended Format payload ([BRC-128](/transactions/0128)) it is **not** the hash of the payload bytes. EF carries the marker and per-input source data that the identifier does not cover, so the identifier is computed over the standard serialization of the transaction, exactly as it is everywhere else in Bitcoin. Hashing the EF bytes directly yields a value that will never match.

The same TxID appears on every fragment of a transaction. It serves as the reassembly key, and as an integrity proof a listener MAY check after reassembly. Verification is OPTIONAL rather than required: at line rate it costs a full pass over every reassembled payload, and an implementation that offers it SHOULD make it configurable. A listener that verifies MUST drop the object on mismatch.

#### 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 data over the IPv6 multicast fabric. A new frame version (`0x04`) reuses the BRC-124 92-byte header layout and carries either a block announcement, whose payload is a complete [BRC-144](/transactions/0144) block object, or a standalone coinbase transaction, delivered to all subscribers via a dedicated control-plane multicast group (`FF0E::B:FFFE`). On this fabric a block announcement and the block data are the same thing: the announcement carries the block. The standalone coinbase message type (`MsgType 0x02`) is deprecated but retained and reserved; see the CoinbaseTx Payload section and [BRC-133](/transactions/0133).

## 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. The announcement carries the whole block object, because a subscriber that learns of a block without its subtree list and coinbase must fetch them over some other path, which the fabric exists to avoid.
2. **Coinbase transactions** must reach every subscriber regardless of shard assignment, since every node needs the coinbase to reconstruct the block Merkle root. The coinbase therefore travels INSIDE the block object, where it inherits the block's proof of work; the standalone message type that once carried it separately is deprecated (see `CoinbaseTx Payload`).
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` | A BRC-144 block object, carried verbatim  |
| `0x02` | `BlockMsgCoinbase` | Raw serialized coinbase transaction bytes |

Any other value is rejected. `0x02` is deprecated (see the CoinbaseTx Payload section) and stays reserved for standalone coinbase carriage; it must not be reassigned.

#### 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)

The payload is a complete [BRC-144](/transactions/0144) block object, carried verbatim with no additional envelope:

| Size   | Field            | Description                                                                 |
| ------ | ---------------- | --------------------------------------------------------------------------- |
| 80     | BlockHeader      | Standard 80-byte BSV block header                                           |
| 8      | TransactionCount | uint64 BE. 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` |

The fixed prefix through `SubtreeCount` is **104 bytes**; everything after it is variable. The `Coinbase` carries no length prefix — it is self-delimiting by transaction structure, so a reader parses it and resumes at `Height`. The field definitions, including the block-header layout and the coinbase-placeholder rule for the first subtree, are normative in [BRC-144](/transactions/0144) and are not restated here.

**Minimum payload size:** 120 bytes plus the coinbase transaction (M = 0, no BUMP). **Total payload size:** 120 + 32 × M + `len(Coinbase)` + `CoinbaseBUMPLen` bytes.

The `ContentID` in the frame header is the 32-byte block hash, which a receiver verifies as `SHA256d(BlockHeader)` over the payload's first 80 bytes. The `SubtreeHashes` are the ordered Merkle roots of the sharded transaction subtrees included in this block, matching the producer's subtree enumeration. `M` may be zero for empty blocks or blocks with no sharded subtrees.

**There is no separate coinbase identifier field.** The coinbase transaction itself is present, so its id is `SHA256d(Coinbase)` and a reader that wants the id computes it. A payload that ends after the subtree hashes, with no coinbase, is malformed and MUST be rejected.

> **Revision note.** Earlier revisions of this BRC defined this payload as an 80-byte header, a 32-byte coinbase id and a `uint32` subtree count — a lean announcement that named the block without carrying it. No implementation ever produced that grammar: every producer has emitted the BRC-144 block object described above, and this revision makes the specification say so. Because the two grammars share a prefix, a reader built to the old text does not fail cleanly on a zero-subtree block; it reads a `SubtreeCount` of zero out of the coinbase's null previous-output hash and reports a coinbase id that is actually the block's count fields. A reader MUST therefore treat the payload as a BRC-144 object.

### CoinbaseTx Payload (MsgType 0x02)

**Deprecated, and retained deliberately.** Current implementations never produce `CoinbaseTx` frames: a coinbase transaction is invalid to a node unless it is connected to its block, so the coinbase travels inline in the [BRC-144](/transactions/0144) block body, where it inherits the block's proof of work. A standalone coinbase frame carries no proof of work of its own, and a listener that gates block-control frames before fan-out (the reference listener's block-control gate, on by default) drops it; the reference implementation counts it under the drop reason `coinbase_legacy`. The message type is kept, not withdrawn, because a future design may need to carry blocks and their coinbase separately across the multicast fabric and recombine them at the edges. `MsgType 0x02` and [BRC-133](/transactions/0133) therefore remain reserved for that purpose. The format below remains normative for any implementation that emits or accepts the message type.

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), which is also `SHA256d(Coinbase)` over the coinbase carried inside 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.

A block announcement carries the whole block object, so its size follows the block: the fixed prefix and the subtree list are small (104 + 32 × 128 = 4200 bytes for 128 subtrees), and the coinbase transaction and its optional BUMP carry the rest. Announcements therefore routinely exceed a 9000-byte jumbo frame and fragmentation is the normal case, not the exception.

### 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 (M=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
<4-byte PayloadLen>                                               // 184 + len(Coinbase)

// Payload — a BRC-144 block object
<80-byte block header>                                            // BlockHeader
0000000000000FA0                                                  // TransactionCount = 4000
00000000001E8480                                                  // SizeInBytes = 2,000,000
0000000000000002                                                  // SubtreeCount = 2
<32-byte subtree hash 0>                                          // SubtreeHashes[0]
<32-byte subtree hash 1>                                          // SubtreeHashes[1]
<coinbase transaction>                                            // Coinbase (self-delimiting)
00000000000C3500                                                  // Height = 800,000
0000000000000000                                                  // CoinbaseBUMPLen = 0
```

### 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 (payload = a BRC-144 block object) |
| BlockMsgCoinbase       | 2     | 0x02   | MsgType: coinbase transaction (deprecated; reserved)           |
| GroupBlockBroadcast    | 65534 | 0xFFFE | Control-plane group index for block frames                     |
| BlockHeaderSize        | 80    | 0x50   | Standard BSV block header size (bytes)                         |
| BlockObjectFixedPrefix | 104   | 0x68   | BRC-144 fixed prefix through SubtreeCount                      |
| 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
* [BRC-133: Multicast Coinbase Transaction Frame Format](/transactions/0133): the standalone coinbase carriage using `MsgType 0x02` (deprecated, retained)
* [BRC-144: Block Frame Format](/transactions/0144): the block body that carries the coinbase inline


# 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 is longer than the transaction-frame TTL, to accommodate large reassembly windows; the reference implementation defaults it to five minutes.

### Merkle Verification

After reassembly, a consumer MAY recompute the Merkle root to verify the SubtreeID:

* The consumer decodes the payload into nodes and computes SHA256d pairwise up the binary tree, then compares the root to SubtreeID.
* On mismatch, the reassembled object MUST be dropped.
* The cost is significant at scale (roughly one double-SHA256 per node, so \~1M operations for a 1M-node subtree), which is why this is optional rather than required, and why an implementation that offers it SHOULD leave it off by default.

### 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.

## Status Note

**Deprecated, and retained deliberately.** Standalone coinbase carriage, the mechanism this BRC specifies (BRC-131 `MsgType 0x02`, `BlockMsgCoinbase`), is deprecated. It is kept on purpose and is not withdrawn.

* **Not produced.** A coinbase transaction is invalid to a node unless it is connected to its block, so current implementations never emit standalone coinbase frames. The coinbase travels inline in the block body defined by [BRC-144](/transactions/0144), where it inherits the proof of work of the block it belongs to.
* **Dropped by default.** A standalone coinbase frame carries no proof of work of its own, so nothing about it can be validated in isolation. A listener that gates block-control frames before fan-out (the reference listener's block-control gate, which is on by default) drops such a frame; the reference implementation counts it under the drop reason `coinbase_legacy`.
* **Retained deliberately.** A future design may need to carry blocks and their coinbase separately across the multicast fabric and recombine them at the edges. This specification keeps that option open, so it remains the reference for such a design, for deployments that disable the gate, and for decoding archived traffic.
* **Reserved.** The BRC-133 number, BRC-131 `MsgType 0x02`, and the virtual flow index `0xFFF8` (`GroupCoinbaseFlow`) remain reserved for standalone coinbase carriage. They must not be reused for any other purpose.

## 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 (deprecated; reserved, see Status Note)                |
| `GroupBlockBroadcast` | `0xFFFE` | Block control multicast group index (egress)                                             |
| `GroupCoinbaseFlow`   | `0xFFF8` | Virtual HashKey ingredient; coinbase flow identity (never a multicast address); reserved |

## 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
* [BRC-144: Block Frame Format](/transactions/0144): the block body that carries the coinbase inline, superseding standalone coinbase carriage


# 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 Frame Format

Jeff Harris (<jeff@lightweb.net>)

## Abstract

This BRC specifies how standalone 80-byte BSV block headers are distributed to downstream consumers, in two forms. The **datagram form** wraps the header in a minimal 172-byte frame that reuses the 92-byte BRC-124 header layout, ensuring compatibility with existing infrastructure classifiers and firewall rules, and is carried over IPv6 multicast or unicast datagrams. The **stream form** writes the bare 80-byte header, with no frame at all, on a single-class byte stream dedicated to headers. Both are produced by splitting the block header out of a BRC-131 block announcement.

## 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

This section governs the **datagram form**; the stream form has no group and is specified under *Stream Delivery Form* below.

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]` — the first 80 bytes of the BRC-144 block object that payload carries.
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    |

### Stream Delivery Form

On a **single-class byte stream** carrying nothing but block headers, the emitter writes the bare 80-byte header and nothing else: no frame header, no tag, no length prefix, no record envelope. A block header is fixed size, so the stream is self-delimiting — a consumer reads 80 bytes at a time, and the Nth header begins at byte offset `80 × (N − 1)`.

This is the form for a persistent unicast connection dedicated to headers, where the connection itself identifies the class. It is deliberately minimal:

* **No frame header.** The 92-byte header exists to classify a datagram on a shared multicast group and to carry the emitter's sequencing. A dedicated stream needs neither: the connection names the class, and the transport already orders and delivers what is written.
* **No sequencing fields.** `HashKey` and `SeqNum` identify the emitter's flow, which is meaningless to a subscriber and differs between the primary and standby emitters that may serve it. What a consumer verifies is the header itself, and the chain it builds from them.
* **No length prefix.** Eighty bytes is fixed by consensus. A length would be redundant, and a reader that trusted it over the fixed size would desynchronise on a malformed writer rather than on the first bad header.

A consumer of the stream form:

1. **Read** exactly 80 bytes.
2. **Chain** — check that `PrevBlockHash` (bytes 4–35) equals `SHA256d` of the previous header it accepted, which is what makes a stream of headers a chain rather than a list.
3. **Identify** — compute `BlockHash = SHA256d(header)` where the block id is needed; the stream carries no separate identifier.

A stream is a **live tail**: a consumer that connects mid-chain receives headers from connect time forward and cannot bootstrap a chain from this stream alone.

The two forms carry the same 80 bytes and differ only in envelope, so an emitter MAY serve both, and a consumer MUST NOT expect frame fields on the stream form.

### Consumer Processing

A consumer receiving BRC-135 **datagram** 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    | `0`–`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 allocated by [BRC-148](/transactions/0148) as `DomainsValid`, which appends a per-plane Domains descriptor section after this datagram's other trailing payloads. A consumer that does not implement BRC-148 MUST treat bit 7 as reserved and MUST NOT interpret any trailing bytes it introduces. 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` and `7` are assigned by [BRC-148](/transactions/0148) as further informational hints. Values above those remain reserved. `RoleHint` is informational in every case: it never governs routing or filtering.

### 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. `0` means unset: a decoder MUST treat it as "generation not pinned" and MUST NOT read it as a width |
| 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-131: Multicast Block Announcement Frame Format](/transactions/0131) — this object is what a BRC-131 `MsgType 0x01` announcement carries on the multicast fabric
* [BRC-135: Multicast Block Header Frame Format](/transactions/0135) — the header-only subset of this object, for consumers that need the chain and not the block
* [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**, as one frame carrying the submission record verbatim, keyed to the first topic. The frame says how many of the leading names are deliverable; the rest are labels every recipient sees (BRC-149 §Deliverable topics and labels). Sibling emissions do not exist.

**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. A submission is the pair *(topic list, BEEF object)*, mirroring the BRC-22 submit shape, and names 1..15 topics on every path; it is never rejected for its count. The ingress emits it as one frame carrying the record verbatim, keyed to the first topic, so every submission has the bounded, election-scoped footprint of a transaction submission and is admitted as an **open class**. What the ingress path decides is how many of the leading names are **deliverable**: open/anonymous ingress delivers the first topic only, an authenticated ingress up to the operator's cap; the remaining names are **labels** the recipient sees and no edge matches on (BRC-149 §Deliverable topics and labels). This is what keeps the open door free of amplification without refusing anyone's labelling. An operator MAY further restrict the class as local policy. 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 the same object under a different topic list is a distinct submission (its ContentID is over the record). Ingress duplicate suppression MUST therefore key on the (ContentID, TopicID) pair, never on the subject TxID.

#### 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 two fixed-offset envelope fields and, when the payload is a submission record, its leading topic list (which precedes the object, so the read is bounded by the record envelope and never parses BEEF):

1. **TopicID** (header offset 56) plus the record's next `DeliverCount − 1` names — 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** (the object's bytes 0–3) — 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 one of its **deliverable** topics (BRC-149: the header TopicID and the record's next `DeliverCount − 1` names) is a member, and it is delivered **once** however many are, under the first in record order; a label topic never matches. 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 frame's TopicID and DeliverCount from their fixed offsets, the further deliverable names from the payload record, and the BEEF version from the object inside it.
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 per deliverable name), so per-frame cost grows with the deliverable prefix, which the ingress bounds, and never with the election.
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 payload bytes: the submission record when the ingress carried one, the bare object otherwise. 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**, 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 object's first four 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, TopicID and DeliverCount appear in **every** fragment, while the record's topic list and the version word appear 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 the deliverable topics when no subscriber has elected any of them, which fragment 0 makes possible.

### 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`      | DeliverCount     | How many of the payload record's leading topics are **deliverable** (matched by delivery edges): `1` from open ingress, up to the operator's cap from authenticated ingress. `0` is the legacy encoding and is read as `1`. Every further name in the record is a **label**: carried to the subscriber, never matched. Set by the ingress, never taken from a publisher's pre-framed value. The BEEF encoding version is **not** here — it is the object's first four bytes. |
| 8      | 32   | `[32]byte`  | ContentID        | `SHA-256d(payload bytes)` — the object's identity, over the payload as carried (the submission record when the ingress carried one, so the same object under a different label set is a distinct submission); 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)` of the record's **first** topic. The shard key: group derivation takes its top bits, and it is always the first deliverable topic. 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 **submission record verbatim** (leads with the `0xBEEF` tag; every topic name the publisher submitted, then the object, proof data intact), or a bare BEEF object (leads with `0x01`). The two cannot collide on their leading bytes.                                                                                                                                                                                                                                    |

#### Object leading bytes — BEEF version word

The object is the record's `Object` field, or the whole payload when the payload is a bare object.

| Object `[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, TopicID and DeliverCount appear in every fragment; ContentID is the reassembly verification hash. Reassembly MUST key slots on the (ContentID, TopicID) pair. The record's field order puts the topic list before the object, so fragment 0 carries the whole list and an edge may discard a non-matching object before reassembling it; that order is fixed for this reason. 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)
```

The ingress emits the record as **one** `FrameVer 0x09` frame at any topic count: the payload is the record verbatim, `TopicID = SHA-256(topics[0])`, ContentID is over the record, and the frame goes to the first topic's group. 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.

#### Deliverable topics and labels

A record names 1..15 topics on every path and is never rejected for its count. What the ingress path decides is how many of the leading names are **deliverable** — matched by delivery edges against subscribers' elections — and it writes that number into `DeliverCount`. Every further name is a **label**: it reaches every subscriber the object reaches, inside the payload, and is never matched on, so a subscriber electing only a label topic does not receive the object.

* **Open / public / anonymous ingress** MUST set `DeliverCount = 1`. One deliverable topic per anonymous record is what keeps the free door free of amplification: a record costs one frame and reaches one topic's subscribers however many names it carries.
* **Authenticated / consumer-tunnel ingress** MAY set `DeliverCount` up to the operator's cap, which is at most 15 and is operator policy. The operator's fan-out exposure is bounded by that cap, not by `TopicCount`.

`DeliverCount` is the ingress's to set. An ingress accepting a pre-framed `FrameVer 0x09` MUST overwrite it from its own policy for that source, and MUST reject a pre-framed record whose header TopicID is not the hash of its first topic. There is no per-topic charge on either path: nothing beyond the deliverable prefix is delivered, so nothing beyond it is priced, and a subscriber's cost stays what its lane carried.

A subscriber that elected more than one of a frame's deliverable topics receives the object **once**, under the first elected topic in record order; the delivery record below names that topic.

#### Detection on shared ports

BEEF submission records MAY ride the open transaction port 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 identifier of the elected topic that matched)
 32       4   PayloadLen  (uint32 BE, ≥ 1)
 36       …   Payload     (the frame payload verbatim: the submission record, or a bare object)
```

The TopicID is the one of the subscriber's elected topics that the edge matched, which need not be the frame's header TopicID. The payload is the frame payload verbatim, so when the publisher submitted a record the subscriber receives every topic name it wrote, deliverable and label alike, and can map the matched identifier back to one of them; a subscriber that receives a bare object maps the identifier back from its own election. The subscriber tells the two forms apart by the payload's leading bytes exactly as the frame does. Subscribers taking whole `FrameVer 0x09` frames instead of a stripped lane need no record; the frame already carries the identifiers and the payload.

## 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
* [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, inscription rules, or BSV-21 admission — see [BRC-159](/tokens/0159) / [BRC-160](/tokens/0160) / [BRC-150](/tokens/0150) / [BRC-176](/tokens/0176).
* 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).


# Subject BEEF

David Case (<david.case@shruggr.cloud>)

## Abstract

**Subject BEEF** is [Atomic BEEF](/transactions/0095) in shape (a prefix and a subject transaction id in front of a [BRC-96](/transactions/0096) BEEF V2) without BRC-95's restriction to the subject's ancestors. The subject names which transaction the bag is about. Anything else in the bag is allowed: a profile decides what must be present.

## Specification

|                  | Atomic BEEF ([BRC-95](/transactions/0095))   | Subject BEEF (this BRC)                |
| ---------------- | -------------------------------------------- | -------------------------------------- |
| Prefix (4 bytes) | `0x01010101`                                 | `0x5709beef`                           |
| Subject          | TXID (32 bytes)                              | TXID (32 bytes)                        |
| Body             | BEEF                                         | BEEF V2 ([BRC-96](/transactions/0096)) |
| Graph            | Ancestors of the subject transaction         | Any transactions                       |
| Fail if          | Subject tx missing, or unrelated txs present | Subject tx missing                     |

```
5709beef                                                 // prefix
2222222222222222222222222222222222222222222222222222222222222222 // subject TXID
0200beef                                                 // BEEF V2 (example)
...
```

## Notes (informative)

* A reader MUST NOT apply BRC-95's ancestor rule or strip transaction bodies from a Subject BEEF. Re-encoding it as Atomic BEEF may drop transactions a profile requires.
* Completeness is profile-defined (e.g. [BRC-176](/tokens/0176)). Subject BEEF alone says only which transaction is the subject.
* The distinct prefix lets a strict Atomic BEEF reader fail cleanly instead of rejecting a valid bag as malformed Atomic BEEF.
* For a subject output rather than a transaction, use [Outpoint BEEF](/transactions/0158).


# 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




---

[Next Page](https://bsv.brc.dev/llms-full.txt/1)

