All notable changes to the bsv-sdk gem are documented here.
The format is based on Keep a Changelog and this gem adheres to Semantic Versioning.
- Protocol layer: base class with declarative DSL, HTTP dispatch, and Result value types
- WoCREST protocol with transaction detail, script query, exchange rate, fee, mempool, SPV, wallet, and address commands
- ARC protocol with broadcast escape hatches and enriched response data
- TAALBinary protocol with binary broadcast support
- Chaintracks and Ordinals protocols with base_url override
- Provider configuration container with block DSL and
.default(testnet:)pattern - Provider introspection and capability matrix
- Concrete provider defaults for GorillaPool, WhatsOnChain, and TAAL
- Facades (ARC, WhatsOnChain, ChainTracker) hollowed out to delegate via Protocol
- Removed
BSV::MAINNET_URL/TESTNET_URLconstants and ENV var reading — provider defaults are the single source of truth - Removed
BASE_URLfrom protocols —base_url:is now mandatory - Wallet namespace reorganisation:
StoreandBroadcastQueuecollaborators namespaced underClient ProtoWalletandWalletClientreplaced withClient
- Ruby 2.7 kwargs compatibility in
Protocol#calland TAALBinary spec doubles - Nil-guard on WoCREST broadcast and ARC status metadata
- ARC status rejection checks and TAALBinary nil-txid guard
BSV::Wallet::Walletautoload failure in multi-gem setup- SimpleCov coverage gate now requires
COVERAGE=trueexplicitly
Transaction#to_efnow derivessource_satoshisandsource_locking_scriptfrominput.source_transaction.outputs[input.prev_tx_out_index]when the explicit fields are unset. Previouslyto_efraisedArgumentErroron inputs wired viaTransaction.from_beef, causingARC#broadcastto silently fall back to raw hex — which ARC rejects when parent transactions are unconfirmed. Consumer impact: recently-received UTXOs that previously could not be re-broadcast now can. Matches TS SDKwriteEFbehaviour. (#467, HLR #466)Transaction.from_beefandfrom_beef_hexnow useBeef#find_atomic_transactionto locate the subject transaction, ensuring thatFORMAT_RAW_TXancestors whose txid appears as a leaf in a separately-stored BUMP have theirmerkle_pathattached. Covers a late-bound BUMP attachment gap not handled by the initialwire_source_transactionspass. MatchesTransaction.fromAtomicBEEFsemantics in the TS SDK. (#468, HLR #466)
Related upstream issue: wallet-toolbox#149 — same architectural gap in the TS SDK.
- Certificate infrastructure:
BSV::Auth::Certificatebase class with field map, serialisation, and signature verification (#420) BSV::Auth::MasterCertificatefor certificate issuance with identity key binding (#421)BSV::Auth::VerifiableCertificatefor selective field revelation with proof-of-field-revelation (#422)- Certificate utilities:
validate_certificatesandget_verifiable_certificateshelpers (#427, #428) - Peer protocol:
certificateRequest/certificateResponsemessage handling, callback registration, andlast_interacted_peer(#430, #431) - High-level peer session API:
Peer#to_peerandPeer#get_authenticated_sessionfor reusable authenticated sessions (#433) - BRC-104 HTTP auth transport:
AuthFetchclient,SimplifiedFetchTransport,AuthMiddleware(Rack), andAuthHeaders/AuthPayloadserialiser (#437–#441) AuthFetch402 payment handling for paid API endpoints (#441)BSV::WireFormatmodule for camelCase/snake_case conversion at JSON boundaries (#447)- Cross-SDK conformance and integration specs for certificates (#423)
- Peer protocol integration tests (#434)
- BRC-104 integration tests (#442)
- Auth handshake now uses shallow key conversion to avoid corrupting nested message payloads
- Certificate classes hardened against edge cases from code review
- Certifier allowlist enforced in
process_certificate_response - Flaky
validate_certificates_specfixed (missingrequire 'base64')
GetVerifiableCertificatesbug warning note removed (underlying bug now fixed in bsv-wallet)- Peer protocol internals refactored:
PairedTransporthelper, deduplicated high-level API, hardened message processing
ARC.defaultandLivePolicy::DEFAULT_ARC_URLnow point toarcade.gorillapool.io(ARCADE) instead of the oldarc.gorillapool.ioendpoint (#418)- Centralised ARCADE URLs into
BSV::MAINNET_URL/BSV::TESTNET_URLconstants, overridable viaBSV_ARC_MAINNET_URLandBSV_ARC_TESTNET_URLenvironment variables
Base58Check.check_encodeacceptsprefix:parameter;check_decodeacceptsprefix_length:(F1.1)Base58.decode("")raisesArgumentErrorinstead of returning empty bytes (F1.2)Digest.hash256alias forsha256d(F1.7)Script.p2pkh_lockaccepts Base58Check address strings as well as raw 20-byte hashes (F3.18)TransactionInput#sequenceandTransactionOutput#locking_scriptnow writable viaattr_accessor(F4.10/F4.11)- Coinbase 100-block maturity check in
MerklePath#verify— intentionally diverges from TS SDK bug (F5.11) - ECIES Electrum
no_key:encrypt andsender_public_key:decrypt with uncompressed key detection (F6.7) BSV::Messagesnamespace re-exportingSignedMessageandEncryptedMessage(F6.16)
PointInFiniteFieldzero-coordinate Base58 round-trip (BN(0) now encodes as"\x00")p2pkh_lockencoding check uses bytesize only — accepts 20-byte hashes regardless of string encodingBase58Check.check_decoderaises onprefix_lengthexceeding payload- ECIES key-format ambiguity documented (inherited TS SDK design)
- Numeric fee
.ceilbehaviour documented with inline comment (F4.6)
arc_statusattribute onBroadcastErrorto distinguish ARC rejection reasons (double-spend, invalid, malformed) for downstream status mapping
-
Chronicle opcode support (HLR #328). All 10 Chronicle-restored opcodes now execute with correct semantics, replacing the 0.9.0 "raise first" fail-safes:
- Splice: OP_SUBSTR, OP_LEFT, OP_RIGHT
- Arithmetic: OP_LSHIFTNUM, OP_RSHIFTNUM (sign-preserving right shift)
- Arithmetic: OP_2MUL, OP_2DIV (restored from disabled)
- Flow control: OP_VER (push 4-byte LE tx version), OP_VERIF/OP_VERNOTIF (version-conditional branching, added to CONDITIONAL_OPCODES)
MISSING_TX_CONTEXTerror code for OP_VER without transaction context
-
Arcade integration (HLR #329):
BSV::Transaction::ChainTrackers::Chaintracks— chain tracker using Arcade's Chaintracks v2 API for SPV verificationChainTrackers.default(testnet:)— factory returning a Chaintracks instance pointed at ArcadeARC.default(testnet:)— factory returning an ARC broadcaster pointed at GorillaPool, matching TS/Go/Py SDK defaultsARC#broadcast_many(txs)— batch broadcast via POST/v1/txs, returns mixed array ofBroadcastResponse/BroadcastError- Skip-validation headers (
skip_fee_validation:,skip_script_validation:) onbroadcastandbroadcast_many
- Directory restructure (PR #327). Source files moved to per-gem
directories:
gem/bsv-sdk/,gem/bsv-wallet/,gem/bsv-attest/. No change to gem names or require paths.
op_disabledhandler — OP_2MUL/OP_2DIV are now restored; the handler is no longer needed.
-
Change distribution threshold fixed (#323, F4.1).
distribute_changenow drops change outputs only whenavailable <= 0, not whenavailable <= change_outputs.length. Previously 1 satoshi of available change with 1 output was incorrectly dropped. -
estimated_sizeraises on inputs without template (#323, F4.3). Previously fell back to a 148-byte P2PKH estimate. Now raisesArgumentErrormatching TS/Go behaviour. Migration: setunlocking_script_templateon all inputs before callingestimated_sizeorestimated_fee. -
total_input_satoshisfalls back throughsource_transaction(#323, F4.4). Ifsource_satoshisis nil butsource_transactionis wired, the satoshis are extracted from the referenced output automatically. -
Transaction#signvalidates output satoshis (#323, F4.9). RaisesArgumentErrorif any output has nil satoshis, preventing a corrupt sighash preimage. -
BEEF/BUMP validation and merge hardening (HLR #315, A3 cluster). Twelve findings addressed across
BeefandMerklePath:- F5.1 —
BeefTxTXID_ONLY entries now store txid in display byte order (matchingTransaction#txid). Wire serialisation reverses at the boundary. Fixes cross-SDK TXID_ONLY interop. - F5.12 —
Beef.from_binarynow raisesArgumentErrorfor unknown version magic bytes instead of silently accepting them. - F5.10 —
MerklePathconstructor now validates: non-negativeblock_height, non-emptypath, all levels areArray<PathElement>, and level 0 contains at least onetxid: trueelement. - F5.2 —
MerklePath#compute_rootcorrectly handles single-level compound paths wheremax_offset.bit_length > path.length(matches TS SDKcomputeRootlogic including duplicate-sibling handling for odd rightmost nodes). - F5.4 —
Beef#valid?now cross-checks eachFORMAT_RAW_TX_AND_BUMPentry: the BUMP must exist andcompute_root(txid)must succeed. - F5.3 — New
Beef#verify(chain_tracker = nil, allow_txid_only: false)method: callsvalid?then optionally verifies each BUMP's root against a chain tracker viavalid_root_for_height?(root_hex, block_height). - F5.5 —
sort_transactions!now preserves unsortable (cyclic) transactions in@txs_not_validinstead of silently dropping them.to_binarynow callssort_transactions!before serialising. - F5.6 —
merge_bumpretroactively upgrades existingFORMAT_RAW_TXentries toFORMAT_RAW_TX_AND_BUMPwhen the new BUMP covers their txid. - F5.7 —
merge_transactionandmerge_raw_txnow implement the full upgrade chain:TXID_ONLY → RAW_TX → RAW_TX_AND_BUMP. - F5.8 —
find_bumpnow also scans@bumpsdirectly when the transaction-table has no matching entry. - F5.9 —
Beef#mergeconstructs newBeefTxinstances rather than sharing (and mutating) source references. - F5.20 —
to_binarycallssort_transactions!before serialising to ensure correct dependency order.
- F5.1 —
-
PrivateKey#to_wifalways produces compressed WIF (#316, F2.4). Thecompressed: falsekeyword has been removed. BSV exclusively uses compressed public keys, so exporting an uncompressed WIF is never valid on the network ("construct only what's valid").from_wifcontinues to accept both compressed and uncompressed WIF for import compatibility with legacy wallets. Migration: remove anyto_wif(compressed: false)call sites. If you need to import an existing uncompressed WIF,from_wifstill handles it. -
Curve.multiply_generator/multiply_pointroute secret scalars through constant-time Montgomery ladder (#316, F2.1).PrivateKey#public_key,PrivateKey#derive_shared_secret,PublicKey#derive_shared_secret, andECDSA.sign_rawnow callCurve.multiply_generator_ct/Curve.multiply_point_ct(new), which delegate toPoint#mul_ct(Montgomery ladder). Variable-time wNAF (mul) is retained for public-scalar paths (signature verification). Benchmarked at ~2× slower than wNAF, within the expected 2–3× bound. -
WNAF_TABLE_CACHEis now bounded at 512 entries (#316, F2.1). Evicts the oldest entry (FIFO) when the limit is reached. Prevents unbounded memory growth in long-running server processes that operate on many distinct base points. -
Signature.from_derrejects multi-byte DER length encoding (#316, F2.3). Signatures with a length byte where bit 7 is set (e.g.0x81,0x82) are now rejected withArgumentError: non-canonical DER length. This enforces BIP-66 strict DER; such encodings were never generated by this SDK and should never appear in valid BSV transactions. -
ECDSA.signacceptsforce_low_s:keyword (#316, F2.2).ECDSA.sign(hash, key, force_low_s: true)explicitly normalises S to the lower half of the curve order. Default (false) preserves existing behaviour (sign_rawalready normalises internally). Documented that BSV consensus requires low-S.
-
Curve.ec_key_from_private_bytesandCurve.ec_key_from_public_bytesdeleted (#316, F2.9). These methods were unused in all production code paths — the only callers were test helpers and the shim'sparse_dermethod, which are also removed. TheBSVShimECDER-parsing constructor (OpenSSL::PKey::EC.new(der_string)) is no longer supported; pass aBSVShimECPointdirectly. -
Script::Script.pushdrop_lockdefaultlock_positionchanged to:before(#317, F3.12). Thelock_position:keyword argument has been added topushdrop_lockandPushDropTemplate#lock. The default is now:before(lock script first, then data fields and drops), matching the ts-sdk convention used by overlay token protocols. The previous implicit behaviour was equivalent to:after. Migration: callers that built PushDrop outputs using the old default must addlock_position: :afterto preserve their existing on-chain script layout. Scripts already on-chain are unaffected — the parser detects both layouts. -
Script#parse_chunksis now lenient on truncated scripts (#317, F3.16). Previously, calling#chunkson a truncated script raisedArgumentError. Now the parser returns a partial chunk array with a trailing raw-bytes chunk, consistent withp2pkh?,op_return?, and other byte-level predicates that already returnedfalserather than raising. Callers that rescueArgumentErrorfrom#chunksshould update their rescue logic. -
Script::Script.op_returnand OP_RETURN parsing (#317, F3.1, F3.10). The parser now terminates at a top-levelOP_RETURNand absorbs all trailing bytes into a single raw-data chunk (matching ts-sdk).Chunk#to_asmrenders these asOP_RETURN <tail_hex>rather than separate push chunks. The#op_return_datamethod re-parses the tail internally, so it still returns individual data items. Code that inspectedscript.chunks[2..]after anOP_RETURNmust switch toscript.op_return_data.
-
Extended NOP range
OP_NOP11–OP_NOP77(#317, F3.2). Opcodes0xba–0xfcare now defined as named constants so thatto_asm/from_asmcan round-trip scripts containing them. -
Canonical ASM aliases
"0"and"-1"(#317, F3.3).Script.from_asmnow accepts"0"as an alias forOP_0and"-1"as an alias forOP_1NEGATE, matching the ts-sdk'sfromASMbehaviour. -
Explicit PUSHDATA sequences in
from_asm(#317, F3.4). Token sequences of the formOP_PUSHDATA1 <len> <hex>,OP_PUSHDATA2 <len> <hex>, andOP_PUSHDATA4 <len> <hex>are now consumed as a unit byfrom_asm. -
OP_UNKNOWN<n>ASM rendering for unlisted opcodes (#317, F3.6).Chunk#to_asmnow emitsOP_UNKNOWN<n>(e.g.OP_UNKNOWN186) for opcodes with no defined constant, making the output unambiguous and round-trippable viafrom_asm. -
PushDropTemplate#locksupportslock_position:(#317, F3.12). Thelock_position:keyword argument (:beforedefault /:after) is now forwarded fromPushDropTemplate#locktoScript.pushdrop_lock.
-
encode_minimallyno longer collapses[0x00]toOP_0(#317, F3.14/F3.21).OP_0pushes an empty byte array; pushing a single zero byte[0x00]is semantically different. The ts-sdk has the same bug increateMinimallyEncodedScriptChunk— we fix it locally and plan to raise it upstream. -
Stack#pop_int/Stack#peek_intnow enforce minimal encoding by default (#318, F7.11).require_minimal:now defaults totrue, matching post-Genesis BSV consensus rules. Previously the default wasfalse, allowing non-minimally encoded script numbers to be silently accepted. Migration: callers that relied on decoding non-minimal encodings (e.g."\x80\x00\x01"for 256 instead of the minimal"\x00\x01") will now receive aScriptErrorwith code:minimal_data. Passrequire_minimal: falseexplicitly to restore the previous behaviour where that is intentional. -
Chronicle opcodes now raise
ScriptErrorCode::UNIMPLEMENTED_OPCODE(#318, F7.1/F7.2).OP_SUBSTR(0xb3),OP_LEFT(0xb4),OP_RIGHT(0xb5),OP_LSHIFTNUM(0xb6),OP_RSHIFTNUM(0xb7),OP_VER(0x62),OP_VERIF(0x65), andOP_VERNOTIF(0x66) previously executed silently as no-ops or reserved-opcode errors with a different code. Any script that executes one of these opcodes will now raiseScriptErrorwith code:unimplemented_opcoderather than succeeding silently. Full Chronicle string and numeric-shift semantics are planned for SDK v0.10. Migration: scripts relying on these opcodes producing no-op orRESERVED_OPCODEbehaviour must be updated. Chronicle opcode constants (OP_SUBSTR,OP_LEFT, etc.) are now defined inBSV::Script::Opcodes.
-
32 MB stack memory limit (#318, F7.18). The script execution stack now enforces a 32 MB aggregate memory cap, matching the ts-sdk. Every push (including those from
dup_n,over_n,pick_n, andtuck) checks the limit and raisesScriptErrorwith code:stack_memory_exceededif exceeded. This naturally bounds O(n²) opcodes such asOP_MULon large operands. -
CHECKMULTISIG post-Genesis key-count limit removed (#318, F7.8). The 20-key cap on
OP_CHECKMULTISIGhas been removed in line with post-Genesis BSV rules. The stack memory limit is now the practical bound. -
Conditional nesting depth cap (#318, F7.19).
OP_IF/OP_NOTIFnow raiseScriptErrorwith code:unbalanced_conditionalif the nesting depth exceeds 256, preventing interpreter stack overflow from deeply nested conditionals. -
Hybrid public key prefix rejection (#318, F7.10).
CHECKSIGandCHECKMULTISIGnow explicitly document that hybrid encoding prefix bytes 0x06 and 0x07 are rejected. Only compressed (0x02/0x03) and uncompressed (0x04) keys are valid. -
CHECKSIG / CHECKMULTISIG no-tx behaviour corrected (#318, F7.9). When no transaction context is provided (e.g.
Interpreter.evaluatewithout a tx),OP_CHECKSIGandOP_CHECKMULTISIGnow pushfalsefor non-empty signatures rather than raisingSIG_NULLFAIL. NULLFAIL only applies when a real verification failure occurs — without a tx there is nothing to verify. -
Transaction#estimated_feedefault rate aligned and deprecated (#310, F4.2). The default fee rate changed from 0.5 sat/byte (500 sat/kB) to 0.1 sat/byte (100 sat/kB), matching theSatoshisPerKilobyte.newdefault. The method now delegates throughSatoshisPerKilobyteinternally and emits a deprecation warning pointing consumers at the fee model API. Migration: replacetx.estimated_feewithSatoshisPerKilobyte.new.compute_fee(tx). If you relied on the 0.5 sat/byte default, passvalue: 500to the fee model. -
BSV::Primitives::Hexmodule (#310, F1.5). Strict hex decode/encode with validation — raisesArgumentErroron odd-length or non-hex input instead of silently truncating. All consumer-facingfrom_hexparse paths (Transaction,Beef,MerklePath,Script,PublicKey,Signature,Builder) now useHex.decodeand reject malformed hex loudly.Script.from_asmhex tokens are also validated — previously non-hex tokens were silently packed as garbled bytes. -
Pure-Ruby RIPEMD-160 (#310, F1.8). Replaces the
OpenSSL::Digest::RIPEMD160dependency with a pure-Ruby implementation (BSV::Primitives::Ripemd160), eliminating the portability failure on OpenSSL 3 builds that don't load the legacy provider. Same precedent as the pure-Ruby secp256k1 port (#253). Verified against all 9 official RIPEMD-160 spec vectors.
-
Cross-SDK conformance vector suite (#307). Canonical test vectors are now vendored under
spec/conformance/vectors/and executed as part of the default test run. The initial set covers BRC-42 key derivation (private + public),SymmetricKeyAES-256-GCM decryption, BIP-143 sighash, legacy sighash, the Bitcoin Corescript_tests.jsoncorpus, BUMP parse/round-trip, and three canonical BEEF fixtures (BRC-62, BRC-95 / V2 multi-tx, base64). Provenance (source SDK, source path, upstream commit SHA) is tracked inspec/conformance/vectors/README.md; the sync procedure lives atdocs/testing/conformance-vectors.md. Existing inline BRC-42 and BEEF vectors inspec/conformance/have been migrated to load from the vendored files, so future syncs are a plaindiffrather than a Ruby literal edit.Four vector families (
sighash_bip143.json,sighash_legacy.json,script_tests.json) are vendored but their execution is deferred: legacy sighash is not supported on BSV (kept for reference only); BIP-143 vectors require a non-FORKID sighash entry point that the Ruby SDK correctly rejects, so execution is deferred to the A2 cluster;script_tests.jsonis deferred to A5 (parser) and A6 (interpreter). Each deferred spec documents its gap explicitly.
Paired security patch release. Three P0 findings from the
2026-04-08 cross-SDK compliance review
plus follow-up hardening from the PR review pass. Must be installed
together — the bsv-wallet gemspec now pins its bsv-sdk dependency
to >= 0.8.2, < 1.0 to enforce the paired upgrade and prevent a stale
pair where one gem has its fixes and the other doesn't.
Two GitHub Security Advisories accompany this release (draft until CVE IDs return from MITRE):
-
VarInt.encodenow rejects negative integers and values above 2^64 − 1. PreviouslyVarInt.encode(-1)fell into the single- byte branch and emitted0xFF(the marker for a 9-byte encoding), silently corrupting the transaction stream with no exception raised. The docstring already required a non-negative integer; the implementation did not enforce it. Closes F1.3. -
ARC broadcaster recognises the full failure status set. The previous
REJECTED_STATUSEScontained onlyREJECTEDandDOUBLE_SPEND_ATTEMPTED; responses with txStatusINVALID,MALFORMED,MINED_IN_STALE_BLOCK, or anyORPHAN-containingtxStatus/extraInfowere silently treated as successful broadcasts. Callers relying onbroadcast()to signal failure would trust transactions that were never actually accepted by the network. The new failure set matches the TypeScript reference broadcaster exactly, and case-insensitive matching defends against ARC's documented history of emitting values outside its own OpenAPI enum (TS issue #105). Malformed 2xx responses without atxidfield also raise, closing the same silent-success class for shape corruption. Closes F5.13.
- ARC broadcaster HTTP wire format brought into line with the
TypeScript reference:
- Content-Type is now
application/json(wasapplication/octet-stream) - Body is
{"rawTx": hex}— Extended Format (BRC-30) hex when every input hassource_satoshis/source_locking_scriptpopulated (so ARC can validate sighashes without fetching parents), falling back to plain raw-tx hex otherwise - New
XDeployment-IDheader (default:bsv-ruby-sdk-<random hex>, overridable viadeployment_id:constructor kwarg) - New optional
X-CallbackUrlandX-CallbackTokenconstructor kwargs for ARC status callbacks
- Content-Type is now
lib/bsv/network/**/*added toMetrics/ClassLengthandMetrics/ParameterListsRuboCop exclusion lists to match the existing treatment oflib/bsv/wallet_interface/**/*. ARC is HTTP-client boilerplate in the same shape.- Review-feedback hardening bundled into the same PR to
keep the security-patch window small: case-insensitive ARC failure
matching,
Base64.strict_decode64on BRC-52 preimage fields,EncodingErrorrescue inCertificateSignature.verify!, rejection of mixed string / symbol duplicate field names, malformed 2xx rejection in ARC, and even-length guard on hex signatures.
- Existing
bsv-walletusers pinned tobsv-sdk ~> 0.4will need to relax their constraint or upgrade. Anything installed beforebsv-wallet 0.3.4is vulnerable to the F8.15 certificate forgery primitive. - Callers passing negative integers to
VarInt.encode(unlikely — the docstring already disallowed it) will now get anArgumentErrorinstead of silent corruption. Fix: pass non-negative values. - Callers relying on ARC broadcaster silently succeeding for INVALID
/ MALFORMED / MINED_IN_STALE_BLOCK / ORPHAN responses will now see
BroadcastErrorraised. Fix: handle the error — the previous behaviour was objectively wrong and any downstream logic that tolerated it was silently corrupt. - Callers of
acquire_certificatewith a fake or untrustedsignature:field will now seeBSV::Wallet::CertificateSignature::InvalidError. Fix: ensure the certificate has been properly signed by the declared certifier.
- 3112 examples, 0 failures (up from 3080 on 0.8.1)
- 16 new regression tests for F1.3, F5.13, and F8.15
- 16 further regression tests for the review-feedback hardening
- Ruby 2.7 — 3.4 matrix green
- CodeQL clean; RuboCop clean across 266 files
Transaction#to_beefstrips phantomtxid: trueleaves — when a proof loaded from a sharedLocalProofStorecarries txid flags for transactions that are not part of the bundle being constructed,to_beefnow rebuilds each per-block BUMP from only the bundle's own txids instead of propagating the phantoms into the serialised output. ARC previously rejected such BEEFs with misleading parser errors, blocking any wallet workflow that received a BEEF viainternalize_actionand then spent the internalised UTXOs. Closes #302.
MerklePath#extract(txid_hashes)— returns a new trimmed compound path covering only the requested txids, reconstructing the minimum set of sibling hashes at each tree level. RaisesArgumentErroron empty input, unknown txid, or root mismatch. Ported from the TypeScript SDK. Used internally byTransaction#to_beefand available for direct use.MerklePath#trim— removes internal nodes not required by level-zero txid leaves. Called implicitly by#combineand#extractand rarely needs to be invoked directly. Ported from the TypeScript SDK.MerklePath#initialize_copy—.dupnow produces a new MerklePath whose outer and level arrays are independent of the source, so the copy can be freely mutated via#combine,#trim, or#extractwithout affecting the original.PathElements remain immutable and are shared between source and copy.
MerklePath#combinenow calls#trimat the end so merged paths stay minimal across repeated merges, matching the TypeScript SDK. Combined paths are strictly smaller than before — external callers that inspectedmp.pathafter#combinemay see fewer nodes, though every txid leaf's merkle proof is preserved.MerklePath#combinealso preservestxid: trueflags when the incoming leaf is flagged and the existing leaf at the same offset isn't, so merging an ancestor's single-leaf proof into a compound that already contains the same offset as a sibling no longer loses the txid flag.Transaction#to_beefnow raisesArgumentErrorif an ancestor's merkle path doesn't actually contain that transaction's txid, or if the rebuilt BUMP's root doesn't match the source root. Previously such corrupt proof data would silently emit a broken BEEF. Callers relying onto_beefnot raising on valid data are unaffected; the new exception only triggers on corrupt proof stores.
Beef#merge_transactionindirectly benefits from the tighter#combine+#trimbehaviour: compound BUMPs no longer accumulate dead sibling hashes across repeated merges.- On the real-world
#302regression fixture, the cleaned BUMP shrinks from 2476 B to 1300 B (47% reduction) as a side effect of#extractremoving intermediate siblings that are no longer needed once phantom leaves are gone.
MerklePath.from_tsc— convert WhatsOnChain TSC merkle proofs (the flat leaf-to-root sibling list returned by/tx/{txid}/proof/tsc) into BRC-74 BUMP format. Verified end-to-end against a real mainnet vector (block 612251). Closes #280.Beef#version=writer — promoted from internalinstance_variable_setto a proper accessor.
Beef#to_binaryrewrite — serialises BUMPs from the canonical@bumpsarray and usesbeef_tx.bump_indexas the on-wire reference, instead of walking each transaction'smerkle_pathvia object identity. Fixes duplicate-BUMP serialisation for same-block ancestors that previously caused ARC468 BEEF invalidrejections. Matches the TS and Go reference SDKs. Closes #288.Beef#to_hexpreserves the bundle's@versionso a BEEF parsed from V2 round-trips to V2 hex (and V1 to V1) instead of always silently downgrading to V1. The original docstring already claimed "V2 hex string" — this fix matches the original intent. Closes #292.Beef#initializedefaultversion:parameter changed fromBEEF_V2toBEEF_V1to matchto_binary's default. Every existingBeef.new + to_hexcaller continues to emit V1 (preserving every existing observable behaviour). Closes #292.bsv-sdkgem packaging — explicit module list inbsv-sdk.gemspecexcludesbsv-attestandbsv-walletcode. Reducesbsv-sdkfrom 144 files to 98 (24% smaller); no overlap with the sibling gems exceptLICENSE.Transaction#to_beefdocstring corrected from "BEEF V2 binary bundle" to "BEEF V1 binary bundle (BRC-62)" to match what the method actually emits.
Beef#to_binaryraisesArgumentErrorupfront when V1 (BRC-62) is requested for a bundle containingFORMAT_TXID_ONLYentries, instead of crashing deep insidewrite_v1_txwithNoMethodError. V2 (BRC-96) supports TXID-only and is unaffected. The error message points the caller atversion: BEEF_V2. Closes #290.Beef#mergeraisesArgumentErroron inconsistentbump_indexfrom the source bundle (when the source has a transaction pointing at abump_indexthat doesn't exist in the source's@bumps), instead of silently propagating a stale index that could attach the wrong merkle path to a transaction in the merged bundle. Closes #291.Beef::BeefTx#initializevalidates thatFORMAT_RAW_TX_AND_BUMPrequires a non-nilbump_index, failing fast in the constructor instead of crashing later inVarInt.encode(nil).Beef#merge_raw_txbounds-checks thebump_indexparameter and raisesArgumentErrorif out of range, instead of silently writing an invalid index that downstream parsers would misinterpret.
- CI is now green: 73 pre-existing RuboCop offenses across
spec/conformance/openssl_shim_compliance/resolved. Closes #293. - OpenSSL EC shim conformance suite is now skipped on Ruby 2.7,
where stock
OpenSSL::PKey::EC::Point#addis unavailable. The shim itself still has direct unit-test coverage on every supported Ruby.
Beef#to_binarynow defaults to BEEF V1 (BRC-62) format, matching the TS reference SDK'sTransaction#toBEEF(). ARC's parser does not support V2. Passversion: BEEF_V2for BRC-96 format. Atomic BEEF (BRC-95) inner envelope remains V2 per spec.
The sdk gem was re-released alongside this wallet change with no behavioural changes of its own.
- Use internal byte order for Atomic BEEF subject txid lookup, fixing serialisation of transactions loaded from Atomic BEEF format.
- Pure Ruby secp256k1 — native Ruby implementation of secp256k1
elliptic curve operations, ported from the TypeScript reference SDK.
Replaces OpenSSL's EC point arithmetic with an OpenSSL compatibility shim
— zero consumer code changes required. See
docs/about/secp256k1.md.
- Field arithmetic (modular multiplication, inversion, square root) over the secp256k1 prime.
- Jacobian coordinate point operations (addition, doubling, scalar multiplication).
- Windowed-NAF (w=5) scalar multiplication with precomputed table caching.
- SEC 1 point serialisation (compressed and uncompressed).
- 126 byte-for-byte compliance specs against real OpenSSL.
- 24 process-isolated integration tests (separate Ruby processes, MD5 file comparison).
- Registry client —
BSV::Registrymodule for on-chain definition management.Client— register, resolve, list, revoke, and update definitions for protocols, baskets, and certificate types via PushDrop tokens on the overlay network.- Per-type overlay topics (
tm_basketmap,tm_protomap,tm_certmap) and lookup services matching TS and Go SDKs. - Types:
BasketDefinitionData,ProtocolDefinitionData,CertificateDefinitionData,CertificateFieldDescriptor,RegisteredDefinition. - Ownership verification before revocation. BEEF Array/String normalisation for wire format compatibility.
- OpenSSL usage reduced — OpenSSL now used only for hashing (SHA/RIPEMD), HMAC, PBKDF2, AES, and constant-time comparison. Elliptic curve operations are pure Ruby.
- SHIP/SLAP overlay services —
BSV::Overlaymodule for topic-based transaction broadcasting and service discovery.TopicBroadcaster(aliased asSHIPBroadcaster) — broadcasts tagged BEEF to topic-interested hosts with configurable acknowledgement modes (all/any/specific hosts) and STEAK response parsing.LookupResolver— discovers competent hosts via SLAP trackers, queries in parallel, aggregates and deduplicates results. TTL-based host caching.HostReputationTracker— EWMA latency scoring with exponential backoff, DNS error escalation, thread-safe. Optional persistence via injectable store adapter.AdminTokenTemplate— decode/lock/unlock for SHIP/SLAP advertisement PushDrop tokens with BRC-42 wallet key derivation.- Abstract base classes (
LookupFacilitator,BroadcastFacilitator) with default HTTPS implementations — all dependencies injectable via constructor. - SSRF protection for SLAP-discovered domains (private/loopback IP rejection).
- Identity client —
BSV::Identitymodule for certificate-based identity resolution and publication.Client— resolve identities by key or attributes, publicly reveal certificate fields on-chain, revoke revelations. All overlay dependencies injectable.IdentityParser— converts identity certificates toDisplayableIdentity, handling all 9 known types (xCert, discordCert, phoneCert, emailCert, identiCert, registrant, coolCert, anyone, self) plus generic field-name heuristic fallback.- Types:
DisplayableIdentity,IdentityCertificate,CertifierInfo,ClientOptionswith cross-SDK constant alignment. - Certificate verifier injectable with safe-by-default (raises
NotImplementedError).
- PushDropTemplate — reusable wallet-aware PushDrop template with BRC-42 key derivation, optional ECDSA field signing, and P2PKH lock/unlock. Used by Identity client, reusable for ContactsManager and other PushDrop-based features.
ProtoWalletparameter name mismatch:_originator:→originator:to match theWalletInterfacecontract.
- Bitcore ECIES —
ECIES.bitcore_encrypt/ECIES.bitcore_decrypt. AES-256-CBC with random IV, SHA-512(X-coordinate) key derivation. Matches ts-sdk and go-sdk Bitcore variants.
LivePolicy.default— one-line convenience for live fee queries via GorillaPool ARC with 5-minute cache and 100 sat/kB fallback. (The underlyingLivePolicyfee model itself shipped in sdk-0.3.2.)
- Default fee rate:
SatoshisPerKilobytedefault changed from 50 to 100 sat/kB (matches ts-sdk LivePolicy fallback).
- OP_CAT template — OP_CAT concatenation script template with lock/unlock constructors.
- LivePolicy fee model — fetches policy from ARC
/v1/policyendpoint. (The convenience constructorLivePolicy.defaultwas added in sdk-0.4.0.)
- PUSHDATA1/2/4 bounds check (silent data corruption on truncated scripts).
- Extended key path validation (reject non-numeric indices).
This was the first formal bsv-wallet gem release tag. Wallet code that
landed in master before this date (notably the BRC-100 identity certificate
methods and the BRC-100 blockchain-data / authentication methods committed
during the sdk-0.3.1 window) is part of this gem's initial released state.
ARC#broadcastwait_for:parameter — sets theX-WaitForheader (RECEIVED, STORED, ANNOUNCED_TO_NETWORK, SEEN_ON_NETWORK, MINED) so callers can choose how long ARC blocks before responding.
- SymmetricKey — AES-256-GCM encryption/decryption with 32-byte IV (cross-SDK compatible). Construct from random, ECDH, or raw bytes.
- BRC-77 SignedMessage — authenticated message signing and verification using BRC-42 derived keys. Supports targeted (specific verifier) and "anyone" modes.
- BRC-78 EncryptedMessage — end-to-end encrypted messaging using ECDH-derived symmetric keys.
- Schnorr ZKP (BRC-94) — zero-knowledge proof of ECDH shared
secret knowledge.
Schnorr.generate_proof/Schnorr.verify_proof. - Shamir's Secret Sharing — split private keys into threshold
shares (
PrivateKey#to_key_shares) with Lagrange interpolation reconstruction. Backup format with integrity check.
- PushDrop template — data carrier with P2PK spending.
Script.pushdrop_lock/Script.pushdrop_unlockwith field extraction. - RPuzzle template — R-puzzle hash-based spending with 6 hash type variants (raw, SHA1, SHA256, RIPEMD160, HASH160, HASH256).
- Benford's law change distribution — privacy-preserving change output splitting using Benford's first-digit distribution.
- Empty plaintext/ciphertext handling on older OpenSSL versions.
- PushDrop detection for minimally-encoded fields.
Transaction#feechange distribution uses Benford's law (was equal split).LineLengthraised to 150.
- Truncated
OP_PUSHDATA1/2/4scripts now raiseArgumentErrorinstead of crashing withTypeError. Transaction#to_beefusesmerge_bumpto correctly handle multiple ancestors at the same block height.PrivateKey#derive_childusesBN.mod_addinstead of Integer roundtrip for modular addition.- Fixed txid byte-order documentation (display order, not internal order).
- FORKID enforcement spec verifying interpreter rejects signatures without SIGHASH_FORKID.
- ExtendedKey fingerprint chain integrity across 3-generation derivation.
- Mnemonic entropy round-trip across all 5 valid entropy lengths.
- BEEF spec for multiple ancestors at the same block height.
- ECDH shared secret derivation
(
PrivateKey#derive_shared_secret,PublicKey#derive_shared_secret). - BRC-42 key derivation (
PrivateKey#derive_child,PublicKey#derive_child) with official spec test vectors.
- Chain tracker interface (
ChainTrackerbase class) with WhatsOnChain implementation. - Fee model interface (
FeeModelbase class) withSatoshisPerKilobyteimplementation. Transaction#feewith change output distribution across multiple change outputs.Transaction#verifyfor full SPV verification (merkle path, script execution, recursive ancestry).TransactionOutput#changeflag for identifying change outputs.MerklePath#verifyfor SPV chain tracker integration.- BEEF completion:
Beef#merge,Beef#valid?, lookup methods (find_bump,find_transaction_for_signing). Transaction#to_beef/Transaction.from_beefconvenience methods.- Extended Format (EF) transaction serialisation (
to_ef,to_ef_hex,from_ef,from_ef_hex). VerificationErrorwith typed error codes for SPV verification failures.
- ECIES refactored to use
PrivateKey#derive_shared_secretinternally (no API change). Transaction#estimated_sizemade public for fee model access.
- Nil
source_satoshisnow raises instead of silently coercing to zero in fee distribution and verification. - Script chunk round-trips preserve original push encoding.
OP_RETURNinside conditionals correctly checked for conditional balance.- Point x-coordinate extraction preserves leading zeros via octet string.
Integer#nobits?replaced with Ruby 2.7-compatible bitwise check.- Defensive parsing with descriptive errors for truncated binary input.
- BRC-42 conformance specs with 9 official specification test vectors.
- ECDH conformance specs (commutativity, cross-method, pinned known-key vector).
- SPV verification conformance specs (merkle path, script execution, ancestry).
- Fee model conformance specs (formula validation, default rate, change distribution).
- Chain tracker conformance specs.
- BEEF cross-SDK conformance vectors.
- Schnorr (BRC-94) cross-SDK interoperability vectors.
- 6 exact-match RFC 6979 vectors from Trezor/CoreBitcoin.
- VarInt boundary tests at size-prefix transitions.
- Script vectors converted to tracked known-failures system.
Initial release of the BSV Ruby SDK.
- secp256k1 elliptic curve operations (point arithmetic, scalar multiplication).
- ECDSA signing and verification with RFC 6979 deterministic nonces.
- Public and private key handling (WIF import/export, compressed/uncompressed formats).
- Base58Check encoding and decoding.
- Hash functions: SHA-256, RIPEMD-160, Hash160 (SHA-256 + RIPEMD-160), SHA-512, HMAC.
- BIP-32 hierarchical deterministic key derivation (extended keys, hardened/normal child paths).
- BIP-39 mnemonic phrase generation and seed derivation.
- ECIES encryption and decryption (BIE1 format).
- Bitcoin Signed Message (BSM) signing and verification.
- Opcode constants (full set).
- Script chunk representation and parsing.
- Script serialisation and deserialisation.
- Script templates: P2PKH, P2PK, P2MS (multisig), OP_RETURN data.
- Script type detection (including read-only recognition of P2SH and other legacy types).
- Script builder API for programmatic construction.
- Script interpreter with stack operations, arithmetic, crypto, flow control, splice, and bitwise ops.
- Transaction construction and serialisation (raw format).
- BIP-143 sighash computation (all hash types with FORKID).
- Transaction signing with configurable sighash flags.
- BEEF serialisation (BRC-95/BRC-96).
- Merkle path representation and verification.
- Fee estimation.
- Script verification during signing.
- Unlocking script templates for common script types.
- ARC broadcaster for transaction submission.
- WhatsOnChain chain data provider.
- Basic wallet functionality.
- Cross-SDK test vectors from Go, TypeScript, and Python reference implementations.
- NIST and RFC hash function test vectors.
- Bitcoin Core script interpreter test suite.
- Protocol conformance specs for opcodes, sighash flags, and transaction templates.