bsv-rs
A Rust SDK for BSV: cryptographic primitives, the Bitcoin Script interpreter, transactions with BEEF and SPV, BRC-42 wallets, BRC-103 mutual authentication, and the overlay network (SHIP, SLAP, STEAK), with storage, registry, key-value and identity clients on top.
It is a reference-parity port of the TypeScript @bsv/sdk, cross-checked against the Go SDK: the same bytes on the wire, the same verdicts from the interpreter, pinned by shared test vectors and by the ts-stack conformance corpus. It builds for wasm32-unknown-unknown and runs in production inside Cloudflare Workers.
What you get
| Module | Feature | What it is |
|---|---|---|
primitives |
default | SHA-256/512, RIPEMD-160, HMAC, PBKDF2, HMAC-DRBG (RFC 6979), secp256k1 and P-256 ECDSA, ECDH, Shamir, AES-256-GCM, BigNumber, hex/base58/base64, a binary Reader/Writer with varints |
script |
default | Script parsing (hex, ASM, chunks), every BSV opcode, the Spend interpreter, templates: P2PKH, P2PK, Multisig, RPuzzle, PushDrop |
transaction |
Transaction building, signing, fee models (static and ARC live policy), MerklePath (BRC-74 BUMP), Beef (BRC-62/95/96), verify (scripts and merkle roots against a ChainTracker), ARC and WhatsOnChain clients |
|
wallet |
BRC-42/43 KeyDeriver, ProtoWallet (sign, verify, encrypt, decrypt, HMAC), WalletClient over HTTP, the BRC-100 binary wire protocol (28 methods, Go-compatible) |
|
messages |
BRC-77 signed and BRC-78 encrypted messages | |
compat |
BIP-32 HD keys, BIP-39 mnemonics (eight wordlists), Bitcoin Signed Messages, Electrum and Bitcore ECIES | |
totp |
RFC 6238 one-time passwords | |
auth |
BRC-103 mutual authentication (Peer, sessions, BRC-52/53 certificates) over BRC-104 HTTP; socketio adds a Socket.IO 5 transport, websocket a tokio-tungstenite one |
|
overlay |
LookupResolver (SLAP host discovery, request coalescing, per-host reputation), TopicBroadcaster (SHIP, STEAK acknowledgements), signed SHIP/SLAP admin tokens, Historian |
|
storage |
UHRP content-addressed upload and download | |
registry |
On-chain definitions for baskets, protocols and certificate types | |
kvstore |
LocalKVStore (encrypted, wallet baskets) and GlobalKVStore (public, overlay-backed) |
|
identity |
Certificate-based identity resolution and contacts |
default = ["primitives", "script"]; full turns every module on (including socketio). Platform and transport flags: http (reqwest), websocket (opt-in, not in full), wasm, dhat-profiling. The dependency order is primitives → script → transaction → wallet → { messages, auth, overlay }, with storage, registry and kvstore on overlay, identity on auth and overlay, and compat and totp on primitives alone.
[]
= "0.3" # primitives + script
= { = "0.3", = ["transaction"] } # + transactions, BEEF, SPV
= { = "0.3", = ["wallet"] } # + BRC-42 keys, ProtoWallet
= { = "0.3", = ["auth", "http"] } # + BRC-103 over HTTP
= { = "0.3", = ["overlay", "http"] } # + SHIP/SLAP
= { = "0.3", = ["full", "http"] } # everything, native
= { = "0.3", = false,
features = ["auth", "wallet", "transaction", "overlay", "socketio", "wasm"] } # a Worker
Examples
Every code block below is a file under examples/, compiled by CI (cargo build --examples --all-features) and pinned to the README byte for byte by tests/readme_examples.rs. The first five run with no network.
Keys, hashes, signatures
cargo run --example keys
// examples/keys.rs
use ;
Scripts, and the interpreter
cargo run --example script
// examples/script.rs
use P2PKH;
use ;
A transaction: build, sign, verify
cargo run --example transaction --features transaction
// examples/transaction.rs
use PrivateKey;
use P2PKH;
use SignOutputs;
use ;
BRC-42 keys and the ProtoWallet
cargo run --example brc42 --features wallet
// examples/brc42.rs
use PrivateKey;
use ;
BEEF: write, read, validate, verify
cargo run --example beef_spv --features transaction
// examples/beef_spv.rs
use PrivateKey;
use P2PKH;
use SignOutputs;
use ;
The overlay network
cargo build --example overlay --features "overlay,http" (running it reaches the public mainnet hosts)
// examples/overlay.rs
use ;
use Transaction;
async
More: BRC-103 over HTTP or Socket.IO (auth, socketio), the wallet wire protocol (wallet::wire), UHRP storage, the registry, the KV stores and identity all carry worked examples in their module docs on docs.rs.
The contracts that matter
The interpreter is the TypeScript SDK's default evaluation mode. Spend has no verify-flags parameter. A transaction of version 1 or lower runs strict: minimal pushes and minimally encoded numbers, low-S, NULLDUMMY, push-only unlocking scripts, a clean stack, strict DER and public-key encodings, SIGHASH_FORKID required. Version 2 and above runs the post-Genesis relaxed mode (MINIMALDATA, LOW_S and CLEANSTACK not enforced), as Spend.isRelaxed() does upstream; set_require_minimal and set_require_push_only override either way. The opcode set is post-Genesis BSV (OP_MUL, OP_CAT, OP_LSHIFT and the rest enabled; OP_2MUL, OP_2DIV, OP_VER, OP_VERIF, OP_VERNOTIF disabled; no pre-Genesis size or count limits; no P2SH evaluation; CLTV and CSV are NOPs). A CHECKSIG's signed subscript continues across the unlock/lock boundary after an OP_CODESEPARATOR, so OP_PUSH_TX covenants verify. A script element may be up to 1 GiB; the working memory budget is 32 MB by default (memory_limit); exhausting it is a ScriptResourceLimit (Stack, AltStack, ElementSize) that is_resource_limit() tells apart from a refusal, and OP_NUM2BIN refuses an oversized size operand before allocating it.
BEEF linking is linear and verify walks by txid. Transaction::from_beef links each distinct unproven parent once and gives every later input sourcing the same txid a stub, so a diamond chain (each level spending both outputs of the last) links in time and memory linear in the BEEF; verify gathers the reachable transactions into a map by txid, checks a proven one against the ChainTracker and does not descend it, executes every input script of an unproven one against its source looked up by txid, and refuses an unproven transaction whose outputs exceed its inputs (a transaction with no inputs is a synthetic root and exempt). A BEEF whose links form a cycle terminates.
Sighash computation lives in the script templates, not in Transaction, as in the reference SDKs. SighashCache computes the three BIP-143 midstates once per transaction and reuses them across inputs (the free functions recompute per call, quadratic in the input count).
Parsers are bounded. Every pre-allocation sized by an attacker-controlled count is capped by what the remaining bytes could hold (primitives::bounded_capacity): a crafted 20-byte BEEF with a u32::MAX input count is an Err, never an abort, on native and on wasm32 (where panic = abort would otherwise be unrecoverable).
wasm32 is a first-class target. The wasm feature routes randomness through the JS host, wall-clock reads through js_sys::Date (std::time::SystemTime is unimplemented on wasm32-unknown-unknown), and the Peer handshake timeout through futures-timer's setTimeout backend (no tokio time driver exists in a Worker). The Socket.IO transport pulls in no dependency, so enabling it never breaks a wasm build. This build is exercised on every change:
Conformance
Parity with the reference SDKs is a discipline, not a claim:
tests/vectors/: 2,031 JSON vectors shared with the TypeScript and Go SDKs (sighash 500; script evaluation 1,488 across valid, invalid and spend cases; BRC-42 derivation, HMAC-DRBG, AES-256-GCM, certificates, overlay types and admin tokens, wallet wire messages).- The
ts-stackconformance corpus:tests/conformance_scripts.rsdrives the interpreter, the sighash builder and theScriptAPI through the script-domain corpus (5,116 vectors, the total pinned) andtests/conformance_beef.rsthe BUMP, serialization, BEEF and regression corpora. They read$BSV_CONFORMANCE_DIR(default../ts-stack/conformancebeside the crate) and skip, loudly, when the corpus is absent; when present, every vector is executed or counted against an enumeratedunsupportedallowlist with a reason, and the per-class counts are pinned so corpus or harness drift is a red test. - Cross-SDK wire protocol: the BRC-100 wallet wire (
WalletWireTransceiver/WalletWireProcessor) round-trips all 28 methods against vectors captured from the Go SDK's serializer. - Known divergences are written down, in
CHANGELOG.mdand inCLAUDE.md: the Go SDK's default counterparty (AnyonevsSelf), Go's missing TOTP, overlay caching, historian, reputation and RPuzzle; the TypeScript TOTP default of 2 digits (this crate uses 6, per the RFC); the SDKs' disagreement on the nonce HMAC inputs (verify_nonceis only ever called on a peer's own nonces); the Go admin-token signing key; and RFC 6979 nonces for a digest at or above the curve order, where k256 follows the RFC and libsecp256k1 does not (signatures differ in that regime only, and both verify).
Numbers (measured at 0.3.24)
| Source | 88,544 lines of Rust under src/, 13 feature-gated modules, one Error enum |
| Tests | 2,863 passed, 0 failed (1,469 unit, 1,226 integration across 36 files, 168 doc tests); 127 doc examples are ignored illustrations |
| Vectors | 2,031 shared JSON vectors + the ts-stack corpus (5,116 script-domain vectors pinned) |
| Fuzzing | 4 libFuzzer targets: the script parser, the transaction parser, the wire protocol, base58 |
| Benchmarks | 4 Criterion suites: hashes, primitives, script, memory (with RSS tracking) |
| CI | Linux, macOS and Windows on stable and beta; clippy with -D warnings; rustfmt; doc build |
Run it yourself: cargo test --features "full,http,websocket" (with ~/bsv/ts-stack beside the crate the conformance census runs too).
Who runs it
The LOW stack (bsv-low, a real-money card game on mainnet) runs bsv-rs in every one of its Cloudflare Workers: its overlay engine executes every submitted spend with Transaction::verify before broadcasting it, its watchtower co-signs 2-of-3 pot spends and verifies BRC-103 envelopes with ProtoWallet and Peer, its relay speaks BRC-103 over Socket.IO with this crate's transport, its app-layer authenticates with the BRC-104 middleware built on it, and its monitor parses and classifies spends with Transaction. Several of this crate's fixes were found there first (see the changelog: bounded pre-allocation, linear BEEF linking, the interpreter's resource-limit class).
Standards
| BRC | What | Module |
|---|---|---|
| BRC-42 / BRC-43 | Key derivation, security levels, protocol IDs | wallet |
| BRC-52 / BRC-53 | Identity certificates, field encryption | auth |
| BRC-62 / BRC-74 / BRC-95 / BRC-96 | BEEF, BUMP merkle paths, atomic BEEF, BEEF v2 | transaction |
| BRC-77 / BRC-78 | Signed and encrypted messages | messages |
| BRC-100 | The wallet interface and its binary wire protocol | wallet |
| BRC-103 / BRC-104 | Mutual authentication and its HTTP transport (the successors of BRC-31/Authrite; some older comments still use that name) | auth |
| SHIP / SLAP / STEAK | Overlay submission, lookup availability, acknowledgements | overlay |
| UHRP | Content-addressed storage | storage |
Development
&&
Releases: bump version in Cargo.toml, write the CHANGELOG.md entry (what changed, why, which test pins it), cargo publish, tag vX.Y.Z, push main and the tag. Every public API change is additive within 0.3; a struct gaining a public field is called out in the changelog with what a downstream literal needs.
License
MIT or Apache-2.0, at your option (LICENSE-MIT, LICENSE-APACHE).