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.
- 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.
- Prove. A received coin becomes spendable only when its block is buried
Ddeep on the wallet's own header chain of that chain. For Litecoin,Dis 6 confirmations. Explorers supply bytes and are never trusted for depth. - 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.
- 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.
- Apps. Three methods,
getCoinAddress,listCoinsandsendCoins, take achainargument.sendCoinsis a payment in the same scope as a BSVcreateAction: 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
spendingAuthorizationgrant, and any per-app automatic-payment budget a wallet keeps, coverssendCoinsat 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.
- 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.
- 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,decryptand both key-linkage methods. Coins move only through §6. - 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. - 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:
- The raw transaction (serialized without witness) hashes to the claimed txid.
- It is not a coinbase transaction, and its witness-stripped serialization is not exactly 64 bytes.
- 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:
- A coin only ever moves toward
spendable. A burial at depthDis kept as a fact (§4.3 rule 6) and never re-checked against a source. abandonedis presentation. Nothing proves a transaction will never confirm. A wallet that later finds an abandoned coin in a block counts it from there.- A proof missing because the wallet's chain is not yet
D − 1past the block, or could not be read, is not a failure. The coin staysconfirmingorunchecked.
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>"
}
txis the transaction serialized without witness data.pathlists the merkle siblings from the leaf up, in display (big-endian) hex. It has at most 32 entries.posis the transaction's index in the block,heightthe block's height andheaderits 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:
- The double SHA-256 of
txequalstxid. txis not a coinbase transaction, and it is not exactly 64 bytes long (a 64-byte transaction can pose as an inner merkle node).- Output
voutoftxhas a locking script byte-equal to the coin script. - Folding
txidwithpathbypos(double SHA-256 of left‖right, internal byte order) yields the merkle root ofheader. - The block hash of
headeris the hash atheighton the wallet's chain. - The wallet's chain has at least
D − 1blocks afterheight.
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 − 1pastheightor no header source could be reached. Missing is not failure. The coin staysconfirmingorunchecked, and the wallet checks again later. - Failed: any of checks 1 to 4 fails, or the wallet's chain is at least
D − 1pastheightand 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.
- 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).
- 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.
- 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.
- 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.
- 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 − 1deep needs no network. - 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
Dstays 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,uncheckedormissingthat 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:
- Validate.
addressdecodes as a payment address the binding accepts.amountis at least the dust floor. The destination script is not the coin script. Paying oneself only spends a fee, so the wallet refuses withown-address. - Plan. Choose inputs only among spendable coins that no recorded payment spends. Pay
amountto 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 withinsufficient. - Sign every input under the binding's signing rule.
- 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.
- Broadcast to any source. A source that refuses or cannot be reached leaves the payment
signed, and it is retried on every later pass. - Keep alive. A
sentpayment 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": "…" }
protocolsis 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.signatureis a 64-byte compact ECDSA signature (r ‖ s, hex) byidentityKeyover the SHA-256 of the UTF-8 JSON array["wallet-protocols-v1", identityKey, protocols, at].- 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 whoseatis not newer than the one held. - 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
- 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 withunsupported-recipient. If the record cannot be read or does not verify, refuse withrecipient-unchecked. Never send on an assumption. - Destination. The coin script of the payment key (§7.1). The recipient's identity must not be the sender's own (
own-address). - Pay under §6 steps 2–6. Change returns to the sender's own coin key.
- 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 boxcoin-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
- 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.
- Prove. §4 applies unchanged.
- 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.
- 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.
- 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:
- Network. Refused while the device is offline, before consent.
- 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.
- 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.
- Budget. Any per-app automatic-payment budget and any BRC-73
spendingAuthorizationgrant 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_UNAVAILABLErather than sent uncounted. - Payments that are not plain coin payments (tokens, items, offers) stay outside the budget, as they are for BSV.
- 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
- 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".
- 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.
- 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_ARGSbefore 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
Dblocks 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.
chainargument. One method set for every chain keeps the BRC-100 surface flat as chains are added.
Open questions
- 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.
- 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.tsis 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.tsis the header chain for every binding, parameterised by its rules.src/wallet/coinMethods.tsimplements the §9 methods.- Paying an identity (§7):
src/wallet/walletProtocols.ts(receiving record),src/wallet/coins/coinPaymentBox.ts(boxcoin-payment), and coin recipes in the custody journal (src/wallet/custodyJournal.ts). BRC-CLOUD hosts the records (src/walletProtocols.js). getLtcDepositAddress,listLtcDepositsandwithdrawLtcremain as earlier names for the Litecoin binding.- Checkpoints: Litecoin 3196900, Bitcoin Cash 973400, Bitcoin 971900, Dogecoin 6420500.
- Sources: Litecoin over Esplora (
litecoinspace.organd 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