# BRC-222: 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 properties follow, and each is unavailable to an e-signature service whose evidence lives in its own database. **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.md) handle; and how people who already hold a [BRC-100](../wallet/0100.md) wallet receive signing requests in it and sign with it. No new cryptographic primitive is introduced. ## Motivation **The deployed thing.** 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. There was no document describing how signing order, identity and authority, rather than mere existence, could be settled by data the verifier holds. **Why this cannot be avoided by cleverness.** Three approaches suggest themselves and all three fail. 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. Requiring every party to co-sign one final transaction does not help either. It proves that everybody agreed at the end. It loses the order, loses the gate (an authoriser who approves before a later signer is the point of the role), and needs every party present at once, which is exactly what asynchronous signing exists to avoid. Trusting the coordinator less by trusting a different coordinator more, a notary service or a qualified trust service provider, is a legal improvement and a structural non-change. The evidence still lives with one party. What survives 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. The coordinator is still needed, to hold the token and pay for the transactions, and it is named, replaceable, and unable to do anything a verifier would not catch. ## Prior Art This document exists because the pieces were already here. The step certificate is [BRC-220](./0220.md)'s, the state token is a [BRC-48](../scripts/0048.md) PushDrop output, the key derivation is [BRC-42](../key-derivation/0042.md) and [BRC-43](../key-derivation/0043.md), the identity certificates are [BRC-52](../peer-to-peer/0052.md) and [BRC-53](../wallet/0053.md), key linkage revelation is [BRC-69](../key-derivation/0069.md) protected by [BRC-72](../key-derivation/0072.md), domain trust anchors are [BRC-68](../peer-to-peer/0068.md), handles, messageboxes and delegation are [BRC-169](../peer-to-peer/0169.md), the encrypted payloads are [BRC-2](../wallet/0002.md) and [BRC-78](../peer-to-peer/0078.md), the SPV proof is [BRC-95](../transactions/0095.md) verified per [BRC-67](../transactions/0067.md), and the overlay is [BRC-22](../overlays/0022.md) and [BRC-24](../overlays/0024.md). 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.** Being precise about which is which matters, because a document claiming novelty it does not have invites a reviewer to go looking for the rest of what it got wrong. **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](./0220.md) 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.md) 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 are held in secure hardware and are what non-technical users already sign in with. 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 results below are not, and they are stated as claims a reviewer can check rather than as a summary of effort. **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.md) 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.md) 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": "" } ], "parties": [ { "partyId": 0, "role": "sender", "address": "@crumbs@nexus.app" }, { "partyId": 1, "role": "signer", "address": "anna@acme.example", "requirements": { "level": "verified-role", "certificates": [ { "type": "", "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:` 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 `"."` | #### 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.md) and [BRC-43](../key-derivation/0043.md): | Parameter | Value | | --- | --- | | `protocolID` | `[1, "consent envelope step"]` | | `keyID` | `-` | | `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.md) 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 ` 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.md). 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.md) 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](./0220.md) 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.md) and operating as a strict overlay in the sense of [BRC-183](../overlays/0183.md). 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.md). A domain MAY advertise its lookup host per [BRC-180](../overlays/0180.md). ### 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": "" }`. `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](./0220.md) 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": ".", "stepIndex": 1, "stepType": 1, "partyId": 1, "prevOutpoint": ".", "prevStepDigest": "", "fieldsHash": "", "evidenceHash": "", "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": "", "authenticatorData": "", "clientDataJSON": "", "signature": "" } ``` 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": "", "certificates": [ "" ], "domainObservations": [ "
" ], "delegation": "" } ``` 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.md) or [BRC-97](../wallet/0097.md) 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.md). Only `evidenceHash` is public. #### 6.7 Certificate types Three [BRC-52](../peer-to-peer/0052.md) 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](./0227.md). 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.md) 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": "" }`. 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.md) at `https:///manifest.json` under `metanet.trust`. That key is the certifier for `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.md) 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.md): ```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 | `"."` 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.`: ``` v=metanet-trust1; k=<66 hex characters, compressed secp256k1 key>; n= ``` 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 no past signature may depend on what a domain publishes when it is verified. 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": "", "fetchedAt": "2027-03-02T10:11:12Z", "contentSha256": "", "finalUrl": "https://acme.example/manifest.json", "coObservations": [ { "observer": "", "signature": "" } ] } ``` 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.md) 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 `@[+tag]@`, 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.md) 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.md). 2. Each object key is wrapped for each party that has an identity key with [BRC-2](../wallet/0002.md), `protocolID [2, "consent envelope document key"]`, `keyID -`, 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.md). #### 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=`. 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": ".", "coordinator": "https://signing.example", "role": "signer", "partyId": 1, "definitionUrl": "uhrp://...", "definitionKeyWrap": "", "documentUrls": ["uhrp://..."], "documentKeyWraps": [""], "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": ".", "definition": { "...": "section 3, including the salt" }, "documents": [ { "index": 0, "finalLength": 70, "finalSha256": "" } ], "steps": [ "" ], "evidence": [ "" ], "beef": "", "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.md) 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.md) 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.md) 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.md) or [BRC-225](../peer-to-peer/0225.md), 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.md) shares. ### 15. Verification, in Order Given a PDF, a verifier performs these checks in this order. Each has its own result code, each fails alone, and the order is what makes failures attributable. 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.md) 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 Recorded so that the next author does not rediscover them or ship them incompatibly. **Batch-only anchoring.** An earlier draft allowed high-volume envelopes to skip the state token and anchor steps in [BRC-220](./0220.md) 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](./0220.md) 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.md) 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.md) 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.md) and [BRC-53](../wallet/0053.md) own certificates and selective revelation. Section 6.7 defines three types and no new mechanism. [BRC-100](../wallet/0100.md) 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.md) 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* where it always was, with the identity level telling a verifier how much of it there is. ## 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 | Two lines deserve drawing out. **The coordinator is charged the most and kept the least.** Every property that makes it untrusted also removes what an incumbent platform monetises: the archive, the audit trail, the lock-in. A coordinator under this document competes on service, and that is the intended result. **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 second half 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. It 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 states which it reaches, because "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 is buildable before any coordinator exists, against the Test Vectors, and it is what makes everything else in this document worth trusting. ## 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 | Two rows deserve reading together. **A signature was moved** and **a step was signed by a key that is not a party's** are the two ways a history can be dishonest, and section 15's order separates them: check 5 catches the first before policy is consulted, and only a failure at check 6 with check 5 passing implicates the controller's choice of what 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--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 These are the vectors worth having, because each one is a failure section 15 must 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.md) - [BRC-22: Overlay Network Data Synchronization](../overlays/0022.md) - [BRC-24: Overlay Network Lookup Services](../overlays/0024.md) - [BRC-26: Universal Hash Resolution Protocol](../overlays/0026.md) - [BRC-29: Simple Authenticated BSV P2PKH Payment Protocol](../payments/0029.md) - [BRC-33: PeerServ Message Relay Interface](../peer-to-peer/0033.md) - [BRC-42: BSV Key Derivation Scheme (BKDS)](../key-derivation/0042.md) - [BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties](../key-derivation/0043.md) - [BRC-48: Pay to Push Drop](../scripts/0048.md) - [BRC-52: Identity Certificates](../peer-to-peer/0052.md) - [BRC-53: Certificate Creation and Revelation](../wallet/0053.md) - [BRC-60: Simplifying State Machine Event Chains in Bitcoin](../state-machines/0060.md) - [BRC-64: Overlay Network Transaction History Tracking](../overlays/0064.md) - [BRC-67: Simplified Payment Verification](../transactions/0067.md) - [BRC-68: Publishing Trust Anchor Details at an Internet Domain](../peer-to-peer/0068.md) - [BRC-69: Revealing Key Linkages](../key-derivation/0069.md) - [BRC-72: Protecting BRC-69 Key Linkage Information in Transit](../key-derivation/0072.md) - [BRC-78: Serialization Format for Portable Encrypted Messages](../peer-to-peer/0078.md) - [BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services](../overlays/0087.md) - [BRC-94: Verifiable Revelation of Shared Secrets Using Schnorr Protocol](../key-derivation/0094.md) - [BRC-95: Atomic BEEF Transactions](../transactions/0095.md) - [BRC-97: Extensible Proof-Type Format for Specific Key Linkage Claims](../wallet/0097.md) - [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](../wallet/0100.md) - [BRC-137: Device-Aware Wallet Onboarding and Fallback Login for BRC-100 Applications](../opinions/0137.md) - [BRC-138: Single-Use Signed Proofs for Request Authentication](../peer-to-peer/0138.md) - [BRC-140: Threshold Key Sharing and Backup via Shamir's Secret Sharing Scheme](../key-derivation/0140.md) - [BRC-141: Fountain-Coded Air-Gap Transport for Arbitrary Payloads](../peer-to-peer/0141.md) - [BRC-169: Universal Handle Addressing and Resolution for the Metanet](../peer-to-peer/0169.md) - [BRC-180: Overlay Service Discovery at an Internet Domain](../overlays/0180.md) - [BRC-181: Wallet-Enforced Autonomous-Agent Spend Policy](../wallet/0181.md) - [BRC-183: A Framework for Strict and Federated Overlays](../overlays/0183.md) - [BRC-220: NotaryHash, Privacy-Preserving Signed-Hash Notarization with SPV-Verifiable Certificates](./0220.md) - [BRC-225: Animated-QR Air-Gap Transport for Arbitrary Payloads (TKQR1)](../peer-to-peer/0225.md) - [BRC-227: Frictionless On-Chain Onboarding via Pre-Funded Claimable Tokens](./0227.md) - [BRC-369: Keyed Content and Conditional Key Release](../peer-to-peer/0369.md) - [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