We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
A payment that carries its own proof
Any wallet can send it, any app can accept it, and anyone can check it without asking a company. The specs for that exist: BEEF, BUMP and BRC-100. This kit is a way to show they work together, with test vectors anyone can use and a check you can try by hand.
Draft
So far the vectors have been tried against one SDK, @bsv/sdk 2.4.0, in a one-off run, and against no live wallet. Nothing here says any wallet or project conforms to this kit or endorses it.
Check a BEEF
BEEF V1, BEEF V2 or Atomic BEEF around either, up to 4,000,000 bytes. Your bytes are read for this check and not kept.
What this check says, and what it leaves to you
The check says whether a transaction's bytes sit under a merkle root in a block header. That is inclusion in a block, and it's the whole claim.
It doesn't run scripts or check signatures, so it can't say a transaction is valid. It doesn't look for double spends, so it can't say inputs were unspent. It doesn't read amounts or terms, so it can't say the right payment was made. A receiver does those parts, and each one is listed below.
Roots are compared with the headers Bitblocks holds, which come from one node. Keep your own headers if you'd rather not take ours on trust.
- Proven
- Its bytes hash to its txid, its BUMP climbs to a root, and that root is the one in a verified header on the chain we hold.
- Anchored
- It has no BUMP, and every input leads, within this BEEF, to proven transactions. It is not itself shown to be in any block.
- Unknown
- We can't say: we hold no usable header at that height, or the BEEF lacks what it needs. Never read this as a pass.
- Failed
- The evidence is wrong: the root differs from our header, the BUMP reaches no root or can't be that block's, or an ancestor failed.
Test vectors, open to everyone
/interop/vectors/v1.json needs no key and no account. Version 1.0.0 holds 46 vectors: 15 valid and 31 deliberately invalid.
Each one gives its bytes, the outcome to expect and where it came from, and the file carries the headers to check against. Examples from BRC-62 and BRC-74 sit beside bytes written by @bsv/sdk, a real mainnet transaction, and cases we crafted to fail.
A test runs every vector through our reader and verifier, so the file can't drift from what the code does.
Where we're stricter than a spec, the vector is marked hardening, and a reader can differ there and still follow the spec.
curl -s https://bitblocks.app/interop/vectors/v1.json | jq '.vectors[0] | {id, valid, expected}'
What a conforming sender does
- Sends the payment as Atomic BEEF (BRC-95), with the payment as the subject.
- Includes every unconfirmed ancestor, back to transactions with a BUMP (BRC-74).
- Puts parents ahead of children, and ends with the payment (BRC-62).
- Leaves out anything outside the subject's ancestry.
- Writes each VarInt in its shortest form, with nothing after the end.
What a conforming receiver does
- Reads the bytes strictly, and refuses anything that doesn't parse.
- Checks every BUMP's root against headers it trusts, by its own choice of source.
- Treats unknown as not yet, and failed as no.
- Runs the payment's scripts against the outputs it spends, and adds up the amounts.
- Confirms the outputs pay what was agreed, to whom it was agreed.
- Sends the payment to the network, and keeps the BEEF with its record.
One payment, end to end
- The receiver states the terms: the amount, the script to pay, and a random salt.
-
The wallet builds the payment and produces Atomic BEEF without sending it.
A BRC-100 wallet returns this from
createAction, and needsnoSendset, because by default it sends the payment itself. - The wallet hands the bytes straight to the receiver, with no third party in between.
- The receiver verifies against headers: the payment reads anchored, and each parent reads proven or anchored.
- The receiver checks scripts, amounts and terms, then sends the payment to the network. If the network reports a conflict, it stops here and issues nothing.
- The receiver makes a payment evidence statement (BRC-237). If it takes the payment, it adds a signed acceptance record naming the outputs it accepted.
- Once the payment is mined, the receiver fetches its BUMP, checks again, and the payment reads proven. That makes a new statement, and the acceptance record stays as it was signed.
A receipt is those two things together. BRC-237 defines the evidence half, and it's new, unproven on a live chain, and not yet produced here. No published format covers acceptance, so that record is our candidate. Evidence says what was checked, acceptance says what the receiver took as payment, and neither says the payment will be mined.
Anchored and proven, for a fresh payment
A fresh payment isn't in a block yet, so anchored is the best it can read, and that's the normal result. It means the payment's inputs name transactions that, inside this BEEF, lead back to transactions in blocks.
Anchored doesn't mean accepted. The scripts may not pass, an input may already be spent, and the network hasn't said anything yet. It's the start of a receiver's work, and for a small payment that work may be enough to go ahead. How much risk to take before proven is the receiver's call.
Proven means the payment itself sits in a block under a header you rely on. That is the strongest thing this check can say, and it still covers inclusion alone.
Failed means the evidence doesn't hold against these headers. That usually means it's wrong, and after a reorg a good payment's old path reads failed until the path is replaced.
Known gaps
- Scripts, signatures, amounts and double spends aren't checked here, so an anchored transaction may still be invalid. An input's output index isn't looked at either.
- Headers come from one node, and we trust it for which chain is longest.
- A block's transaction count isn't in its header, so the check on a proof's shape rests on our node's word.
- BRC-95 doesn't say which byte order the subject txid takes. We read internal order, as the SDK writes it.
- BRC-96 treats a txid-only entry as agreed between two parties. A third-party check has no such agreement, so we answer unknown.
- No live wallet has been tried yet, and one SDK run is a start rather than a result.
If you build wallets or apps, run the vectors and tell us where they're wrong. The full write-up is in docs/INTEROP_KIT.md.