BRC-175 Payments

1Color Offline Peer-to-Peer Payments

This BRC specifies how one wallet pays 1Color (BRC-162) tokens to another wallet when either party, or both, may be offline. The payer hands the payee one payment bundle : a signed transaction, the data the payee needs to recognize its output, and enough evidence to show that every token the payer spends traces back to outputs an overlay has already admitted

Deggen (d.kellenschwiler@bsvassociation.org)

How it connects

Relationships

Authoritative content mirror

Full upstream text

View source ↗
Rendered from payments/0175.md at the indexed upstream commit

Abstract

This BRC specifies how one wallet pays 1Color (BRC-162) tokens to another wallet when either party, or both, may be offline. The payer hands the payee one payment bundle: a signed transaction, the data the payee needs to recognize its output, and enough evidence to show that every token the payer spends traces back to outputs an overlay has already admitted. The payee checks the bundle without any network access, credits the payment or holds it until missing evidence arrives, and settles it with the overlay later.

The evidence is an admission attestation: an overlay operator's signature over the outputs it admitted. The payee walks back through the transaction graph until every token input ends at an attested output, or at an output the payee has verified itself. It trusts an attestation only when the signer's identity key is one of its own trust anchors.

Offline acceptance carries counterparty risk: the payer could spend the same input twice before the payee settles. This BRC states that risk and, for regulated assets, how it is contained. The overlay records the owner of every admitted output, so a double-spent outpoint identifies the identity key that spent it, even when the payment used sender blinding. Under that asset's identity registration, the key identifies a person.

The bundle has one binary encoding. Bindings are given for a nearby session (QR pairing, sealed frame, air-gap QR) and for a BRC-33 message box.

Motivation

A token payment today is accepted when its overlay admits it. That needs a network connection at the moment of payment. Many real payments happen without one: in a shop with poor signal, between two phones on a train, at a market stall. A stablecoin that only works online is a weaker substitute for cash.

The rules needed to accept a payment offline safely are subtle. The payee must know which ancestors to check, what signature stands in for the overlay, whose signature to believe, what to do when settlement later fails, and why it is reasonable to take the risk at all. Until now these rules existed only in one wallet's source code. This document states them so that other wallets, overlays and issuers can interoperate.

BRC-207 requires a current overlay answer before a wallet spends or accepts a token. This BRC defines the deferred admission policy that relaxes that requirement under the conditions below. A wallet chooses whether to offer it. It applies to any 1Color asset and needs no declaration by the issuer.

Relationship to other documents

Concern Document
Token encoding, asset ID, supply rules BRC-162
Wallet module, key namespace, baskets, overlay topics BRC-207
Key derivation and invoice numbers BRC-42, BRC-43
Specific key linkage disclosure BRC-69, BRC-72
Transaction envelope BRC-62, BRC-233
Token lineage proofs (walk to deploy) BRC-176
Overlay submission BRC-22
Trust anchors BRC-189
Expiry of unsent actions BRC-177
Air-gap QR transport BRC-141
Message box relay, handles BRC-33, BRC-169
Ephemeral sender keys BRC-228

BRC-176 proves lineage all the way back to a deploy. This BRC stops the walk at the first attested output. That keeps a bundle small enough for a QR code, at the cost of trusting the attesting overlay for everything older.

MUST, MUST NOT, SHOULD and MAY express requirements for conforming implementations.

Roles

Role Does Is trusted for
Payer Builds, signs and hands over the bundle Nothing. Every claim is checked.
Payee Checks the bundle, credits it, settles it Its own trust choices
Overlay operator Admits transactions to tm_color_<assetId> and signs attestations Having admitted the attested outputs, if the payee trusts its key
Identity registrar For regulated assets, binds identity keys to real-world persons Identity of a key's owner, outside this protocol

One organization MAY hold several roles. An issuer commonly runs the overlay and the registrar.

Admission attestation

An attestation is an overlay operator's statement: "the topic tm_color_<assetId> admitted these outputs of this transaction."

Message

1color-admit:v1:<topic>:<txid>:<outputs>
  • topic is tm_color_<assetId> per BRC-207, using the canonical asset ID <64 lowercase hex>_<decimal vout>.
  • txid is 64 lowercase hex characters, in display order.
  • outputs is the admitted output indices as decimal integers without leading zeroes, unique, ascending and joined by ,. It MUST NOT be empty.

The topic is part of the message so an attestation for one asset cannot be replayed as evidence for another.

Signature

The signer signs with the BRC-42 child of its identity key for protocol [2, "color admission attestation"], key ID "1" and counterparty "anyone". The invoice is 2-color admission attestation-1. This is what BRC-100 createSignature({data: UTF-8(message), protocolID, keyID: "1", counterparty: "anyone"}) does: it computes e = SHA-256(UTF-8(message)) once and signs e with ECDSA over secp256k1. The dedicated protocol keeps attestation signatures apart from every other use of the signer's keys.

A verifier derives the signer's child publicly, using the anyone private key 1 and the same invoice, or calls verifySignature from an anyone wallet with the same tuple and counterparty set to the signer's identity key. It MUST NOT verify against the bare identity key. A signature made with the bare identity key is not a valid attestation.

Signers SHOULD use RFC 6979 deterministic nonces, so re-signing the same message gives the same bytes and settlement retries are idempotent. The signature is DER encoded. Verifiers MUST accept any valid S value and MUST NOT require low-S.

An overlay MUST sign only outputs its topic has admitted. It returns the attestation for each topic in its BRC-22 submission response as admissionSignature (lowercase DER hex) and admissionIdentityKey (the signer's identity key, compressed, 66 hex), next to that topic's outputsToAdmit.

Meaning and limits

An attestation lists the complete set of outputs of that transaction the topic admitted, as the signer knew it at signing time. An overlay MUST NOT sign a partial set. So an output missing from an attestation by a signer the payee accepts was not admitted by that signer, and the walk-back treats it as refused. Local validation cannot see an overlay's policy refusals, such as a frozen input, a sanctioned identity or a missing KYC registration; the complete set is how they reach an offline payee.

An attestation proves admission at signing time. It does not prove the output is still unspent. It is a probabilistic reduction of risk, not a guarantee, so it carries no timestamp and no expiry. That gap is the counterparty risk discussed below.

Trust

A verifier MUST accept an attestation only when the signer key is one of its trust anchors under BRC-189, and the signature verifies against that anchor's derived child. A key named inside a bundle is a hint for lookup. It is never a reason to trust. Nothing in the asset's deploy names a signer.

Wallet builds normally ship with the issuers' attesting keys already trusted. A user MAY add or remove a key at any time, for example one published on an asset's website. Removing a key affects only future checks. Rotation and revocation of an issuer's attesting key belong to the issuer's authority chain and are outside this BRC.

Payment bundle

Encoding

All integers marked VarInt use Bitcoin CompactSize encoding in its minimal form. A non-minimal VarInt MUST fail. str is a VarInt byte length followed by strict UTF-8. bytes is a VarInt length followed by raw bytes. A txid field is 32 bytes in display order: the bytes of the hex string as written. A decoder MUST reject trailing bytes.

u8      version            = 0x01
33B     senderKey
VarInt  outputIndex
str     derivationPrefix
str     derivationSuffix
str     assetId
VarInt  nSubmissions
        nSubmissions × { 32B txid ; bytes offChainValues }
VarInt  nAttestations
        nAttestations × { 32B txid ; VarInt nOutputs ; nOutputs × VarInt output ;
                          bytes signature ; 33B signerKey }
str     note
bytes   transaction

Why each field is sent

Field Why the payee needs it
version Lets a decoder refuse a format it does not implement.
senderKey The counterparty for deriving the payee's output key under BRC-42. It is the blinded key A′ for a blinded payment, or the payer's identity key A otherwise. The payee treats both the same way.
outputIndex Which output of the tip pays the payee. Output order is randomized, so the payee cannot guess it.
derivationPrefix, derivationSuffix The remittance strings that select the payee's key in the BRC-207 namespace. On the nearby rail the payee chose them, which binds the bundle to one session.
assetId Which asset is being paid. It fixes the topic, the attestation message and the per-asset basket.
submissions For the tip and every unattested token ancestor, the exact off-chain values the overlay needs to admit that transaction. The payee must be able to settle without the payer, so it carries what the payer would have sent. For an asset without ownership disclosure the value MAY be empty.
attestations Where the walk-back stops. Each entry lets the payee confirm, offline, that the overlay already admitted the named outputs, and that it admitted no others.
note Optional message from payer to payee. Display only. Empty means absent.
transaction A BRC-233 Subject BEEF whose subject is the tip. It carries the bodies the payee needs to validate scripts, Bitcoin evidence and conservation, to read the asset's deploy, and to settle. See the structural rules.

There is deliberately no amount field: the amount is read from the payee's output script, so the bundle cannot claim a different one. There is also no overlay URL and no trusted key: the payee uses its own configuration for both, so a payer cannot steer it to a server or signer of the payer's choosing.

Structural rules

  • senderKey and each signerKey MUST be valid compressed secp256k1 points. signerKey is the signer's identity key, not its derived child.
  • assetId MUST be canonical as defined in BRC-162.
  • Submission txids MUST be unique. Attestations MUST be unique by (txid, signerKey); several signers MAY attest one transaction. No txid appears in both the submissions and the attestations.
  • Attestation outputs MUST be ascending and unique, and the list MUST NOT be empty.
  • derivationPrefix and derivationSuffix MUST satisfy BRC-207's remittance rules: nonempty strict UTF-8 without whitespace. The resulting protocol name MUST fit BRC-43's 280-character bound.
  • transaction MUST parse as a BRC-233 Subject BEEF whose subject is the tip. Like a BRC-176 packet, it MUST NOT be validated, stripped or round-tripped as BRC-95 Atomic BEEF: BRC-95 handling may drop the ancestor bodies the walk-back needs.
  • transaction MUST contain the full body of the tip, of every transaction the walk-back expands, and of the source of every input of those transactions, including attested parents and fee inputs, so every input script can be validated. It MUST contain Bitcoin evidence (BRC-62 merkle paths or unmined ancestors) for every input.
  • transaction MUST contain the deploy transaction body, unless the payee already holds it. The deploy is authenticated by its bytes hashing to the txid in assetId. The payee reads the deploy's BRC-162 payload for display fields and for any required profile, such as issuer controls.

A bundle that breaks any rule fails as decode_failed.

Walk-back

The walk-back decides whether every token in the payment traces to verified outputs, and which transactions the payee must settle. It runs without network access.

function walk(tx):                       // tx starts as the tip
    if tx.txid is on the stack: fail "shape"         // malformed bag
    for each input i of tx:
        parentId, vout = i.sourceTxid, i.sourceOutputIndex
        parent = body of parentId in transaction
        if parent is absent: fail "uncovered_ancestor"          // rule B
        if parent.outputs[vout] is not a token of assetId: continue   // rule K
        if anchored(parentId, vout): continue        // bottom of this branch
        if refused(parentId, vout): fail "uncovered_ancestor"   // rule A
        walk(parent)
        mustSubmit.add(parentId)
    return

anchored(txid, vout) =
    attested(txid, vout)
    or (the asset declares no issuer-controls profile
        and the payee has itself verified that output to the deploy)

attested(txid, vout) =
    an attestation for txid exists whose signerKey is a trust anchor,
    whose signature verifies for topic tm_color_<assetId>, and whose outputs include vout

refused(txid, vout) =
    an attestation for txid by a trust anchor verifies, and its outputs omit vout

The result is mustSubmit plus the tip, ordered so every parent comes before its children. A transaction reached through several inputs is walked once.

Rules A, B and K, and why they exist:

  • A. Admitted, but not this output. An attestation lists the complete admitted set, so an omitted output was refused by that overlay, for example a phantom coin from an over-output transaction, or an output refused on policy grounds. The walk MUST NOT search further back to justify it.
  • B. Missing body. Every parent needs its body, even an attested one, because the child's input script must be validated against the parent's output. A mined parent is not treated as admitted: mined is not admitted.
  • K. Non-token inputs. Inputs that do not carry this asset, such as fee funding, are neither walked nor counted for token lineage. Their scripts and Bitcoin evidence are still validated below.

Self-verified anchors. A payee MAY end a branch at an output it has itself verified back to the deploy, for example under a complete BRC-176 lineage check. This lets wallets that do not rely on overlays share assets with wallets that do. It applies only to assets whose deploy declares no required processing profile that depends on overlay state, such as issuer controls. For an asset under issuer controls only an attestation can anchor, because only the overlay sees freezes and registration status.

Every txid in mustSubmit, and the tip, MUST have a submission entry. Otherwise the bundle fails as not_covered. A wallet MAY cap walk depth or bundle size by policy; exceeding a cap fails as limit_exceeded.

Payee acceptance

Acceptance has three outcomes:

Outcome Meaning What the payee keeps
credited Every check passed. The bundle, and the output in its balance with color_offline
held Every check the payee can make passed, but some evidence is not available yet The bundle, durably, as pending. Nothing is credited or spendable.
declined A check failed Nothing

A payment is held, not declined, when the only failures are temporary:

  • a merkle path names a block above the payee's verified header tip;
  • a source transaction body is missing but the payee expects to obtain it later.

A held payment is re-checked on a schedule and whenever evidence arrives. It then becomes credited or declined. A payee MUST cap held payments per sender and in total. When a cap is reached, a new payment is declined as limit_exceeded, never silently dropped.

The payee MUST run these checks in order. A hard failure stops the checks and declines the payment with the ack code shown. A temporary condition is recorded and checking continues. If no hard failure occurs but a temporary condition remains, the payment is held.

  1. Decode. The bundle decodes under the structural rules. Else decode_failed.
  2. Output. outputIndex names a tip output that is a BRC-162 value output of assetId with a P2PKH remainder. Else invalid_tx.
  3. Ownership. The payee computes its key with BRC-100 getPublicKey. The arguments are protocolID [2, "p color <assetTag> <prefixTag> <suffixTag>"] and keyID "color" per BRC-207, with counterparty: senderKey and forSelf: true. The HASH160 of that key MUST equal the output's public key hash. Else session_mismatch. This proves the payee alone can spend the output.
  4. Scripts. Every input of the tip and of each mustSubmit transaction MUST pass consensus script validation against its source output. The tip MUST be consensus-final. Else invalid_tx.
  5. Bitcoin evidence. Each merkle path is checked against the payee's own header chain, never against headers carried in the bundle. Each check gives one of three results:
    • valid: the path proves inclusion in a block the payee holds;
    • invalid: the path is malformed, or contradicts a header the payee holds. The bundle fails as invalid_tx;
    • unknown: the path names a block above the payee's verified tip. The payment is held.
  6. Token rules. The tip and each mustSubmit transaction MUST satisfy BRC-162 conservation and authority rules for assetId, with all inputs counted from walked or anchored sources. Else invalid_tx.
  7. Walk-back. It succeeds. Else not_covered.
  8. Session or request. On the nearby rail the prefix, suffix, asset and any requested amount equal the session's. Else session_mismatch. A session already paid fails as already_paid, here or atomically at step 10.
  9. Policy. The amount and depth fit the payee's deferred-admission limits, and held caps are not exceeded. Else limit_exceeded.
  10. Save. The payee durably stores the bundle and marks the session spent, in one atomic step. For a credited payment, the same commit records the credit, or a pending application of it that can be completed idempotently. Else save_failed.
  11. Ack. The payee sends its ack, then exposes the output with color_offline, or shows the payment as held. A crash before or after the ack cannot lose or duplicate a credit. No ack asserts Bitcoin confirmation or that the inputs are still unspent.

Settlement

Settlement turns an offline credit into an admitted one.

Who submits. The payee submits. Once the payee has acked the payment as credited, the payer MAY also submit. After a held ack the payer waits for the final outcome and does not submit. Submission is idempotent, so a duplicate does no harm. If the payee declines, neither party submits the transaction. The payer either resends a corrected payment or cancels. A payee that declined MUST NOT submit what it declined. With no ack channel, as on the QR fallback, the payer MUST NOT submit until it learns of acceptance.

Online payments. A payee that is online when it receives a bundle settles immediately, using the same steps. This also works when payer and payee trust different overlays, because the payee submits to its own.

Steps:

  1. Submit each mustSubmit transaction, then the tip, in order. Each goes to the settling party's configured provider for tm_color_<assetId> with its submission entry's off-chain values.
  2. Accept an admission only when it carries an attestation from a trust anchor covering the expected outputs. An unsigned or unverifiable answer is a retryable failure, never an admission.
  3. Store each attestation. It becomes evidence for later bundles.
  4. Broadcast only after the overlay has admitted the transaction. Broadcasting the tip's BEEF also broadcasts its unmined ancestors. If a transaction were mined before admission and the overlay then refused it, its token inputs would be destroyed on-chain.
  5. When the tip is admitted, remove the color_offline tag.

Outcomes:

Outcome Payee action
Retryable (unavailable, rate-limited, unverifiable answer) Keep state and retry with backoff.
Final refusal of the tip or an ancestor Mark it refused and its descendants orphaned. Remove the credit and correct the balance. Keep the bundle.
Conflict: an input already spent by another transaction As for a final refusal. Also record the competing txid the overlay reports. Keep the bundle as evidence.

A wallet SHOULD tell the user when settlement has not finished within its stale threshold. A threshold of three days is suggested.

Payer exposure. Once a payer has released signed bytes, it MUST keep the spent inputs reserved and the exposure recorded. That holds after a negative ack too, because the payee still holds bytes that could be submitted. The reservation ends only when the tip is admitted, a conflicting spend is established, or the payer cancels. To cancel, the payer spends the inputs back to itself once online; it MUST tell the user that the cancel races any copy of the original. A wallet SHOULD offer that cancel explicitly after a decline.

Deferred admission policy

This section defines the BRC-207 wallet policy that makes offline payment possible. A wallet MAY offer it. When enabled, it applies to every 1Color asset. No declaration by the issuer is needed or possible: the risk falls on the holder who accepts it, and issuer supply is never affected. An asset that declares an issuer-controls profile still has that profile's rules applied at settlement.

  • Accepting. An output accepted under this BRC is held with the BRC-207 state tag color_state_available and the additional wallet-authored tag color_offline. It counts toward the asset's token balance. Applications cannot set or remove color_offline. This extends BRC-207, which otherwise reserves color_state_available for admitted outputs.
  • Held payments. A held payment contributes nothing to the balance and cannot be spent. Wallets SHOULD show it as an incoming payment pending verification.
  • Spending offline. A color_offline output MAY be spent in a new bundle. The payer forwards the evidence: its own bundle's submission entries and attestations for every still-unattested ancestor. A payment whose Bitcoin evidence is unknown is held, not credited, so unverifiable evidence never travels onward.
  • Spending online. Before an online spend of a color_offline output releases a signature, the wallet MUST settle the transaction that created the output and its unattested ancestors. If any is refused, the spend fails before broadcast.
  • Limits. The wallet sets per-asset amount caps, maximum walk depth, maximum age, held-payment caps and accepted counterparties. The user consents to the policy when enabling it, and that consent authorizes later settlement.
  • Lifecycle promises. A wallet that expires unsent actions, as in BRC-177, MUST NOT expire or reclaim the inputs of a released bundle. A future portable statement of that promise, if specified, SHOULD be signed by the key that controls the spent input, not a wallet identity, MUST NOT be forwarded beyond the direct payee, and is optional for the payee to require. Otherwise it would reveal every earlier payer to every later payee.
  • Capability. A wallet that accepts bundles MAY advertise BRC-205 attribute brc175-offline=2 for this revision. Value 1 does not establish support for derived-key attestations or held outcomes. A payer that cannot tell whether the payee accepts bundles SHOULD settle online first.

Bindings

Nearby session

The payee starts a payment by showing a session QR:

bsvpay1:<base64url, unpadded, of UTF-8 JSON>
Member Type Why
v integer 2 Session format version.
s base64url of 16 random bytes Session ID. It names the radio rendezvous and is burned after one payment, so a bundle cannot be replayed.
k base64url of 32 random bytes Pre-shared key that seals the bundle, so only this payee can read it.
i compressed key, 66 hex Payee identity key B. The payer derives the payee's output key toward it.
p, x strings Payee-chosen derivation prefix and suffix. They make the output key fresh and tie the bundle to this session.
a decimal string, optional Requested amount in token base units, up to uint64. Absent means the payer chooses.
t object i: asset ID. Optional k: array of attester keys the payee trusts, as a hint so the payer can choose evidence the payee will accept. Optional n, s, d: display name, ticker, decimals.
c integer, optional Transport capability bits, so the payer picks a radio both phones support.

The payer replies with one sealed frame:

0x01 ‖ IV (32 bytes) ‖ AES-256-GCM ciphertext of the bundle ‖ tag (16 bytes)

The key is the session PSK. A wrong key or any tampering fails authentication, which maps to decode_failed. The frame travels by whichever radio both sides support. Radio link details are implementation-specific. If no radio works, the payer shows the sealed frame as a BRC-141 animated QR; 1024-byte blocks are suggested. A sealed frame MUST NOT exceed 65,536 bytes.

The payee answers over the radio with a UTF-8 JSON ack of at most 4,096 bytes:

{ "ok": true }
{ "ok": false, "error": "not_covered" }

A held payment is acked as { "ok": true, "held": true }: the payee has stored it but credited nothing, so a merchant SHOULD NOT treat it as paid.

error is one of decode_failed, invalid_tx, session_mismatch, already_paid, not_covered, limit_exceeded and save_failed. A negative ack means the payee kept nothing. It does not free the payer's inputs; see payer exposure under Settlement. The QR fallback has no return path. The payer then treats the payment as delivered but unconfirmed: it keeps its reservation and does not submit until it learns of acceptance.

Message box

For payments to a remote payee, the payer sends the bundle through a BRC-33 message box named color-payments. It may first resolve a BRC-169 handle to the payee's identity key. The message body is:

{ "v": 1, "bundle": "<standard base64 of the bundle>" }

With no session, the payer chooses the derivation prefix and suffix. They MUST be fresh random values. The payee runs the acceptance checks, skipping step 8. It acknowledges the message only after step 10, including for a held payment, or after a decline. The payee SHOULD send its outcome back to the payer's color-payments box as { "v": 1, "tip": "<txid>", "ok": true }, with "held": true, or with "ok": false and an error code. When a held payment later resolves, it sends the final outcome the same way.

Sender blinding

A payer MAY hide its identity from the payee. It picks a fresh random scalar r and sends senderKey = A′ = A + r·G. It derives the payee's key from the shared secret S′ = S + r·B, where S is its BRC-42 shared secret with B. Because b·A′ = S′, the payee derives the same key with an unchanged getPublicKey call and counterparty: A′. The payee cannot tell a blinded payment from an unblinded one, or from a BRC-228 ephemeral key. Unblinded payment, with senderKey = A, is equally valid.

The payer keeps r privately, against the payment's outpoint. Revealing r later proves that the payer built the payment: anyone can check A′ − r·G = A. r never appears in a bundle, in an overlay submission or in BRC-100 metadata.

What each party learns:

Party Payer identity Payee identity
Payee A′ only itself
Overlay of a regulated asset A B
Public chain neither neither

Blinding gives privacy from counterparties and the public. It deliberately gives none from a regulated overlay.

Counterparty risk and double-spend attribution

The risk

An attestation shows that an output was admitted, not that it is still unspent. Until the payee settles, the payer can spend the same input to someone else. Whoever settles first wins; the other payee's credit is removed. Deferred admission puts that risk on the payee who accepted it. Issuer supply is never affected, because the overlay admits only one spend of each output.

The payee's limits bound the exposure. For regulated assets, attribution makes cheating traceable and therefore deterrable.

Regulated assets

A regulated asset is one whose overlay admits an output only when the submission discloses the output's owner and that owner is registered with the asset's identity registrar. Those admission controls belong to an optional issuer-controls profile declared in the asset's BRC-162 req map. This BRC does not define that profile; it states what attribution needs from it.

  • Ownership disclosure. For each token output, the submission carries a BRC-69 specific linkage, encrypted to the overlay key under BRC-72. The linkage lets the overlay recompute the output key as owner + k·G and match it to the script. For a blinded output, the payer discloses the offset k it derived from S′.
  • Owner record. The overlay MUST keep the owner identity key of every admitted output. It MUST keep that record after the output is spent, and record the txid of the spend it admitted.
  • Conflict record. The overlay SHOULD keep the bytes of any refused submission that tried to spend an already spent output.

Only the owner can spend an output: its private key is b + k, and the payer knows k but not b. So the identity key recorded as the owner of a double-spent outpoint is the identity that signed both spends, unless that key was stolen or shared.

Reporting a double spend

  1. The victim's wallet reports the double-spent outpoint, the bundle it holds and the competing txid from settlement. The bundle contains the second spend, signed by the offender's key.
  2. The overlay operator looks up the outpoint's owner record. That gives the owner identity key and the transaction the overlay admitted.
  3. The registrar maps the identity key to the person who registered it.

The outpoint is enough to start the lookup. The victim's bundle is the proof. Blinding does not hinder either step, because the overlay saw the real keys. Access to owner records, and the legal process for disclosure, are governed by the operator's and registrar's terms. Those terms are outside this BRC.

For assets without ownership disclosure, attribution stops at the overlay's view of the transactions. There is no person behind a key.

Security considerations

  • PSK exposure. Anyone who photographs the session QR can open the sealed frame. They learn the payment but cannot redirect it, because the output pays the payee's key. Payees SHOULD show session codes only briefly.
  • Replay. A session is burned when its payment is saved. A message-box bundle is unique by tip txid, and saving it twice MUST be idempotent.
  • Attester compromise. A stolen attester key can attest outputs that were never admitted, and wallets trusting it would accept them. Settlement would then fail, and those payees lose the credited value. Wallets SHOULD let users remove a trust anchor quickly, and overlays SHOULD rotate keys under BRC-189's rules for trust changes.
  • Eviction. If an admitted transaction is later evicted, for example because it never confirmed, its outputs are no longer admitted. A settled payment can then be reversed. Wallets SHOULD show an evicted credit as reversed, not silently hide it.
  • Stale trust. Removing a trust anchor does not revoke attestations already relied on. Wallets SHOULD re-check pending color_offline outputs against current anchors.
  • Early broadcast. Anyone holding a signed tip can broadcast it before the overlay admits it, including someone who photographed the session QR and opened the sealed frame. If the overlay then refuses it, for example because an input is frozen, the token units are destroyed on-chain. For assets under issuer controls the issuer's remedy is reissue under that profile. Payees SHOULD settle promptly to narrow the window.
  • Chained refusals. A final refusal orphans every descendant in a chain of offline payments. So one double spend can hurt every good-faith payee downstream, and per-wallet depth limits are the only bound on how many. Wallets SHOULD keep depth limits low for high-value assets.
  • Size. Decoders MUST bound allocation by the received length before reading counts. Walk depth and BEEF size MUST be bounded.

Test vectors

All keys below are test values. Never use them for real funds.

Blinded derivation

Item Value
payer private key a 0000000000000000000000000000000000000000000000000000000000000457
payee private key b 00000000000000000000000000000000000000000000000000000000000008ae
blinding factor r 0000000000000000000000000000000000000000000000000000000000000d05
A 0228b7f3a019749cce6fc677afa8fae72ec10e811ed4b04e1963143cef87654b75
B 0271b357df56cfc77291b75e5f550d72687c1bef84ff9eadb4946eb55516a89d6f
A′ = A + r·G 024d4dd4c2ad973e10065a593d0203dda4174292bb36e4e99fb2fbb1373a17874b
asset ID a1a1…a1_0 (a1 × 32, then _0)
prefix, suffix AQ==, Ag==
protocol name p color a1a1…a1v0 41513d3d 41673d3d
invoice 2-p color a1a1…a1v0 41513d3d 41673d3d-color
S′ = a·B + r·B 035a21815b3303fdba64ea2822232d22a91fbc2ad2e5eee4f18b3308d6b7350e8a
k = HMAC-SHA256(S′, invoice) d093c0c52c9228a51a2641a963aeb3b370070494a3ec750cd2c2c500ab4d458a
payee key P = B + k·G 02b2fd0a551e8e3e3eabcb97acc52b5b16f3a1ab18472cddbe0b54cbcd5d6c05e7
HASH160(P) 7cd27b9db3068175b6a8122e42034a298e547996

The payee's unchanged getPublicKey({protocolID, keyID: "color", counterparty: A′, forSelf: true}) returns P.

Attestation

Item Value
attester private key 00000000000000000000000000000000000000000000000000000000000015b3
attester identity key (signerKey) 02798b81951a6a8c7ef8beda09b06a23031d7efaf37b663265e0188ab378124046
invoice 2-color admission attestation-1
derived signing key (anyone derivation) 033520aaa77b6914019796e974d1737acc890c5283c0921ddcfc2d5c815db8fb90
message 1color-admit:v1:tm_color_a1a1…a1_0:c3c3…c3:0,2
e = SHA-256(message) 7774ec9347afe70b376d452746c9aa4ef7a11205a35dc92bf3866d8b32d0a9f0
signature (RFC 6979, DER) 304402207fc9b223eb7d3dc5817804320a490404b47f5247efbaa3089080d38c376dd713022003a71ceddfb40e36db48c11f50e202426e76b42c0aa47c886e980f4dba87ee1c

a1a1…a1 and c3c3…c3 stand for the byte repeated 32 times (64 hex characters).

Negative case. This signature over the same e, made with the bare attester identity key, MUST be rejected:

304502210084f2172ef0fdd4a01038af38f00b339133580eca3393e5989df9688bf9462e58022042a1b88ca1750fef4b7aa0466bcfebd2d9c6bb1f4333fadce2ef2504471ace67

Bundle encoding

This vector covers framing only. Its transaction is four placeholder bytes, not valid BEEF.

  • senderKey: A′ above.
  • outputIndex: 1.
  • prefix and suffix: as above.
  • assetId: as above.
  • One submission: txid b2 × 32, off-chain values {}.
  • One attestation: the one above, with signerKey the attester identity key.
  • Empty note.
  • transaction: 01010101.
01024d4dd4c2ad973e10065a593d0203dda4174292bb36e4e99fb2fbb1373a17874b0104
41513d3d0441673d3d426131613161316131613161316131613161316131613161316131
613161316131613161316131613161316131613161316131613161316131613161316131
61315f3001b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2
b2027b7d01c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3
c302000246304402207fc9b223eb7d3dc5817804320a490404b47f5247efbaa3089080d3
8c376dd713022003a71ceddfb40e36db48c11f50e202426e76b42c0aa47c886e980f4dba
87ee1c02798b81951a6a8c7ef8beda09b06a23031d7efaf37b663265e0188ab378124046
000401010101

Length: 294 bytes. The line breaks are for display only.

Implementations

A prior version of this protocol is implemented in the BSV wallet (bsv-wallet, packages/expo-wallet-toolbox/core/localpay and core/mandala) with the @bsv/mandala library and its Go overlay. That implementation predates the 1Color names and this bundle encoding, and is being migrated to this specification.

References