BRC-212 Wallet

Compact Wallet Feature Flags in getVersion

This proposal defines one BRC-205 attribute, brc212 , whose value is a bit mask of wallet feature flags written in the base64url digit alphabet, six flags per character. Each flag stands for one boolean capability advertisement, at one revision, defined by some other BRC. A wallet supporting all four capabilities advertised today returns example-1.2.3; brc21

Darren Kellenschwiler (d.kellenschwiler@bsvassociation.org)

How it connects

Relationships

Authoritative content mirror

Full upstream text

View source ↗
Rendered from wallet/0212.md at the indexed upstream commit

Abstract

This proposal defines one BRC-205 attribute, brc212, whose value is a bit mask of wallet feature flags written in the base64url digit alphabet, six flags per character. Each flag stands for one boolean capability advertisement, at one revision, defined by some other BRC. A wallet supporting all four capabilities advertised today returns example-1.2.3; brc212=P; (24 bytes) instead of example-1.2.3; brc207-color=4; brc207-color-attested=1; brc175-offline=2; brc177-nosend=2; (90 bytes).

The proposal also holds the flag registry: the table assigning each flag index to a feature. Future BRCs that need a yes/no capability advertisement claim the next free index in that table rather than minting a new attribute key.

Motivation

BRC-100 gives getVersion a [vendor]-[major].[minor].[patch] string of 7–30 bytes. BRC-205 added key=value attributes and raised the complete-string limit to 2048 bytes. Since then, four BRCs have each defined a capability attribute of the form brc<number>-<name>=<revision>:

Attribute Defined in Bytes, including ; separator
brc207-color=4 BRC-207 16
brc207-color-attested=1 BRC-207 25
brc175-offline=2 BRC-175 18
brc177-nosend=2 BRC-177 17

That pattern does not scale, for two reasons.

Deployed validators still enforce 30 bytes. BRC-205's own implementation notes point to SDK result validation and the binary wire transceiver that enforce the original 7–30-byte bound. Until every application and SDK in the field has been updated, a wallet that returns more than 30 bytes risks a thrown validation error in an application that would otherwise have worked. A wallet using wallet-brc100-1.0.0 (19 bytes) cannot add even brc207-color=4 without crossing the bound.

Each feature spends bytes and an attribute slot. BRC-205 caps the string at 24 attributes. At 16–25 bytes each, a few dozen features would exhaust the attribute count well before the byte limit, and every one of them would be outside the 30-byte bound.

A boolean capability needs one bit. Packing those bits into a single attribute lets a wallet advertise up to 42 features in 30 bytes with a 13-byte base, and up to 1536 features within BRC-205's limits.

Scope

The uppercase terms MUST, MUST NOT, SHOULD, SHOULD NOT and MAY have the meanings in RFC 2119 and RFC 8174. The Specification section is normative. Examples and rationale are informative.

This proposal defines one BRC-205 attribute, its value encoding, and a registry of flag indexes. It does not change the getVersion({}) call, its { version: string } result shape, its call code, or any transport. It does not change BRC-205's outer grammar, percent-encoding, bounds, duplicate-key or unknown-key rules.

Only boolean capability advertisements are in scope. Attributes that carry a value, such as account-label from BRC-205 or brc185-icon from BRC-185, remain ordinary attributes.

Specification

1. The brc212 attribute

Property Definition
Key brc212
Meaning The set of registered features, from section 4, that the responding wallet advertises.
Presence Optional. Omission means no flags are supplied. It does not mean no features are supported.
Value 1–256 characters from the base64url digit alphabet, as in section 2.

The key is the bare BRC number with no suffix. BRC-205 section 6 permits a BRC to use its bare brc<number> key; this proposal relies on that permission.

The attribute obeys every generic rule in BRC-205: single occurrence, the 2048-byte complete-string bound, the 256-byte value bound, and the rule that any generic failure makes all attributes unavailable. Every character of the alphabet is in BRC-205's unreserved set, so a value is always written literally and never percent-encoded.

Example:

example-1.2.3; brc212=P;

2. Value encoding

The value is a little-endian sequence of base64url digits. Each digit carries six flags.

The alphabet is that of RFC 4648 section 5:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_

A has digit value 0 and _ has digit value 63.

Flag index i is set if and only if bit i mod 6 of the digit at character position floor(i / 6) is 1, where character positions count from 0 at the left and bit 0 is the least significant bit of the digit value. Character position 0 holds flags 0–5, position 1 holds flags 6–11, and so on.

This is not base64 decoding of bytes. There is no grouping into bytes, no padding, and no length rule beyond BRC-205's bounds. Any length from 1 to 256 characters is valid.

A sender:

  • MUST set exactly the flags for the features it advertises.
  • SHOULD omit trailing A digits. Since A carries no flags, omitting it does not change the set.
  • MUST omit the attribute when no flag is set, because BRC-205 values cannot be empty.
  • MAY clear flags to stay within a length budget, such as the 30-byte bound in section 5. A shorter value is a smaller set, not a truncated value.

A reader:

  • MUST treat the attribute as absent if any character is outside the alphabet. This includes +, /, =, . and ~. The other attributes in the string remain usable, as BRC-205 section 3 specifies for a known attribute that fails its own constraints.
  • MUST treat flag positions beyond the value's length as clear.
  • MUST accept trailing A digits. P, PA and PAAA denote the same set.
  • MUST ignore set flags whose index it does not recognize or which section 4 marks as unassigned.

Reference implementation, informative:

const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'

function encodeFlags (indexes) {
  const digits = []
  for (const i of indexes) {
    const pos = Math.floor(i / 6)
    while (digits.length <= pos) digits.push(0)
    digits[pos] |= 1 << (i % 6)
  }
  while (digits.length > 0 && digits[digits.length - 1] === 0) digits.pop()
  return digits.map(d => ALPHABET[d]).join('') // '' means omit the attribute
}

function decodeFlags (value) {
  if (value.length < 1 || value.length > 256) return null
  const flags = new Set()
  for (let pos = 0; pos < value.length; pos++) {
    const d = ALPHABET.indexOf(value[pos])
    if (d < 0) return null // attribute absent
    for (let bit = 0; bit < 6; bit++) {
      if ((d >> bit) & 1) flags.add(pos * 6 + bit)
    }
  }
  return flags
}

3. Meaning of a flag

Each registered flag stands for exactly one capability advertisement at exactly one revision. A set flag makes the same assertion, with the same semantics, as the equivalent attribute listed in section 4. For example, flag 0 asserts what brc207-color=4 asserts under BRC-207.

A flag never changes meaning. When the defining BRC publishes a new revision of the capability, the new revision gets a new flag index. A wallet MAY set both the old and new flags if it meets both revisions' requirements.

A consumer MUST treat a feature as advertised if its flag is set or its equivalent attribute is present with the listed value. Either is sufficient, and a consumer need not parse both. A wallet SHOULD NOT emit both for the same feature. If it does, they make the same assertion and neither overrides the other.

Every rule of the defining BRC continues to apply. In particular, and consistent with BRC-205 section 6 and BRC-185 section 1:

  • A flag is optional descriptive metadata. It is not BRC-100 method negotiation, an authentication result, a permission grant or spending authority. All 28 BRC-100 methods remain mandatory.
  • A clear flag, or an absent attribute, MUST NOT be treated as proof of non-support. A wallet MAY omit flags for privacy, while locked, or to meet a length budget.
  • A set flag MUST NOT be treated as proof that any particular call will be authorized or succeed. Every call still checks actual support and permissions.
  • A flag MUST NOT encode user-specific state such as balances, holdings, grants or history.

4. Flag registry

This table is the registry. BRC-205 section 6 introduces no registry service, and this proposal adds none: the table is amended through the repository's ordinary pull-request process.

Flag Defining BRC Feature Equivalent attribute
0 BRC-207 1Color wallet module, capability revision 4 brc207-color=4
1 BRC-207 Default attested admission profile, all four trust modes, revision 1 brc207-color-attested=1
2 BRC-175 Offline bundle acceptance, revision 2 brc175-offline=2
3 BRC-177 noSend expiry, batches and never, revision 2 brc177-nosend=2
4 and above Unassigned

Earlier revisions of these capabilities have no flag, because their defining BRCs state that those values do not establish support for the current revision.

Allocation rules:

  1. A BRC that defines a boolean capability advertisement SHOULD claim a flag rather than define a new attribute key. It MAY do both, listing the attribute in the "Equivalent attribute" column. A new capability with no legacy attribute leaves that column empty.
  2. A new entry MUST use the lowest unassigned flag index. Before assigning, the author MUST check this table on the current master branch and the flags claimed by open pull requests, and MUST recheck immediately before merging. If another proposal has claimed the index, the author renumbers to the next lowest free index.
  3. A flag, once merged, is permanent. It is never reassigned, even if its feature is withdrawn or deprecated; such a flag stays in the table marked withdrawn.
  4. Each entry's defining BRC MUST state what the flag asserts, at what revision, and its privacy implications, as BRC-205 section 6 requires of attributes.
  5. Low indexes fit in the shortest strings. Allocation is first-come and implies nothing about importance.

5. Fitting within 30 bytes

A wallet that wants to stay within BRC-100's original bound, so that applications with un-updated validators continue to work, SHOULD keep the complete string at or below 30 bytes. The attribute costs ; brc212= plus the final ;, which is 10 bytes. The rest is available for digits:

Base Base bytes Digits available Flags available
a-1.0.0 7 13 78
example-1.2.3 13 7 42
wallet-brc100-1.0.0 19 1 6

A wallet that cannot fit every flag it supports MAY choose which to send, clearing the others under section 2, or MAY exceed 30 bytes under BRC-205. This proposal does not require either choice.

This only addresses validators that check length. A validator that also rejects anything after the base, such as a strict [vendor]-[major].[minor].[patch] pattern, rejects every BRC-205 attribute, and needs updating as BRC-205 already describes.

6. Privacy

A capability set can help fingerprint a wallet, and a mask makes advertising many capabilities cheap. Wallets SHOULD set only the flags a requesting application plausibly needs, and MAY return different subsets to different originators. Consumers MUST NOT send the version string or decoded flag set to analytics, unrelated servers or logs by default, and MUST NOT use them for cross-application correlation, as BRC-205 section 5 already requires of the complete string.

Consumers SHOULD refresh the flags on the selected connection before starting a workflow that depends on them, and MUST discard cached flags on provider change, account change or disconnection.

Examples and conformance cases

The inputs are complete version values, without JSON quoting.

Input Required result
example-1.2.3 No flags supplied.
example-1.2.3; brc212=B; Flag 0: brc207-color=4.
example-1.2.3; brc212=P; Flags 0–3. The string is 24 bytes.
wallet-brc100-1.0.0; brc212=P; Flags 0–3. The string is exactly 30 bytes.
example-1.2.3; brc212=F; Flags 0 and 2.
example-1.2.3; brc212=I; Flag 3: brc177-nosend=2.
example-1.2.3; brc212=AB; Flag 6, currently unassigned: ignored. No registered flags set.
example-1.2.3; brc212=7B; Flags 0, 1, 3, 4, 5 and 6: registered flags 0, 1 and 3; flags 4–6 ignored as unassigned.
example-1.2.3; brc212=7BAA; Same set as 7B. Trailing A digits are accepted.
example-1.2.3; brc212=AAAAAAg; Flag 41 only: ignored as unassigned.
example-1.2.3; brc212=B; brc207-color=4; Flag 0 and its equivalent attribute: one assertion, made twice.
example-1.2.3; brc212=e.; account-label=Metanet%20Account; . is outside the alphabet: brc212 is absent. The label remains usable.
example-1.2.3; brc212=P%2B; Decodes to P+, and + is outside the alphabet: brc212 is absent.
example-1.2.3; brc212=%50; Percent-encoded unreserved byte: BRC-205 generic failure; all attributes unavailable.
example-1.2.3; brc212=; Empty value: BRC-205 generic failure; all attributes unavailable.
example-1.2.3; brc212=B; brc212=C; Duplicate key: BRC-205 generic failure; all attributes unavailable.

A value of exactly 256 characters is valid. A value of 257 characters fails BRC-205's value bound and makes all attributes unavailable.

Rationale

Why six flags per character instead of base64 of bytes. Base64 of bytes has padding, length rules and unused trailing bits, all of which need validation, and standard base64's +, / and = are not in BRC-205's unreserved set. Treating each base64url digit as six independent flags avoids all of that. Every length is valid, and one spare character still carries six flags, which matters when only one or two bytes remain under 30.

Why little-endian. New flags are appended at higher indexes. With flag 0 in the first character, adding a flag never moves an existing one, and a wallet that supports only low-index features sends a short value.

Why one flag per revision. A bit has no room for a revision number. Assigning a new flag to each revision keeps every flag's meaning fixed for all time, so a consumer that knows flag 0 never misreads a later revision as an earlier one.

Why the bare key brc212. Every byte of the key is a byte not available for flags under the 30-byte bound. BRC-205's collision rule is that a key begins with its owning BRC's number; the bare number satisfies that rule and cannot collide with any other BRC's key.

Compatibility

Consumers that do not implement this proposal ignore brc212 as an unknown BRC-205 attribute and continue to read any equivalent attributes. Wallets can therefore move from per-feature attributes to flags gradually, or send both during a transition.

This proposal amends BRC-205 section 6 to permit a bare brc<number> key, owned by that BRC. No other BRC-205 rule changes. It does not amend BRC-175, BRC-177 or BRC-207; their attributes keep their meaning, and section 4 records their flag equivalents.

References