Class BSV::Transaction::Tx ¶
Inherits: Object
A Bitcoin transaction: a collection of inputs consuming previous outputs and producing new outputs.
Supports construction, binary/hex serialisation, BIP-143 sighash computation (with FORKID), signing, script verification, and fee estimation.
@example Build, sign, and serialise a transaction
tx = BSV::Transaction::Tx.new
tx.add_input(input)
tx.add_output(output)
tx.sign(0, private_key)
tx.to_hex #=> "0100000001..."
Constants¶
LOG10_RECIPROCAL_D_VALUES_1TO9 ¶
Lookup table for benford_number calculation. we want float values for log10(1 + (1.0 / i)) for the 9 integers 0 < i < 10 in ruby this becomes: (1..9).to_a.collect{|d| Math.log10(1 + (1.0 / d)) }
UNSIGNED_P2PKH_INPUT_SIZE ¶
Estimated size of an unsigned P2PKH input in bytes.
ZERO_HASH ¶
Not documented.
Attributes¶
inputs [R] ¶
- @return [Array
] transaction inputs
lock_time [R] ¶
- @return [Integer] lock time (block height or Unix timestamp)
merkle_path [RW] ¶
- @return [MerklePath, nil] BRC-74 merkle path (for BEEF serialisation)
outputs [R] ¶
- @return [Array
] transaction outputs
version [R] ¶
- @return [Integer] transaction version number
Public Class Methods¶
from_beef(data) ¶
Parse a BEEF binary bundle and return the subject transaction with full ancestry wired, including late-bound BUMP attachment.
For Atomic BEEFs (BRC-95), the subject transaction is identified by the embedded subject_wtxid field. For plain BEEFs, the last transaction with a raw tx entry is used as the subject.
Uses find_atomic_transaction so that FORMAT_RAW_TX ancestors whose wtxid appears as a leaf in a separately-stored BUMP get their merkle_path wired correctly — a gap not covered by the initial wire_source_transactions pass in Beef.from_binary. - @param data [String] raw BEEF binary - @return [Transaction::Tx, nil] the subject transaction with ancestry wired, or nil if the BEEF is empty or contains no raw transaction entries
from_beef_hex(hex) ¶
Parse a BEEF hex string and return the subject transaction. - @param hex [String] hex-encoded BEEF - @return [Transaction::Tx, nil] the subject transaction with ancestry wired, or nil if the BEEF is empty or contains no raw transaction entries
from_binary(data) ¶
Deserialise a transaction from binary data. - @param data [String] raw binary transaction - @raise [ArgumentError] - @return [Transaction::Tx] the parsed transaction
from_binary_with_offset(data, offset = 0) ¶
Deserialise a transaction from binary data at a given offset, returning the transaction and the number of bytes consumed. - @param data [String] binary data containing the transaction - @param offset [Integer] byte offset to start reading from - @return [Array(Transaction::Tx, Integer)] the transaction and bytes consumed
from_ef(data) ¶
Deserialise a transaction from Extended Format (BRC-30) binary data. - @param data [String] raw EF binary - @raise [ArgumentError] if the EF marker is invalid - @return [Transaction::Tx] the parsed transaction with source data on inputs
from_ef_hex(hex) ¶
Deserialise a transaction from an Extended Format hex string. - @param hex [String] hex-encoded EF transaction - @return [Transaction::Tx] the parsed transaction with source data on inputs
from_hex(hex) ¶
Deserialise a transaction from a hex string. - @param hex [String] hex-encoded transaction - @return [Transaction::Tx] the parsed transaction
Public Instance Methods¶
add_input(input) ¶
Append a transaction input. - @param input [Transaction::TransactionInput] the input to add - @return [self] for chaining
add_output(output) ¶
Append a transaction output. - @param output [Transaction::TransactionOutput] the output to add - @return [self] for chaining
estimated_fee(satoshis_per_byte: = 0.1) ¶
Estimate the mining fee based on the estimated transaction size. - @deprecated Use {FeeModels::SatoshisPerKilobyte#compute_fee} instead. This method delegates through +SatoshisPerKilobyte+ internally and will be removed in 1.0. - @param satoshis_per_byte [Numeric] fee rate (default: 0.1 sat/byte = 100 sat/kB, matching the +SatoshisPerKilobyte+ default) - @return [Integer] estimated fee in satoshis (rounded up)
estimated_size() ¶
Estimate the serialised transaction size in bytes.
Uses actual unlocking script size for signed inputs and template estimated length for unsigned inputs. - @return [Integer] estimated size in bytes
fee(model_or_fee = nil, change_distribution: = :equal) ¶
Compute the fee and distribute change across change outputs.
Accepts a {FeeModel} instance, a numeric fee in satoshis, or nil (defaults to {FeeModels::SatoshisPerKilobyte} at 50 sat/kB).
After computing the fee, distributes remaining satoshis across outputs marked as change. The distribution strategy is controlled by the change_distribution: keyword argument:
:equal(default) — divides change equally across all change outputs, matching TS SDK default behaviour.:random— Benford-inspired distribution that biases amounts towards the lower end of the available range, improving privacy by producing varied change amounts.
If insufficient change remains, all change outputs are removed. - @param model_or_fee [FeeModel, Integer, nil] fee model, fixed fee, or nil for default - @param change_distribution [Symbol] +:equal+ or +:random+ (default: +:equal+) - @raise [ArgumentError] if +change_distribution+ is not +:random+ or +:equal+ - @return [self] for chaining
initialize(version: = 1, lock_time: = 0) ¶
- @param
version[Integer] transaction version (default: 1) - @param
lock_time[Integer] lock time (default: 0) - @return [Tx] a new instance of Tx
sighash(input_index, sighash_type = Sighash::ALL_FORK_ID, subscript: = nil) ¶
Compute the BIP-143 sighash digest for an input (double-SHA-256 of the preimage). - @param input_index [Integer] the input to compute the sighash for - @param sighash_type [Integer] sighash flags (default: ALL|FORKID) - @param subscript [Script::Script, nil] override locking script for the input - @return [String] 32-byte sighash digest
sighash_preimage(input_index, sighash_type = Sighash::ALL_FORK_ID, subscript: = nil) ¶
Build the BIP-143 sighash preimage for an input.
Only SIGHASH_FORKID types are supported (BSV requirement). - @param input_index [Integer] the input to compute the preimage for - @param sighash_type [Integer] sighash flags (default: ALL|FORKID) - @param subscript [Script::Script, nil] override locking script for the input - @raise [ArgumentError] if sighash_type does not include FORKID - @return [String] the raw preimage bytes (hash this to get the sighash)
sign(input_index, private_key, sighash_type = Sighash::ALL_FORK_ID) ¶
Sign a single input with a private key (P2PKH).
Computes the sighash, signs it, and sets the unlocking script on the input. - @param input_index [Integer] the input to sign - @param private_key [Primitives::PrivateKey] the signing key - @param sighash_type [Integer] sighash flags (default: ALL|FORKID) - @return [self] for chaining
sign_all(private_key = nil, sighash_type = Sighash::ALL_FORK_ID) ¶
Sign all unsigned inputs.
For each input without an unlocking script: if the input has an {UnlockingScriptTemplate}, delegates to it; otherwise falls back to P2PKH signing with the given private key. - @param private_key [Primitives::PrivateKey, nil] fallback signing key - @param sighash_type [Integer] sighash flags (default: ALL|FORKID) - @return [self] for chaining
to_beef() ¶
Serialise this transaction (with its ancestry chain and merkle proofs) into a BEEF V1 binary bundle (BRC-62), the default format for ARC and the reference TS SDK.
Walks the source_transaction references on inputs to collect ancestors. Transactions with a merkle_path are treated as proven leaves — their ancestors are not traversed further.
Proven ancestors that share a block are combined into a single BUMP per block, then trimmed via {MerklePath#extract} so the serialised bundle carries only the +txid: true+-flagged leaves that correspond to transactions in this BEEF. This prevents "phantom" txid leaves carried over from a shared {LocalProofStore} entry (issue #302) and also shrinks the BEEF by dropping intermediate sibling hashes that are no longer needed.
Ancestor merkle_path objects are not mutated: paths are deep-copied before any combine/trim work. - @raise [ArgumentError] if an ancestor's merkle_path does not actually contain that transaction's txid, or if the cleaned BUMP's root does not match the source root (both indicate corrupt proof data) - @return [String] raw BEEF V1 binary
to_beef_hex() ¶
Serialise this transaction to a BEEF V2 hex string. - @return [String] hex-encoded BEEF
to_binary() ¶
Serialise the transaction to its binary wire format. - @return [String] raw transaction bytes
to_ef() ¶
Serialise the transaction in Extended Format (BRC-30).
EF embeds source satoshis and source locking scripts in each input, allowing ARC to validate sighashes without fetching parent transactions.
Source data is resolved in priority order: 1. Explicit source_satoshis / source_locking_script on the input. 2. Derived from input.source_transaction.outputs[input.prev_tx_out_index].
Source fields on input objects are never mutated — derivation happens on each call, so calling to_ef twice produces identical output. - @raise [ArgumentError] if any input cannot supply source_satoshis or source_locking_script via either mechanism - @return [String] raw EF transaction bytes
to_ef_hex() ¶
Serialise the transaction in Extended Format as a hex string. - @return [String] hex-encoded EF transaction
to_hex() ¶
Serialise the transaction to a hex string. - @return [String] hex-encoded transaction
total_input_satoshis() ¶
Sum of all input source satoshi values. - @return [Integer] total input value in satoshis
total_output_satoshis() ¶
Sum of all output satoshi values. - @return [Integer] total output value in satoshis
txid_hex() ¶
The transaction ID as a hex string (display byte order). - @return [String] 64-char hex-encoded transaction ID (display order)
verify(chain_tracker:, fee_model: = nil) ¶
Perform full SPV verification of this transaction and its ancestry.
Uses a queue-based approach (matching TS/Go SDKs) to walk the transaction ancestry chain:
- If a transaction has a merkle path that validates against the chain tracker, it is marked verified (inputs are not re-checked).
- Otherwise, each input's scripts are executed via the interpreter, and source transactions are enqueued for verification.
- Optionally validates that the root transaction's fee meets the provided fee model.
- Checks that total outputs do not exceed total inputs.
Semantic Divergences from Reference SDKs¶
This implementation raises VerificationError for all failure modes. The TypeScript and Python SDKs return false for script failures and output overflow. The Go SDK propagates errors (not booleans) for script failures, aligning with the Ruby approach.
Rationale: raising provides structured error information (#code, #message, #cause) that a boolean cannot convey. Consumers can rescue VerificationError and inspect #code for specifics.
Divergence summary: * :output_overflow — Ruby raises; TS/Python return false; Go omits the check * :script_failure — Ruby raises; TS/Python return false; Go also propagates errors * :missing_source — Ruby raises; consistent with TS/Go/Python (all raise/error) - @param chain_tracker [Transaction::ChainTracker] chain tracker for merkle root validation - @param fee_model [FeeModel, nil] optional fee model to validate the root transaction's fee - @raise [VerificationError] with code +:invalid_merkle_proof+ if a merkle proof is invalid - @raise [VerificationError] with code +:insufficient_fee+ if the fee is below the model's threshold - @raise [VerificationError] with code +:output_overflow+ if outputs exceed inputs - @raise [VerificationError] with code +:script_failure+ if script execution fails - @raise [VerificationError] with code +:missing_source+ if an input is missing required source data - @return [true] on successful verification
verify_input(index) ¶
Verify the scripts of a single input using the interpreter. - @param index [Integer] the input index to verify - @return [Boolean] true if the scripts evaluate successfully
wtxid() ¶
Wire-order transaction ID (raw SHA-256d of the serialised tx).
Used by BEEF, BUMPs, and merkle paths, which all work in wire byte order to match {TransactionInput#prev_wtxid}. - @return [String] 32-byte transaction ID in wire byte order