BRC-213 Wallet

Native UTXO-Chain Coins in a BRC-100 Wallet

This document lets a BRC-100 wallet hold the coins of another UTXO proof-of-work chain, such as Litecoin, natively. The coins stay on their own chain under a key derived from the wallet's root. Nothing is wrapped, minted, burned or held by a bridge, an issuer or a federation, and BSV carries no record of them. Receive. Each account has one coin key per chain

Brandon Cryderman / HandCash (brandongcryderman@gmail.com) Status: Draft (proposed number, not a BSVA assignment)

How it connects

Relationships

Authoritative content mirror

Full upstream text

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

Abstract

This document lets a BRC-100 wallet hold the coins of another UTXO proof-of-work chain, such as Litecoin, natively. The coins stay on their own chain under a key derived from the wallet's root. Nothing is wrapped, minted, burned or held by a bridge, an issuer or a federation, and BSV carries no record of them.

  1. Receive. Each account has one coin key per chain. The wallet records a coin only when the raw transaction hashes to its txid and the output pays the coin script. The amount comes from those bytes, never from an explorer.
  2. Prove. A received coin becomes spendable only when its block is buried D deep on the wallet's own header chain of that chain. For Litecoin, D is 6 confirmations. Explorers supply bytes and are never trusted for depth.
  3. Pay. A payment is signed and recorded before it is broadcast. Its inputs are then spent for good, and the wallet rebroadcasts it until it is in a block.
  4. Pay an identity. A payment can name the recipient's identity key instead of an address, but only when the recipient's wallet has signed a record saying it receives that chain. The coins go to a fresh BRC-42 child of the recipient's identity, and a sealed notice tells the recipient's wallet which one.
  5. Apps. Three methods, getCoinAddress, listCoins and sendCoins, take a chain argument. sendCoins is a payment in the same scope as a BSV createAction: one consent per call, the same per-app spending budget counted at fiat value, and the same device and network guards.

Four bindings are fixed (§1): Litecoin ("ltc"), Bitcoin Cash ("bch"), Bitcoin ("btc") and Dogecoin ("doge"). Any UTXO proof-of-work chain can be added once every field of its binding is fixed.

This document covers depositing: holding the coins themselves. Wrapping, so that BSV units stand for a deposited coin and move at BSV fees, is a separate, optional step with its own costs (§13). Nothing here wraps.

Motivation

Wallets that show a second chain's coins usually trust either a custodian (wrapped tokens, exchange balances) or an indexer for amounts and depth. Both put a third party between the user and their money.

A UTXO chain needs neither. Any wallet can hold the key, read raw transactions, and check burial against headers it validates itself. The parts that are hard to get right, and must be the same in every wallet, are:

  • what counts as received and what counts as spendable;
  • how a payment survives a crash between signing and broadcasting;
  • how an application asks for a payment without a new consent model.

This document defines the burial proof (§4.1), how a wallet checks one (§4.2) and how it keeps its own header chain of each chain (§4.3). Those checks are all that holding a coin directly needs. There is no reserve, issuer or token. A document that wraps coins as BSV units reuses them for its deposits (§13).

Relationship to other documents

  • BRC-42 / BRC-43 derive the coin key (§2).
  • BRC-208: each vault account has its own coin keys. Draft BRC-249 (account holding, #311) decides which install may sign for an account. It applies to coin payments exactly as it applies to BSV.
  • BRC-73: a spendingAuthorization grant, and any per-app automatic-payment budget a wallet keeps, covers sendCoins at its fiat value (§10).
  • BRC-219 governs the consent prompt for sendCoins.
  • BRC-67 is the same burial check for BSV, where the wallet already follows the chain.
  • BRC-33 (messagebox) and BRC-78 (sealed messages) carry the notice of a payment to an identity (§7).
  • Draft BRC-214 (wallet asset profiles, #338) registers each chain here as the profile coin:<chain>, the same id the receiving record names (§7.2).

Terms

  • Chain: a UTXO proof-of-work chain other than BSV, named by its chain id from the binding ("ltc").
  • Base unit: the chain's smallest amount (a litoshi for Litecoin). Every amount in this document is a decimal string of base units.
  • Coin key / coin script / coin address: the key of §2, the output script it locks, and that script's address in the chain's own encoding.
  • Coin: one unspent output paying the coin script that the wallet has recorded.
  • Received coin: a coin paid to the wallet by anyone.
  • Change coin: a coin that the wallet's own recorded payment pays back to itself.
  • Source: anything that returns chain bytes: an explorer, a peer or a relay. Sources are untrusted.
  • Depth D: the confirmations a received coin needs before it is spent, the block holding it included.

Specification

1. Chain binding

A binding fixes everything a wallet needs for one chain. A wallet MUST NOT support a chain until every field is fixed.

Field Litecoin ("ltc") Bitcoin Cash ("bch") Bitcoin ("btc") Dogecoin ("doge")
Header chain, proof of work scrypt(header, salt = header, N = 1024, r = 1, p = 1, 32 bytes), limit 2^236 − 1 SHA-256d, limit 2^224 − 1 SHA-256d, limit 2^224 − 1 AuxPoW: the header carries chain id 0x62; a merge-mined header's AuxPoW MUST pass Dogecoin Core's CAuxPow::check and its parent header's scrypt hash is the work; otherwise the header's own scrypt hash. Limit 2^236 − 1
Retarget 2016 blocks over 302400 s ASERT (aserti3-2d), anchor block 661647 2016 blocks over 1209600 s, first block of the window to the last DigiShield every block: span of the previous block, dampened to an eighth, clamped to [45, 90] s against 60 s
Depth D 6 6 3 20
Base unit, decimals litoshi, 8 satoshi, 8 satoshi, 8 koinu, 8
Coin script P2WPKH P2PKH P2WPKH P2PKH
Coin address Bech32, ltc, witness v0 CashAddr, prefix bitcoincash, type P2PKH Bech32, bc, witness v0 Base58Check, version 0x1e (D…)
Payment addresses accepted Mainnet P2PKH, P2SH, P2WPKH, P2WSH. Never MWEB (ltcmweb1…) CashAddr P2PKH and P2SH (20- and 32-byte). Never a legacy Base58 address Witness v0 and v1 (taproot), and 3… P2SH. Never a 1… P2PKH address: it is also a BSV address P2PKH (0x1e) and P2SH (0x16)
Signing BIP143, SIGHASH_ALL BIP143 with SIGHASH_ALL|FORKID (0x41) BIP143, SIGHASH_ALL The original signature hash, SIGHASH_ALL
Input sequence 0xfffffffd 0xffffffff 0xfffffffd (replaceable) 0xffffffff
Coin key protocol [2, "wltc reserve"] [2, "bch coin"] [2, "btc coin"] [2, "doge coin"]
Dust floor 1000 litoshi 546 satoshis 546 satoshis 1000000 koinu (0.01 DOGE)
Block interval (display only) 2.5 minutes 10 minutes 10 minutes 1 minute

Every binding hashes transactions (serialized without witness) and block headers (the 80 bytes) with double SHA-256; the proof-of-work hash is the row above. A proof-of-work hash, read as a 256-bit little-endian number, must be at most the target its bits encode, and that target at most the binding's limit.

2016-block retarget (Litecoin, Bitcoin). At a height h that is not a multiple of 2016, bits equal the previous header's bits. At h ≡ 0 (mod 2016), set span = time(h − 1) − time(start), clamped to [T / 4, 4T], then target = previous target × span / T, capped at the limit and encoded in compact form. T is the row's timespan. Bitcoin's window starts at h − 2016; Litecoin's at h − 2017, which closes Bitcoin's off-by-one. For Litecoin, when the previous target's bit length exceeds 235, the target is shifted right one bit before the multiplication and left one bit after it, as Litecoin Core does.

Litecoin MWEB. Only canonical outputs count. A MimbleWimble extension block output is not in the canonical transaction a burial proof hashes, so it can never be a coin, and the wallet never pays one.

Every binding signs with low-S ECDSA over secp256k1, uses key ID "1" and counterparty self for the coin key (§2), and credits no coinbase output.

D weighs each chain's work per block: Bitcoin's three blocks carry far more work than six of any other chain here, and Dogecoin's twenty one-minute blocks are merge-mined by Litecoin's hash power.

The block interval is used only to show an estimate. Nothing waits on a clock: spendability is a count on the wallet's own header chain.

2. Coin key

The coin key of an account is the BRC-42 child of that account's root key under the binding's protocol, key ID and counterparty self.

  1. One coin key per account per chain. The coin address is reused (see Security considerations, and Open questions on per-payment addresses). A payment to an identity (§7) never pays this key.
  2. The protocol is reserved for the wallet. A wallet MUST refuse any application request that would derive, reveal, sign or decrypt under it, including getPublicKey, createSignature, createHmac, encrypt, decrypt and both key-linkage methods. Coins move only through §6.
  3. Bindings SHOULD use protocol [2, "<chain id> coin"]. Litecoin keeps [2, "wltc reserve"] because coins are already held under that key. Changing the derivation would strand them.
  4. The key is a function of the account root, so any install that restores the account can find its coins again (§11).

3. Recording coins

A wallet reads the coin address's outputs from any source and records an output as a received coin only when all of these hold:

  1. The raw transaction (serialized without witness) hashes to the claimed txid.
  2. It is not a coinbase transaction, and its witness-stripped serialization is not exactly 64 bytes.
  3. The named output exists and its script is byte-equal to the coin script.

The coin's amount is that output's value, read from the bytes. A dishonest source can hide or delay a coin, but it can never invent one or change its amount.

A change coin is recorded when the wallet records its own payment (§6). It needs no source.

4. Spendable coins

A received coin is spendable when a burial proof of its transaction (§4.1) checks as buried (§4.2) on the wallet's own header chain (§4.3) at the binding's D. For Litecoin that is a block buried 6 deep on the wallet's own Litecoin header chain.

A change coin is spendable at once. Its parent's inputs are the wallet's own coins, so only the wallet could double-spend it. If the parent never confirms, the change never exists on the chain, and a payment spending it fails on the chain without losing value.

A wallet MUST NOT spend, offer, or count as spendable a received coin that is not buried. It derives each coin's state from its own chain:

State Condition Shown as
unconfirmed The transaction is known to a source and not yet in a block "Waiting for a block"
confirming In a block with c < D confirmations on the wallet's chain "c of D confirmations" (for Litecoin, "c of 6")
spendable Buried (§4) Spendable
unchecked The wallet's chain or every source could not be read Last count known, and when
missing No source knows the transaction "Not seen on the network"
abandoned missing for a period the wallet chooses Abandoned; never counted

Rules:

  1. A coin only ever moves toward spendable. A burial at depth D is kept as a fact (§4.3 rule 6) and never re-checked against a source.
  2. abandoned is presentation. Nothing proves a transaction will never confirm. A wallet that later finds an abandoned coin in a block counts it from there.
  3. A proof missing because the wallet's chain is not yet D − 1 past the block, or could not be read, is not a failure. The coin stays confirming or unchecked.

4.1 Burial proof

A burial proof is a JSON object:

{
  "tx": "<raw transaction, hex>",
  "path": ["<merkle sibling, display hex>", "..."],
  "pos": 0,
  "height": 3196930,
  "header": "<80-byte header of the block, hex>"
}
  • tx is the transaction serialized without witness data.
  • path lists the merkle siblings from the leaf up, in display (big-endian) hex. It has at most 32 entries.
  • pos is the transaction's index in the block, height the block's height and header its 80-byte header.

The proof names a block. It carries no other headers, and it never goes stale.

4.2 Checking a proof

A proof of coin txid:vout checks when all of these hold:

  1. The double SHA-256 of tx equals txid.
  2. tx is not a coinbase transaction, and it is not exactly 64 bytes long (a 64-byte transaction can pose as an inner merkle node).
  3. Output vout of tx has a locking script byte-equal to the coin script.
  4. Folding txid with path by pos (double SHA-256 of left‖right, internal byte order) yields the merkle root of header.
  5. The block hash of header is the hash at height on the wallet's chain.
  6. The wallet's chain has at least D − 1 blocks after height.

The result is one of three outcomes:

  • Buried: all six checks pass.
  • Missing: checks 1 to 4 pass, but the wallet's chain cannot yet decide 5 or 6, because its tip is not D − 1 past height or no header source could be reached. Missing is not failure. The coin stays confirming or unchecked, and the wallet checks again later.
  • Failed: any of checks 1 to 4 fails, or the wallet's chain is at least D − 1 past height and holds a different hash there.

A proof whose shallow block a reorganization removed is missing, not failed, until the wallet's chain is D − 1 past that height.

4.3 The wallet's header chain

The wallet keeps one header chain per binding. It trusts header bytes from any source only after checking them itself.

  1. Checkpoint. The chain starts at a checkpoint the wallet ships: a height, its block hash, and the bits and times the next retarget reads (§1). The checkpoint MUST be below the lowest coin the wallet will check. A wallet SHOULD keep it fixed rather than move it up with releases, because a coin below the checkpoint can only be reached by linking down by hash (rule 4).
  2. Forward headers. A header after the tip is accepted only when it names the tip's hash as its previous block, its bits are exactly what the binding's retarget rule gives at its height, its proof of work meets its target (§1), and its time is at most two hours after the wallet's clock.
  3. Reorganizations. A branch below the tip is accepted only if every header on it passes rule 2 and it has more total work (the sum of 2^256 / (target + 1)) than the part of the chain it replaces. A wallet MAY refuse branches deeper than 100 blocks.
  4. Below the checkpoint. A block below the checkpoint is accepted only when a run of headers links it up to the checkpoint by hash. A wallet MAY cap that run, and treats a coin below the cap as missing.
  5. On demand. A wallet syncs only when a check needs a height or depth its chain does not have yet. A check whose block is already D − 1 deep needs no network.
  6. Storage. A wallet needs to keep only block hashes by height, plus whatever history the retarget rule reads. It MUST keep them across restarts: a burial it has seen at depth D stays buried.

A header source only supplies bytes, so a dishonest source can delay a check but never make one pass.

5. Balance

For each chain and account:

  • spendable: the sum of spendable coins that no recorded payment spends;
  • confirming: the sum of received coins in unconfirmed, confirming, unchecked or missing that no recorded payment spends.

These balances are never added to the BSV balance, never converted into it, and never shown as a token.

6. Payments

A payment of amount to address runs in this order:

  1. Validate. address decodes as a payment address the binding accepts. amount is at least the dust floor. The destination script is not the coin script. Paying oneself only spends a fee, so the wallet refuses with own-address.
  2. Plan. Choose inputs only among spendable coins that no recorded payment spends. Pay amount to the destination, with the network fee on top. Add a change output to the coin script when the remainder is at least the dust floor. Otherwise the remainder goes to the fee. If the coins cannot cover the payment, refuse with insufficient.
  3. Sign every input under the binding's signing rule.
  4. Record. Store the signed transaction, its inputs and its change durably before broadcasting it. From then on its inputs are spent and its change is a change coin. If the record cannot be stored, the payment stops unbroadcast.
  5. Broadcast to any source. A source that refuses or cannot be reached leaves the payment signed, and it is retried on every later pass.
  6. Keep alive. A sent payment is checked until it is in a block. Whenever no source knows it, it is broadcast again.

A wallet MUST NOT spend a recorded payment's inputs in another payment, and MUST NOT release them on a timer. A payment that the chain refuses for good (for example, an input double-spent by a restored older install) is shown with its last error. Its inputs stay spent until the chain shows otherwise.

Fee rates come from a source or the wallet's own floor. A wrong fee rate only delays a payment, and the fee source is not trusted for anything else.

7. Paying an identity

A payment MAY name the recipient's identity key instead of an address. The coins are then locked to a key that only the recipient's wallet derives, and a notice tells it which one. A wallet that cannot read the notice would never find them, so a sender pays an identity only when the recipient has said its wallet receives the chain.

7.1 Payment key

The payment key is the BRC-42 child of the recipient's identity key under protocol [2, "<chain id> coin payment"] (for Litecoin, [2, "ltc coin payment"]), a key ID and, as counterparty, the sender's identity key. The sender derives its public key. The recipient derives its private key with the sender as counterparty. The key ID is 16 random bytes in lowercase hex, fresh for every payment, so no two payments share an address.

These protocols are reserved for the wallet under §2 rule 2.

7.2 Receiving record

A wallet that receives coins paid to its identity publishes a record signed by its identity key:

{ "v": 1, "identityKey": "02…", "protocols": ["coin:btc", "coin:ltc"], "at": 1760000000000, "signature": "…" }
  1. protocols is sorted and lowercase. coin:<chain id> (the BRC-214 profile id) says the wallet receives that chain under this section. Other entries belong to other documents and readers ignore those they do not know.
  2. signature is a 64-byte compact ECDSA signature (r ‖ s, hex) by identityKey over the SHA-256 of the UTF-8 JSON array ["wallet-protocols-v1", identityKey, protocols, at].
  3. A host stores the record at /v1/identities/<identityKey>/protocols.json. Anyone may read it. Only the identity may replace it, with a signed request, and never with a record whose at is not newer than the one held.
  4. A wallet republishes when what it receives changes, not on every start.

The host is storage, not authority. It can withhold a record or serve an older one, but it cannot forge one, and every reader verifies the signature itself.

7.3 Sender

  1. Refuse unless supported. Before consent, read and verify the recipient's record. If there is no record, or it does not name coin:<chain id>, refuse with unsupported-recipient. If the record cannot be read or does not verify, refuse with recipient-unchecked. Never send on an assumption.
  2. Destination. The coin script of the payment key (§7.1). The recipient's identity must not be the sender's own (own-address).
  3. Pay under §6 steps 2–6. Change returns to the sender's own coin key.
  4. Notify. Once the payment is broadcast, send { "v": 1, "t": "coin-payment", "chain", "txid", "vout", "keyId" }, sealed under BRC-78 to the recipient's identity, to its BRC-33 box coin-payment. Retry until a box accepts it. The notice carries no amount, so a notice can never claim more than the chain says. Delivery never gates, delays or undoes the payment.

7.4 Recipient

  1. Read. Open a notice only from the sender the sealed envelope authenticates. Derive the payment key with that sender and the notice's key ID, then record the output under §3 with the payment key's coin script in place of the coin script. A notice for another chain, a malformed one, or one whose output does not pay that script is refused.
  2. Prove. §4 applies unchanged.
  3. Keep the recipe. The account root cannot regenerate a payment key, which also needs the sender and key ID. Before acknowledging the notice, the wallet MUST store both, with the outpoint, somewhere that survives the device (for HandCash, the sealed custody journal beside the account's history backup). A restored wallet replays each stored coin under step 1 against the chain.
  4. Acknowledge the box message only once the coin is recorded and its recipe stored, or once the notice can never be good. A notice whose transaction no source knows yet stays in the box. A wallet MAY drop it after a period it chooses.
  5. Spend. Each input is signed with its own coin's key, either the wallet's coin key or that coin's payment key.

7.5 Applications

sendCoins takes identityKey in place of address (§9). Naming both is COIN_BAD_ARGS. The consent prompt names the identity, not the derived address.

8. Sources

A source supplies raw transactions, merkle paths, headers, address output lists, fee rates and broadcast. The wallet checks every byte (§3, §4). A wallet SHOULD use more than one source and fail over between them. Sources are never asked how deep a block is.

9. Application methods

All three methods take chain, a chain id the wallet supports. An unsupported chain is refused with COIN_UNSUPPORTED_CHAIN. BSV is not a chain here. BSV stays on BRC-100's own methods.

getCoinAddress({ chain })

Requires a connected application, with no further prompt.

{ "chain": "ltc", "address": "ltc1q…", "minConfirmations": 6, "decimals": 8 }

Asking for the address tells the wallet to expect coins and read the address more often for a while.

listCoins({ chain })

Requires a connected application, with no further prompt. The wallet reads the chain before answering.

{
  "chain": "ltc",
  "address": "ltc1q…",
  "spendable": "1200000",
  "confirming": "500000",
  "coins": [
    { "txid": "…", "vout": 0, "amount": "500000", "source": "received",
      "state": "confirming", "confirmations": 3, "needed": 6, "spent": false }
  ],
  "payments": [
    { "txid": "…", "address": "ltc1q…", "amount": "300000", "fee": "282",
      "status": "sent", "confirmed": false }
  ]
}

confirmations appears for a received coin that is not spendable. A payment that has not landed carries lastError when its last broadcast failed. A payment to an identity carries to: { identityKey, notified }, where notified says the recipient's box took its notice.

sendCoins({ chain, address | identityKey, amount, description? })

An action (§10). description is shown to the user exactly as for createAction. With identityKey, the payment follows §7.

{ "chain": "ltc", "txid": "…", "amount": "300000", "fee": "282", "status": "sent" }

status is queued when the payment was signed and recorded but no source took it yet. It is still a payment. It is never undone, and §6 keeps broadcasting it.

Errors

Errors use BRC-100's error object with these codes:

Code When
COIN_UNSUPPORTED_CHAIN chain is missing or not supported
COIN_BAD_ARGS address does not decode for the chain, identityKey is not a compressed public key, both are named, or amount is not a whole number at least the dust floor
COIN_OWN_ADDRESS The destination is the wallet's own coin address or identity
COIN_UNSUPPORTED_RECIPIENT The recipient's record is missing or does not name the chain (§7.3)
COIN_RECIPIENT_UNCHECKED The recipient's record could not be read or did not verify (§7.3)
COIN_INSUFFICIENT The spendable coins cannot cover the amount and fee
COIN_HELD_ELSEWHERE Another install holds this account (BRC-249)
ACTION_DENIED The user refused the payment
SPENDING_BUDGET_UNAVAILABLE A spending budget is active and the payment cannot be valued or recorded against it (§10)

A wallet refuses COIN_BAD_ARGS, COIN_OWN_ADDRESS, COIN_UNSUPPORTED_RECIPIENT, COIN_RECIPIENT_UNCHECKED, COIN_INSUFFICIENT and COIN_HELD_ELSEWHERE before asking for consent. The user is never asked to approve a payment the wallet would refuse.

10. Spending scope

sendCoins is a payment in the same scope as a BSV createAction. A wallet MUST apply every rule it applies to createAction, including these:

  1. Network. Refused while the device is offline, before consent.
  2. Account holding. Refused on an install that does not hold the account. Holding is re-checked after every gap (start, resume, reconnect), as for BSV spends.
  3. Consent. One prompt per call, never shared with another call. The prompt names the application and its description, and shows the amount in the chain's unit with its fiat value. A wallet MUST NOT show a coin amount as BSV or as satoshis.
  4. Budget. Any per-app automatic-payment budget and any BRC-73 spendingAuthorization grant is one budget per application, whatever chain it pays on. A coin payment counts against it at its BSV-equivalent fiat value when consent is given: amount × price(chain) ÷ price(BSV), in satoshis, rounded up.
    • Without both prices, the payment cannot be valued, so it is never approved automatically.
    • With a budget active, an approved payment that still cannot be valued is refused with SPENDING_BUDGET_UNAVAILABLE rather than sent uncounted.
    • Payments that are not plain coin payments (tokens, items, offers) stay outside the budget, as they are for BSV.
  5. Attribution. The payment's activity record names the application that asked.

11. Backup and recovery

The coin key derives from the account root, so restoring the account restores the coins. A restored wallet reads the coin address's unspent outputs and records each one as a received coin under §3. Each one becomes spendable again under §4, which needs no network for a block the wallet's chain already holds D deep.

Coins paid to the identity (§7) are the exception. Their keys need each coin's sender and key ID, which §7.4 step 3 stores off the device.

The payment record is local. A wallet SHOULD back it up with the account's other history (for example, BRC-39). A wallet restored without it might hold an unconfirmed payment the old install signed. That payment's inputs are already gone from the address's unspent set, so the restored wallet cannot double-spend them.

12. Presentation

  1. Coins are shown as their own chain on the wallet's balance view, never among collectables or tokens, and never labeled as being "on BSV", "wrapped" or "bridged".
  2. Activity reads "Received X LTC" and "Sent X LTC", names the chain ("On Litecoin"), shows a fiat value, and links to an explorer of that chain.
  3. A coin that is not spendable shows its state from §4 and who established it: the wallet's own chain, or a source.

13. Depositing and wrapping (informative)

Depositing is this document. The coin stays on its own chain under the wallet's key. Holding it costs nothing, and each payment costs one transaction on that chain.

Wrapping is an extra step taken on a deposited coin. The coin is locked on its chain, and BSV units are minted that stand for it. From then on the units move at BSV speed and fees, and the coin's chain sees nothing until someone redeems.

A wrap is its own consented action and is never automatic. Unwrapping always ends in a native coin again. The trust a wrap needs depends on whether the coin's chain can check BSV:

Coin's chain can… Wrap Who is trusted
verify BSV headers and Merkle paths in script (Bitcoin Cash: introspection, OP_CAT, big integers) The coin is locked in a covenant that releases it against a proven BSV burn No one. Security rests on a challenge window: one honest watcher and honest-majority hash power on BSV
check only signatures, hashes and time (Litecoin, Dogecoin) The coin is locked to the holder's key plus a cosigner's, with a pre-signed exit to the holder The cosigner must delete old key shares. It cannot move the coin alone, and the holder can always exit
— A reserve held by an issuer The issuer holds the coins. That is custody, and this document does not define it

The covenant wrap is the only one with no trusted party. Its costs:

Step Paid Cost
Lock Once One transaction on the coin's chain, into the covenant
Wait before minting Once D confirmations of the coin's chain (6 for Bitcoin Cash, about an hour)
Mint Once One BSV transaction carrying the burial proof, about 1 KB
Transfer Each time One BSV transaction plus the proof package. Nothing on the coin's chain
Redeem Once A BSV burn, then a transaction on the coin's chain carrying every BSV header from the covenant's checkpoint to the burn plus its confirmations, its Merkle path, and the burn transaction. A header is 80 bytes, about 11.5 KB per day the checkpoint is behind, plus the cost of running the check in script
Challenge window Once A wait (on the order of a day) in which anyone can cancel the redemption by showing a heavier BSV chain from the same checkpoint that lacks the burn

A wrap therefore costs a fixed amount at each end and nothing on the coin's chain in between. The redemption is the expensive part: the proof is large, script must verify it, and the window means waiting. Because the cost falls only at the ends, wrapping pays off for large amounts or for coins that will change hands many times. A small amount moved once is cheaper as a plain payment on its own chain.

The challenge window is what makes large wraps workable. Without it, the work in the redemption proof itself must exceed the coin's value, or someone could mine fake BSV headers privately. For a large amount, that would mean thousands of confirmations. With the window, faking a redemption means outmining all of BSV while every watcher looks on, so the proof only needs to reach the burn.

Security considerations

  • Explorer lies. Sources only supply bytes. A forged burial must be on the most-work branch of the wallet's own header chain, which needs majority hash power or an eclipse of every source plus enough real work to outpace the honest chain. Six Litecoin confirmations is where reorganizing a block stops being practical.
  • Unconfirmed incoming coins. They are shown but never spent or counted as spendable, so a double-spent incoming payment only ever disappears from confirming.
  • Crash between sign and broadcast. Recording before broadcasting means a signed payment is either in the record or was never broadcast. Inputs are never reused, so a crash cannot produce two conflicting payments.
  • Reserved protocol. If an application could sign under the coin key, it could build its own coin transactions outside consent. §2 rule 2 closes that.
  • Address reuse. One coin address per account links every incoming payment on the chain. Users who need unlinkability should not reuse the address until per-payment addresses are specified. Payments to an identity (§7) each use a fresh address, and an observer of the chain cannot link them to the recipient's identity, or to each other until the recipient spends them together.
  • Paying a wallet that cannot receive. Coins locked to a payment key are lost if the recipient's wallet never derives it. §7.3 refuses unless the recipient signed a record naming the chain, and a host can only withhold or roll back that record, never forge it. A rolled-back record still names only what that wallet once received.
  • Forged notices. A notice carries no amount and no key, only a key ID. The recipient derives the key from the authenticated sender, reads the amount from the transaction's bytes, and records nothing the chain does not show paying that key.
  • Losing the recipe. A recipient that acknowledges a notice before storing its sender and key ID off the device could lose those coins with the device. §7.4 step 4 orders the two.
  • Wrong network. Bindings fix the address encoding, so a testnet or MWEB address is refused as COIN_BAD_ARGS before signing.
  • Prices. Fiat prices only value a payment for display and budgets. A wrong price can make the user's own budget count a payment high or low, but it cannot move coins without consent or past a refused budget.

Rationale

  • No token. A token on BSV that stands for a coin elsewhere needs someone to hold the coin. Holding the coin directly removes that party, and the wallet already has the key material it needs.
  • The wallet's own header chain. A count from a source is a claim. A count on headers the wallet validated is a fact. The cost is about 18 KB of block hashes per day for Litecoin, synced only when a check needs it.
  • Spendable change. Waiting D blocks for one's own change would lock funds for no security gain, since only the wallet could double-spend its parent.
  • Same scope as BSV. Applications already have one consent model and one budget for payments. A second model for each chain would let an application route around the user's budget by paying on another chain.
  • chain argument. One method set for every chain keeps the BRC-100 surface flat as chains are added.

Open questions

  1. Per-payment addresses. BRC-42 children of the coin key, keyed by an invoice number, would end address reuse. The wallet would then read many scripts, which explorers serve poorly.
  2. Covenant wrap. The Bitcoin Cash covenant of §13 needs its own document: the lock script, how the checkpoint moves forward, the challenge rules, and the BSV-side check of the lock, which reuses §4 with the covenant script in place of the coin script.

Implementations

HandCash Desktop and Mobile (@handcash/wallet-ui), all four bindings:

  • src/wallet/coins/coinWallet.ts is one engine for coins, proofs and payments. src/wallet/{ltc,bch,btc,doge}/ hold each binding (keys, scripts, signing, header rules).
  • src/wallet/ltc/headers.ts is the header chain for every binding, parameterised by its rules.
  • src/wallet/coinMethods.ts implements the §9 methods.
  • Paying an identity (§7): src/wallet/walletProtocols.ts (receiving record), src/wallet/coins/coinPaymentBox.ts (box coin-payment), and coin recipes in the custody journal (src/wallet/custodyJournal.ts). BRC-CLOUD hosts the records (src/walletProtocols.js).
  • getLtcDepositAddress, listLtcDeposits and withdrawLtc remain as earlier names for the Litecoin binding.
  • Checkpoints: Litecoin 3196900, Bitcoin Cash 973400, Bitcoin 971900, Dogecoin 6420500.
  • Sources: Litecoin over Esplora (litecoinspace.org and a relay); the others over Electrum WebSocket servers that fail over (src/wallet/coins/electrumChain.ts).

References

  • BRC-33: PeerServ Message Relay Interface
  • BRC-42: BSV Key Derivation Scheme
  • BRC-43: Security Levels, Protocol IDs, Key IDs and Counterparties
  • BRC-67: Simplified Payment Verification
  • BRC-73: Group Permissions for App Access
  • BRC-78: Serialization Format for Portable Encrypted Messages
  • BRC-100: Wallet-to-Application Interface
  • BRC-208: Vault Sub-Accounts
  • BRC-219: Wallet Permission Prompt Liveness Contract
  • BRC-214 (draft, #338): Wallet Asset Profiles
  • BRC-249 (draft, #311): Vault Account Holding Across Devices
  • BIP143: Transaction Signature Verification for Version 0 Witness Program
  • BIP173: Base32 address format for native v0-16 witness outputs