Beersy
BRC-179

Signed Capability Manifest for Wallet and Tool-Server Method Exposure

A wallet or agent tool-server ships with a big list of methods, and the moment someone registers a new one on the server, it's live, whether or not anyone meant to expose it. There's no signed, checkable list of exactly which methods a given build actually allows, so an operator or auditor can't tell what surface they're exposing versus what quietly crept in. This makes it hard to reason about what an autonomous agent wallet can actually touch.

RexStarBSVchanged 18 Sept 202624 min read

Reference for an AI

Everything an assistant needs to answer questions about BRC-179 accurately, including what it depends on.

Summary

Why
Wallet and tool-server builds can quietly expose new or risky methods the moment they are registered, with no signed record of what surface is actually live, so this exists to make the exposed method set explicit, tiered, and tamper-evident.
What
BRC-179 is a signed, risk-tiered that declares exactly which wallet and tool-server methods a build exposes, enforced by a runtime gate that denies anything not declared and enabled.
How
A build declares its full method list as JSON capability records with id, tier, and allow state, computes a domain-separated SHA-256 fingerprint over the trust-bearing fields, embeds that fingerprint as a pin, and at runtime an enforcer checks every method call against the loaded manifest before letting it do anything.

What this lets you do

  • Declare exactly which SDK or tool-server methods a wallet exposes, tagged by risk tier
  • Keep risky methods (spending, signing, identity, network) off by default until an operator explicitly turns them on
  • Pin that fingerprint into the build so a swapped or edited manifest file refuses to run
  • Reject calls to any method that was not declared and enabled, before any session or auth logic runs

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

method callmanifest+tierallow/deny

The specification

Abstract

This BRC defines a capability manifest: a declarative, risk-tiered, operator-gated document that enumerates exactly which SDK methods and tool-server (MCP) methods a wallet or wallet-adjacent product exposes, together with a runtime enforcer that refuses any method not affirmatively declared and enabled. Each exposed method is a Capability with a globally-unique id of the form product.surface.method, a risk tier drawn from an ordered taxonomy (READ, VERIFY, OPS, CRYPTO, IDENTITY, NETWORK, SPEND, ADMIN), and an optional operator allow-overlay. Read/verify tiers are exposed by default; every tier that mutates state, uses a key, moves value, asserts identity, or reaches the network is withheld by default until an operator affirmatively allows it. The set of (id, tier, effective-enabled) triples is reduced to a domain-separated SHA-256 fingerprint that is stable under documentation edits but changes the instant a capability is added, re-tiered, or toggled. That fingerprint is baked into the build as a pin: at construction the enforcer refuses to run if the loaded does not fingerprint to the pin, so a swapped or edited manifest file cannot widen the exposed surface at runtime; Section 9 states precisely what that does and does not cover. Decisions are fail-closed: an undeclared method is denied, never silently allowed. Where BRC-100 governs per-session application requests and BRC-103/104 govern who is calling, this specification governs the orthogonal supply-side axis (which methods are callable at all) and it is designed to be sealed by a content-anchor code-provenance ordinal.

Motivation

The BSV wallet-interface stack answers three questions well and one question not at all.

  • BRC-100 () standardizes the request/permission surface: an application asks the wallet to createAction, createSignature, getPublicKey, etc., and the wallet may prompt the user per session//protocol. This governs what a given is permitted to ask at request time.
  • BRC-103 / BRC-104 (Peer-to-Peer Mutual Authentication and Exchange, and its HTTP transport) govern identity of the caller, mutual authentication and certificate presentation so the wallet knows who is on the other end of the connection.

None of these answer the supply-side question: of the entire method surface a wallet SDK or an agent/MCP tool-server compiles in, which methods are exposed at all, and how does an operator see, scrutinize, and withhold individual methods before shipping? In practice an MCP server or SDK ships with a large method table; a new method added to the server is live the moment it is registered. BRC-100 will still prompt for it, and BRC-103 will still authenticate the caller, but neither the operator nor a downstream auditor has a signed, machine-checkable statement of the exact exposed set, nor a mechanism that makes "this build exposes exactly these methods and no others" tamper-evident. This is acute for autonomous agent wallets and tool-servers, where the caller is software, spend authority is delegated under policy, and "which tools the agent can even reach" is a primary containment boundary.

This BRC fills that gap. It is deliberately complementary, not competing: the manifest is consulted before BRC-100 permissioning and BRC-103 authentication run, if a method is not in the manifest and enabled, the call is refused before any session or identity logic executes. The fingerprint is designed to be the value a build's code-provenance seal commits to, so that widening the surface necessarily changes the build's published fingerprint. It pairs with a content-anchor code-provenance ordinal (the running-code-fingerprint-as-ordinal principle): a build's seal is minted over build_identity || manifest_fingerprint, making surface-widening publicly evident.

Specification

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

1. Terminology

  • Capability: one exposed SDK method or tool-server method, described by a record (Section 3).
  • Manifest: the complete declared set of Capabilities for a build, plus header fields (Section 6).
  • Effective-enabled: the resolved boolean exposure state of a Capability after applying the operator to the tier default (Section 5).
  • Fingerprint: the domain-separated SHA-256 over the trust-bearing projection of the manifest (Section 7).
  • Pin: a fingerprint value fixed into a build against which the loaded manifest is checked (Section 9).
  • Enforcer: the runtime object that renders and enforces per-Capability decisions (Sections 10-11).

2. Domain constant

Implementations MUST use the exact domain-separation string:

bsv-capability-manifest/v1

This value is denoted DOMAIN below. It appears both as the manifest schema field (Section 6) and inside the fingerprint preimage (Section 7). A manifest whose schema is not byte-equal to DOMAIN MUST be rejected at load.

3. Capability record

A Capability is the of fields in the table below. Fields are marked trust-bearing if they participate in the fingerprint (Section 7); the rest are documentation.

FieldTypeRequiredTrust-bearingMeaning
idstringyesyesGlobally-unique id, MUST equal product.surface.method (Section 4).
productstringyesno (bound via id)Owning product, e.g. wallet, marketplace, messaging, content-anchor, signer, media.
surfacestringyesno (bound via id)The SDK or tool-server surface, e.g. agent-mcp, wallet-connect, marketplace-mcp, messaging-core.
methodstringyesno (bound via id)The exposed method / tool name.
tierenumyesyesOne of the risk tiers in Section 5.
descriptionstringno (default "")noPlain-language description. Documentation only.
grounded_instringno (default "")noRepo-relative file(s) proving the method exists. Documentation only.
allowboolean or nullno (default null)indirectly (via effective)Operator override: true = expose, false = withhold, null = use tier default.

the deliberate design: product, surface, and method are individually non-trust-bearing because their concatenation id is trust-bearing and is checked to equal them (Section 4); description and grounded_in are never trust-bearing (Section 7.3); and allow influences the fingerprint only through the derived effective boolean (Section 5), so two manifests that resolve to the same effective exposure fingerprint identically regardless of whether exposure came from an explicit allow:true or a tier default.

4. Capability id grammar

The id MUST be exactly the three dot-joined components:

id = product "." surface "." method

An implementation constructing or loading a Capability MUST reject it (with a structural error) if id is not byte-equal to the string formed by joining product, surface, and method with .. This makes the id self-describing and prevents a manifest from labeling a capability with a misleading id. Components SHOULD be treated as opaque tokens; this BRC does not further constrain their internal character set, but implementers SHOULD avoid embedding . inside a component to keep the id parseable back into its three parts. Within a single manifest every id MUST be unique; a duplicate id MUST be rejected at construction.

5. Risk-tier taxonomy and default-exposure rule

There are exactly eight tiers, in this canonical order from least to most sensitive:

READ, VERIFY, OPS, CRYPTO, IDENTITY, NETWORK, SPEND, ADMIN
TierMeaningDefault exposure
READRead-only data (balances, listings, history, chain ).on
VERIFYPure verification, no keys, no state mutation, no network.on
OPSMutates persistent state without creating or broadcasting an outgoing . Membership is heterogeneous and MUST be reviewed per capability, not inferred from the tier name: in the reference inventory four of the five members write local management state (triage items, status advancement, checklist sign-offs), while the fifth is the money-in path, which derives a wallet private key and credits the spendable default basket. Implementers MUST NOT assume this tier is free of keys or of value.off
CRYPTOUses a key to sign / / encrypt / decrypt.off
IDENTITYIssue / prove / relinquish certificates, reveal key linkage.off
NETWORKTriggers outbound network (broadcast, relay, fetch-by-id, onion connect).off
SPENDCreates or broadcasts a value-moving action.off
ADMINConfiguration / operator / webhook-registration surfaces.off

The default-exposure rule is: a tier is on-by-default iff it is in the set {READ, VERIFY}. All other tiers are off-by-default. Formally:

default_enabled(tier) = tier ∈ {READ, VERIFY}

An implementation MUST reject a Capability whose tier is not one of the eight listed values. The tier ordering is normative and is used by tooling for grouping and for tier-scoped overlay selectors (Section 12); it does not by itself imply that a higher tier is "more denied", every off-by-default tier is equally off until the operator allows it.

6. Manifest document format

A serialized manifest is a JSON object with these fields:

FieldTypeRequiredMeaning
schemastringyesMUST equal DOMAIN (Section 2); else reject.
manifest_versionstringno (default "0.0.0")Human version of the inventory. Not fingerprinted.
notestringno (default "")Free-text note. Not fingerprinted.
fingerprintstringnoIf present, MUST equal the recomputed fingerprint (Section 8); else reject as tampering.
capabilitiesarrayyesArray of Capability records (Section 3), each serialized with fields id, product, surface, method, tier, allow, description, grounded_in.

A Capability row missing any of the required fields id, product, surface, method, tier MUST cause a load error. description, grounded_in, and allow MAY be omitted and default to "", "", and null respectively. The manifest_version and note fields are metadata and MUST NOT influence the fingerprint.

7. Canonical fingerprint

The fingerprint is the single value that certifies "this build exposes exactly these capabilities." It is computed over a trust-bearing projection only.

7.1 Per-capability projection

For each Capability, form the object with exactly these three keys:

{ "id": <id>, "tier": <tier>, "enabled": <effective-enabled> }

where effective-enabled is resolved per Section 5:

effective(cap) = default_enabled(cap.tier) if cap.allow is null
 = bool(cap.allow) otherwise

enabled is a JSON boolean. No other field of the Capability appears.

The Required column of Section 3 describes the record, which always carries every field once constructed. Section 6 governs the document, where description, grounded_in and allow MAY be omitted and default to "", "" and null. The two are not in tension: a document may leave them out, and the record that results still has them. An implementation whose record type makes them mandatory arguments is conformant, provided its document parser supplies the defaults.

allow is a JSON boolean or null, and an implementation MUST reject any other value as a structural error (Section 13) rather than coercing it. The effective-state rule resolves a non-null allow by truthiness, so a value such as the JSON string "false" would resolve to enabled, exposing the capability that whoever wrote it plainly meant to withhold. The fingerprint would then record enabled: true faithfully, so the mistake is invisible to an auditor comparing fingerprints and survives every downstream check. Reject at load.

7.2 Assembly and canonicalization

  1. Sort the Capabilities by id in ascending Unicode code point order. Code point order and UTF-8 byte order agree, so either may be used to implement it. UTF-16 code unit order does NOT agree and MUST NOT be used: it places characters above U+FFFF before those in U+E000 to U+FFFF, which a language whose default string comparison is over UTF-16 code units will do silently.
  2. Build the list body of per-capability projection objects, in that sorted order.
  3. Build the preimage object:
{ "domain": DOMAIN, "capabilities": body }
  1. Canonicalize the preimage to a string by serializing as JSON with: keys sorted ascending at every object level, no insignificant whitespace (item separator ",", key/value separator ":"), and all non-ASCII characters \uXXXX-escaped (ASCII-only output). Three further rules are REQUIRED, because each is a degree of freedom that changes the digest and each has a common serializer that chooses the other way:
    • The solidus / is NOT escaped. DOMAIN contains one, so it appears in every preimage ever computed, and a serializer that escapes it by default produces a different fingerprint for every manifest in existence rather than only for unusual inputs.
    • Hexadecimal digits in a \uXXXX escape are lowercase.
    • A character above U+FFFF is escaped as the UTF-16 surrogate pair, two \uXXXX escapes. A six-digit or \U-style escape is not this format. Under this canonicalization the outer object serializes with keys in the order capabilities, domain, and each projection object serializes with keys in the order enabled, id, tier.
  2. Encode the canonical string as ASCII bytes and compute SHA-256. The fingerprint is the lowercase hex digest.

Only JSON strings and booleans appear in the preimage, so the canonicalization is unambiguous without a number-formatting rule; implementations that use a different canonical-JSON library MUST reproduce the exact byte sequence described here (this is the specific form emitted by a sorted-keys, tight-separator, ASCII-escaped JSON serializer, it is not required to be RFC 8785 JCS. The two agree on any preimage whose capability ids are ASCII, which is every id in the reference inventory, and they disagree when an id contains non-ASCII: JCS emits raw UTF-8 and does not escape, while this canonicalization is ASCII-only. Section 4 permits non-ASCII ids, so an implementation MUST follow the rules above rather than delegate to a JCS library.)

7.3 What the fingerprint covers and deliberately excludes

  • Covered (any change re-fingerprints): adding a capability, removing a capability, changing a capability's id, changing a capability's tier, and any change to a capability's effective-enabled (whether caused by an allow toggle or, indirectly, by a tier default).
  • Deliberately excluded (changes do NOT re-fingerprint): description, grounded_in, manifest_version, note, and the distinction between "enabled by tier default" vs "enabled by explicit allow:true". Descriptions and file references are documentation and are not trust-bearing; an implementation MUST NOT let them influence the fingerprint. This is a security property: an auditor comparing two builds by fingerprint is comparing exposed behavior, and cannot be misled by prose, nor forced to re-mint a provenance seal for a wording fix.

The consequence, called update coupling: a build's SDK/tool-server contract version is a function of the capability fingerprint, not of the application version. An app update forces a manifest + provenance-seal re-mint iff it moves the fingerprint. The fingerprint is the machine test for "did the exposed contract change."

8. Load-time integrity

When loading a serialized manifest:

  1. Reject if schema != DOMAIN.
  2. Parse every Capability row, enforcing the id-grammar (Section 4), the tier check (Section 5), and uniqueness of ids.
  3. Recompute the fingerprint (Section 7).
  4. If the document carries a fingerprint field, it MUST byte-equal the recomputed value; otherwise the load MUST fail (a stored fingerprint disagreeing with its own body is tampering and MUST be caught at load).

9. Build-pin enforcement

The runtime enforcer is constructed with a pinned_fingerprint value fixed by the build rather than derived from the manifest it is handed. An implementation SHOULD embed it at build time. Where a build permits it to be overridden at run time, for a review harness, that override MUST be reported on startup, because a pin nobody can see changing is not a pin.

  • At construction the enforcer MUST recompute the fingerprint of the manifest handed to it and, if pinned_fingerprint is non-null and does not byte-equal the recomputed fingerprint, MUST refuse to construct (raise a pin error). The guarantee this provides, stated precisely. The only manifest an enforcer will run is one that fingerprints to the pinned value, so a swapped or edited manifest file cannot widen the exposed surface. That is the whole of it, and the threat it answers is a caller of the exposed surface, which controls neither the host nor the build.

It is not a guarantee against an attacker who already controls the host. Whoever can set the environment or edit the source that carries the pin can re-pin to a widened manifest, and no check inside the process can prevent that: the process would be verifying itself with a value the attacker chose. An implementation MUST NOT describe this mechanism as protection against host compromise. Against that threat the has to be outside the build, an independently published fingerprint of the running artifact that a third party can compare, which Section 14 describes and this section deliberately does not claim to be.

  • A pinned_fingerprint of null disables the check. Implementations MUST support this only for review/testing harnesses and MUST NOT ship a production build with a null pin.

A pin covers the surface whose enforcer holds it, and no other. A manifest may declare capabilities for many surfaces, and declaring them is documentation. Enforcement happens only where a process constructs an enforcer over that manifest and routes its calls through it. In a deployment where one of several declared surfaces does so, the pin protects that surface's capabilities and the rest are described but unguarded. An implementation SHOULD state which surfaces construct an enforcer, because a reader counting declared capabilities will otherwise overcount what is enforced.

This mirrors the issuer-key build-pin discipline used elsewhere in the suite (reachability and update-channel pins): the build pin is authoritative over anything supplied at runtime.

10. Decision semantics (fail-closed)

For a requested capability id, the enforcer renders exactly one of four decisions:

DecisionCondition
ALLOWThe id is declared and its effective-enabled is true.
DENY_UNKNOWNThe id is not declared in the manifest.
DENY_TIER_DEFAULTThe id is declared, not enabled, and allow is null (off by tier default; operator has not enabled it).
DENY_OPERATORThe id is declared, not enabled, and allow is false (operator explicitly disabled it).

The decision algorithm MUST be:

decide(id):
 cap = manifest.get(id)
 if cap is None: return DENY_UNKNOWN # fail-closed
 if effective(cap): return ALLOW
 if cap.allow is False: return DENY_OPERATOR
 else: return DENY_TIER_DEFAULT

There MUST be no "unknown = allow" path. An undeclared id is never allowed. A method added to a server without a corresponding manifest entry is therefore dead until it is declared and reviewed.

11. Enforcement boundary

Every exposed SDK method and tool-server method MUST call the enforcer's fail-loud gate with its own capability id before performing any work (before argument-dependent side effects, before BRC-100 permissioning, before BRC-103 authentication). The gate MUST:

  • return normally (permit execution) iff decide(id) == ALLOW;
  • otherwise raise/return a loud error carrying the id and a human-readable reason. Reason strings SHOULD be:
  • DENY_UNKNOWNnot declared in the capability manifest (fail-closed)
  • DENY_TIER_DEFAULTdisabled by default for its risk tier, operator has not enabled it
  • DENY_OPERATORexplicitly disabled by the operator

The gate MUST NOT fall through to execution on any denied decision.

12. Operator allow-overlay

The operator review surface is an overlay: a mapping from capability id to true / false / null. Applying an overlay to a manifest yields a new manifest in which each referenced capability's allow is replaced by the overlay value (and unreferenced capabilities keep their prior allow). Overlay application MUST fail loud if it references any id not declared in the manifest, an operator cannot toggle a capability that does not exist. Because the resulting manifest's fingerprint reflects the new effective states, the operator's decisions are load-bearing (they change the fingerprint the build's provenance seal must commit to), not advisory. Tooling MAY expose selectors that expand an id-prefix (a product or product.surface) or a tier name to the set of matching ids, so that "withhold everything that spends" is a single deny SPEND action; such expansion is a tooling convenience and MUST resolve to explicit per-id overlay entries before application.

13. Error taxonomy

Implementations MUST distinguish at least:

  • Structural / manifest error: unknown tier, id-grammar violation, duplicate id, missing required field, schema mismatch, or an overlay referencing an undeclared id.
  • Pin / tamper error: a stored fingerprint that disagrees with the recomputed body (Section 8), or a manifest whose fingerprint does not match the build pin (Section 9). These are distinct from generic structural errors because they signal a possible swap, not merely malformed input.
  • Decision error: the fail-loud result of gating a denied capability (Section 11).

14. Relationship to BRC-100 / BRC-103 / BRC-104, the reserved namespaces, and provenance sealing

  • The manifest gate runs first. A capability that is DENY_* never reaches BRC-100 permission prompts or BRC-103 authentication, it is refused at the supply boundary.
  • BRC-100 continues to govern per-session, per-counterparty requests for capabilities that the manifest exposes; the manifest does not replace user consent, it bounds the menu consent can be asked about.
  • BRC-103/104 continue to authenticate the caller; the manifest is caller-independent (it constrains the server's own surface).
  • The fingerprint (Section 7) is the value a code-provenance ordinal seals: mint over build_identity || manifest_fingerprint. This makes "this build exposes exactly these capabilities and no others" a publicly verifiable, tamper-evident claim, and forces any surface-widening to change the published seal.
  • BRC-98 modules and this manifest act on different axes, and they compose. BRC-98 reserves protocol identifiers beginning with p and lets a supported scheme define behaviour at that dispatch point. BRC-229 is the worked example: it defines the ecpm scheme and, in its own words, "uses that reserved dispatch point to specialize the meaning of an existing BRC-100 method". A BRC-98 module therefore changes what an already-exposed method means for a caller who names the reserved identifier. This specification governs whether that method is exposed at all, evaluated before any caller is consulted and independently of what any caller asks for. A wallet implementing both evaluates the manifest first: if the underlying method is DENY_*, the module's dispatch point is unreachable, and the module's presence does not widen the surface. Implementers SHOULD give a BRC-98 module's underlying method its own capability id, so the exposure decision is recorded explicitly rather than inherited.
  • BRC-111 labels are likewise orthogonal. A describes or permissions an individual action; a capability id names a method on a server's surface. Neither namespace is claimed here, and a manifest MUST NOT be read as reserving any identifier under p .

15. What is deliberately absent from a manifest

A manifest lists exposed capabilities only. Air-gap co-signer internals (key generation, cosigning, handling), raw key material, egress choke points, deniability containers, subscription/issuer keys, and admin/issuer operations MUST NOT be listed merely to document them: appearing in the manifest is what makes a method eligible to be enabled and executed. Anything that must remain non-exposed is simply absent, and if it ever needs exposure it MUST be added and reviewed, never assumed.

Test Vectors

All fingerprints below are lowercase SHA-256 hex digests reproduced from the reference implementation. DOMAIN = bsv-capability-manifest/v1.

Vector 1, tier defaults, no operator overlay

Two declared capabilities, both with allow = null:

idtierallow
example.agent-mcp.getBalanceREADnull
example.agent-mcp.createActionSPENDnull

Effective states (Section 5): getBalancetrue (READ default-on); createActionfalse (SPEND default-off).

Canonical fingerprint preimage (Section 7.2), exact bytes:

{"capabilities":[{"enabled":false,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":true,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}

Fingerprint:

f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc

Decisions (Section 10): decide("example.agent-mcp.getBalance")ALLOW; decide("example.agent-mcp.createAction")DENY_TIER_DEFAULT; decide("example.agent-mcp.sweepBsv") (undeclared) → DENY_UNKNOWN.

Vector 2, operator allows the SPEND capability

Same two capabilities, but the operator sets allow = true on createAction:

idtierallow
example.agent-mcp.getBalanceREADnull
example.agent-mcp.createActionSPENDtrue

Now createActiontrue. Preimage:

{"capabilities":[{"enabled":true,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":true,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}

Fingerprint:

5f1cbe881b0df89e4c7f37f5e6d0b9cfe0156e1a8f7e7776e318e615e35a3d4e

The fingerprint differs from Vector 1, the single overlay toggle re-fingerprints the build, which would force a provenance-seal re-mint. decide("example.agent-mcp.createAction") is now ALLOW.

Vector 3, operator explicitly disables a default-on capability

The operator sets allow = false on the READ capability (createAction back to null):

idtierallow
example.agent-mcp.getBalanceREADfalse
example.agent-mcp.createActionSPENDnull

Both effective states are false. Preimage:

{"capabilities":[{"enabled":false,"id":"example.agent-mcp.createAction","tier":"SPEND"},{"enabled":false,"id":"example.agent-mcp.getBalance","tier":"READ"}],"domain":"bsv-capability-manifest/v1"}

Fingerprint:

058a0c229fbd0a3830a45dfefecd0343e282f7200406ef58c62586c3679cdffa

decide("example.agent-mcp.getBalance")DENY_OPERATOR (distinct from Vector 1's DENY_TIER_DEFAULT for the same effective-off state, the reason preserves why it is off).

Vector 4, descriptions are not trust-bearing

Take the Vector 1 manifest and change the description and grounded_in of getBalance to arbitrary different strings, leaving id, tier, and allow unchanged. The per-capability projection (Section 7.1) is unchanged, so the fingerprint is identical to Vector 1:

f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc

This demonstrates Section 7.3: documentation edits do not move the fingerprint and do not require a provenance re-mint.

Vector 5, build-pin swap refusal

Construct an enforcer with pinned_fingerprint = f99054c516d0a079194338004f5cb7439331374fa2c219f15c4691d4904f85fc (the Vector 1 pin) but hand it the Vector 2 manifest (fingerprint 5f1c…). Because 5f1c… != the pin, the enforcer MUST refuse to construct with a pin/tamper error and MUST NOT enter service. Handing the same enforcer the Vector 1 manifest constructs normally. This is the runtime guarantee that a swapped manifest cannot widen the surface.

Implementations

A reference implementation exists (not a public path):

  • A stdlib-only manifest module (Python) implementing the Capability record with id-grammar and tier validation, the CapabilityManifest container (dedup, canonical fingerprint, overlay, load-time integrity check, JSON round-trip), the four-valued Decision enum with fail-closed semantics, and the CapabilityEnforcer with build-pin refusal and a fail-loud enforce() boundary gate.
  • A populated suite-wide JSON inventory carrying the schema, manifest_version, note, fingerprint, and capabilities fields described in Section 6, spanning the suite's products and surfaces (wallet agent-MCP, onion connect, marketplace client/MCP, messaging core, seal cores, air-gap signer verify surfaces).
  • A review/toggle/check/diff CLI implementing the operator overlay (Section 12), including product/surface/tier selector expansion, an integrity/CI check mode, and a build-to-build fingerprint diff.
  • A conformance test module covering fail-closed denial, tier defaults, fingerprint stability under documentation edits, build-pin swap refusal, overlay-of-undeclared-id refusal, and stored-fingerprint tamper detection.

Repo-relative grounded_in references inside each capability row point at the concrete file that proves the corresponding method exists, so the inventory is auditable against the real code.

Mathematical and cryptographic basis

The fingerprint is a binding to one exposed surface. It is SHA-256 over a canonical preimage that contains the domain string and the sorted list of (id, tier, enabled) projections and nothing else (Section 7). Canonicalization is injective on that projection: capabilities sorted by id, keys sorted, tight separators, and only strings and booleans present, so a given exposed surface maps to exactly one preimage and one digest. Any change to a trust-bearing element (adding or removing a capability, re-tiering it, or flipping its effective-enabled bit) changes the preimage and therefore the digest. Presenting a different exposed surface under an unchanged fingerprint would require a SHA-256 collision, which is infeasible.

Two properties follow from what the preimage includes and excludes. Documentation fields are outside the preimage, so a wording fix cannot move the fingerprint, and an auditor comparing two builds by fingerprint is comparing exposed behavior rather than prose. The domain string is folded in as the first element, so a manifest digest cannot coincide with a digest any other protocol computes over similar JSON, which closes cross-protocol confusion. The build pin supplies enforcement: at load the enforcer recomputes the fingerprint and refuses to run unless it equals the pinned value, so the commitment is checked against tampering before a single method is exposed, and the decision is fail-closed.

Security Considerations

  • Fail-closed is the whole point. The only path to execution is an id that is both declared and effective-enabled. An undeclared id (a newly registered tool, a typo, an injected id) is denied. Implementers MUST NOT add any default-allow or wildcard path.
  • Build-pin authority. The pin must be embedded in the build artifact, not read from any runtime-mutable source. If an attacker can supply the pin (env/config/file) they can defeat the check; the pin's value is precisely that it is not runtime-suppliable. A production build with a null pin (review mode) silently disables the protection and MUST be prevented by release tooling.
  • Trust-bearing projection. Because only (id, tier, effective) are fingerprinted, an attacker cannot smuggle behavior through description/grounded_in. Conversely, implementers MUST NOT be tempted to "help" by folding descriptions into the fingerprint, doing so would make every prose edit re-mint the provenance seal and erode the auditor's ability to diff behavior.
  • id ≡ product.surface.method. The self-consistency check prevents a manifest from mislabeling a spend method with a read-looking id. Enforcement callers MUST pass the method's own id; a shared or hard-coded id at multiple call sites would let one enabled capability gate an unrelated method.
  • Overlay integrity. Overlay application refuses undeclared ids, so an operator config cannot pre-authorize a not-yet-declared capability. The overlay changes the fingerprint, which is the feature: silent creep is impossible without a visible fingerprint change.
  • Canonicalization determinism. Two independent implementations MUST produce byte-identical preimages or their fingerprints (and therefore their pins and seals) will disagree. The preimage contains only strings and booleans; implementers MUST use sorted keys, tight separators, and ASCII escaping, and MUST NOT emit trailing whitespace, BOMs, or reordered keys.
  • Scope boundary vs. authorization. This BRC bounds which methods are exposed; it does not by itself authenticate callers (see BRC-103/104) or capture per-request user consent (see BRC-100). It is a necessary lower bound, run first, and MUST be composed with those layers, never used as a substitute for them.
  • Absence is protection. Sensitive internals (air-gap key handling, issuer keys, egress choke points, deniability containers) are protected by being absent from the manifest. Listing such a method (even disabled) makes it eligible to be toggled on; implementers MUST keep permanently-non-exposed surfaces out of the manifest entirely.

References

  • BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface, the per-session request/permission surface this specification is consulted ahead of.
  • BRC-103: Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol, caller authentication, complementary to this supply-side control.
  • BRC-104: HTTP Transport for BRC-103 Mutual Authentication.
  • BRC-2: Data Encryption and Decryption; BRC-3: Digital Signature Creation and Verification, example CRYPTO/VERIFY-tier methods referenced by manifest rows.
  • : (BKDS); BRC-43: , , and Counterparties, example key-derivation methods behind READ/CRYPTO rows.
  • BRC-52: / BRC-53: Certificate Creation and Revelation, example IDENTITY-tier methods.
  • BRC-65: Transaction Labels and List Actions, example READ-tier history method.
  • BRC-98: P Protocols: Allowing Future Wallet Protocol Permission Schemes, the reserved protocol-identifier dispatch point; orthogonal axis, see Section 14.
  • BRC-111: P Labels: Allowing Future Wallet Action Label Permission Schemes, reserved label namespace; not claimed by this specification.
  • BRC-229: Wallet-Native Elliptic Curve Point Multiplication as a BRC-98 Module, worked example of a module specializing an existing method.
  • FIPS 180-4: Secure Hash Standard (SHA-256), the fingerprint hash.
  • RFC 8785: JSON Canonicalization Scheme (JCS), referenced for comparison; this BRC's preimage form agrees with JCS on the string/boolean-only preimage but is defined independently in Section 7.2.
  • RFC 2119: Key words for use in RFCs to indicate requirement levels.
  • BRC-145, Registry-Free Typed Content Anchor with On-Chain Code Provenance (merged 2026-08-02): its code-provenance seal is the running-code-fingerprint-as-ordinal mechanism this manifest's fingerprint is minted into.
Was this helpful?

Search Beersy

Search standards by number, title, author or topic