Skip to content

Latest commit

 

History

History
962 lines (675 loc) · 78.8 KB

0222.md

File metadata and controls

962 lines (675 loc) · 78.8 KB

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 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 handle; and how people who already hold a BRC-100 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's, the state token is a BRC-48 PushDrop output, the key derivation is BRC-42 and BRC-43, the identity certificates are BRC-52 and BRC-53, key linkage revelation is BRC-69 protected by BRC-72, domain trust anchors are BRC-68, handles, messageboxes and delegation are BRC-169, the encrypted payloads are BRC-2 and BRC-78, the SPV proof is BRC-95 verified per BRC-67, and the overlay is BRC-22 and BRC-24. 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) 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 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 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) 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 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 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 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} )
  1. 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

{
  "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 and BRC-43:

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 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. 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 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 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 and operating as a strict overlay in the sense of BRC-183.
  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. A domain MAY advertise its lookup host per BRC-180.

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

"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:

"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:

{
  "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 or BRC-97 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. Only evidenceHash is public.

6.7 Certificate types

Three BRC-52 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.
  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 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 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 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:
{
  "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:

{ "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 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) 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 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.
  2. Each object key is wrapped for each party that has an identity key with BRC-2, 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.

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:

{ "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

{
  "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 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 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 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 or BRC-225, 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 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 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 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 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 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 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 and BRC-53 own certificates and selective revelation. Section 6.7 defines three types and no new mechanism.

BRC-100 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 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):

{"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