# Class BSV::Transaction::Tx <a id="class-BSV-Transaction-Tx"></a>

**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.

- **@note** Not thread-safe. Direct mutation of {#inputs} / {#outputs} arrays
(e.g. `tx.inputs << input`) bypasses the cache-invalidation contract
and may produce silently invalid sighashes. Use {#add_input} /
{#add_output} and the documented setter surface, or call
{#invalidate_caches} after direct mutation. See
{file:docs/reference/sighash-cache.md}.

**@example Build, sign, and serialise a transaction**
```ruby
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` <a id="constant-LOG10_RECIPROCAL_D_VALUES_1TO9"></a> <a id="LOG10_RECIPROCAL_D_VALUES_1TO9-constant"></a>
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` <a id="constant-UNSIGNED_P2PKH_INPUT_SIZE"></a> <a id="UNSIGNED_P2PKH_INPUT_SIZE-constant"></a>
Estimated size of an unsigned P2PKH input in bytes.

### `ZERO_HASH` <a id="constant-ZERO_HASH"></a> <a id="ZERO_HASH-constant"></a>
rubocop:disable Style/RedundantFreeze

## Attributes
### `inputs` [R] <a id="attribute-i-inputs"></a> <a id="inputs-instance_method"></a>
- **@return** [Array<TransactionInput>] transaction inputs

### `lock_time` [R] <a id="attribute-i-lock_time"></a> <a id="lock_time-instance_method"></a>
- **@return** [Integer] lock time (block height or Unix timestamp)

### `merkle_path` [RW] <a id="attribute-i-merkle_path"></a> <a id="merkle_path-instance_method"></a>
- **@return** [MerklePath, nil] BRC-74 merkle path (for BEEF serialisation)

### `outputs` [R] <a id="attribute-i-outputs"></a> <a id="outputs-instance_method"></a>
- **@return** [Array<TransactionOutput>] transaction outputs

### `version` [R] <a id="attribute-i-version"></a> <a id="version-instance_method"></a>
- **@return** [Integer] transaction version number

## Public Class Methods
### `from_beef(data)` <a id="method-c-from_beef"></a> <a id="from_beef-class_method"></a>
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 <code>Beef.from_binary</code>.
- **@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)` <a id="method-c-from_beef_hex"></a> <a id="from_beef_hex-class_method"></a>
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)` <a id="method-c-from_binary"></a> <a id="from_binary-class_method"></a>
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)` <a id="method-c-from_binary_with_offset"></a> <a id="from_binary_with_offset-class_method"></a>
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)` <a id="method-c-from_ef"></a> <a id="from_ef-class_method"></a>
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)` <a id="method-c-from_ef_hex"></a> <a id="from_ef_hex-class_method"></a>
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)` <a id="method-c-from_hex"></a> <a id="from_hex-class_method"></a>
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)` <a id="method-i-add_input"></a> <a id="add_input-instance_method"></a>
Append a transaction input.

Idempotent on re-add: calling +add_input(x)+ twice with the same input on the
same <code>Transaction::Tx</code> returns `self` on the second call without
appending a duplicate or invalidating caches. Raises `ArgumentError` if the
input is already attached to a different <code>Transaction::Tx</code>.
- **@param** `input` [Transaction::TransactionInput] the input to add
- **@raise** [ArgumentError] if the input is already attached to a different
Tx instance. Sharing across Tx instances is anti-idiomatic;
construct a fresh instance per Tx. See
{file:docs/reference/sighash-cache.md#one-owner}.
- **@return** [self] for chaining

### `add_output(output)` <a id="method-i-add_output"></a> <a id="add_output-instance_method"></a>
Append a transaction output.

Idempotent on re-add: calling +add_output(x)+ twice with the same output on
the same <code>Transaction::Tx</code> returns `self` on the second call
without appending a duplicate or invalidating caches. Raises `ArgumentError`
if the output is already attached to a different <code>Transaction::Tx</code>.
- **@param** `output` [Transaction::TransactionOutput] the output to add
- **@raise** [ArgumentError] if the output is already attached to a different
Tx instance. Sharing across Tx instances is anti-idiomatic;
construct a fresh instance per Tx. See
{file:docs/reference/sighash-cache.md#one-owner}.
- **@return** [self] for chaining

### `estimated_fee(satoshis_per_byte: = 0.1)` <a id="method-i-estimated_fee"></a> <a id="estimated_fee-instance_method"></a>
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()` <a id="method-i-estimated_size"></a> <a id="estimated_size-instance_method"></a>
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)` <a id="method-i-fee"></a> <a id="fee-instance_method"></a>
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
<code>change_distribution:</code> keyword argument:

*   <code>:equal</code> (default) — divides change equally across all change
    outputs, matching TS SDK default behaviour.
*   <code>:random</code> — 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)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
- **@param** `version` [Integer] transaction version (default: 1)
- **@param** `lock_time` [Integer] lock time (default: 0)
- **@return** [Tx] a new instance of Tx

### `initialize_copy(other)` <a id="method-i-initialize_copy"></a> <a id="initialize_copy-instance_method"></a>
Called by <code>#dup</code> and <code>#clone</code>. Deep-dups +@inputs+ and
+@outputs+ so that the copy and the original do not share mutable input/output
state. Rebinds +@owning_tx+ on each copied struct to `self` (the new
transaction). Resets all cache ivars directly so the dup does not share
mutable cache state (especially +@hash_outputs_single+, a Hash) with the
original.

This closes the hazard at +beef.rb:703,707+ where a shallow dup would leave
the copied inputs/outputs pointing at the original transaction's cache.

Note: we cannot delegate to `invalidate_caches` here because that method calls
+Hash#clear+ on +@hash_outputs_single+, which would mutate the shared Hash and
evict the **original**'s per-index cache too. Direct +ivar = nil+ assignments
on `self` leave the original untouched.

### `invalidate_caches()` <a id="method-i-invalidate_caches"></a> <a id="invalidate_caches-instance_method"></a>
Invalidate all cached state on this transaction (sighash components, wire
serialisation, etc.) AND restore the +@owning_tx+ backref on every current
input and output. This is what makes the escape hatch complete: if a caller
appended an input/output via direct array mutation (bypassing `add_input` /
`add_output`), the new struct's backref is nil and future
<code>sequence=</code> / <code>locking_script=</code> / etc. setters would not
bubble invalidation up. After `invalidate_caches`, the backref is rebound so
subsequent setter mutations flow through correctly.

Raises `ArgumentError` if any input or output is already attached to a
different <code>Transaction::Tx</code> — matches the cross-Tx rebind contract
of `add_input` / `add_output`.
- **@note** Rarely needed. Normal mutation through the documented setter
surface (input.sequence=, output.satoshis=, etc.) invalidates the
right cache slices automatically. This method is an escape hatch
for code that mutates @inputs / @outputs through unsupported paths.
See {file:docs/reference/sighash-cache.md#escape-hatch}.
- **@raise** [ArgumentError] if any input or output is attached to a different
Tx instance
- **@return** [self] for chaining

**@example After mutating the inputs array directly**
```ruby
tx.inputs << input             # bypasses the documented setter surface
tx.invalidate_caches           # clears all cache layers + rebinds @owning_tx
input.sequence = 42            # now invalidates correctly
tx.verify(...)                 # safe
```

### `sighash(input_index, sighash_type = Sighash::ALL_FORK_ID, subscript: = nil)` <a id="method-i-sighash"></a> <a id="sighash-instance_method"></a>
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)` <a id="method-i-sighash_preimage"></a> <a id="sighash_preimage-instance_method"></a>
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)` <a id="method-i-sign"></a> <a id="sign-instance_method"></a>
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)` <a id="method-i-sign_all"></a> <a id="sign_all-instance_method"></a>
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()` <a id="method-i-to_beef"></a> <a id="to_beef-instance_method"></a>
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()` <a id="method-i-to_beef_hex"></a> <a id="to_beef_hex-instance_method"></a>
Serialise this transaction to a BEEF V2 hex string.
- **@return** [String] hex-encoded BEEF

### `to_binary()` <a id="method-i-to_binary"></a> <a id="to_binary-instance_method"></a>
Serialise the transaction to its binary wire format.
- **@note** Memoised; see {file:docs/reference/sighash-cache.md} for the invalidation contract.
- **@return** [String] raw transaction bytes (binary encoding, frozen)

### `to_ef()` <a id="method-i-to_ef"></a> <a id="to_ef-instance_method"></a>
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
    <code>input.source_transaction.outputs[input.prev_tx_out_index]</code>.

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()` <a id="method-i-to_ef_hex"></a> <a id="to_ef_hex-instance_method"></a>
Serialise the transaction in Extended Format as a hex string.
- **@return** [String] hex-encoded EF transaction

### `to_hex()` <a id="method-i-to_hex"></a> <a id="to_hex-instance_method"></a>
Serialise the transaction to a hex string.
- **@return** [String] hex-encoded transaction

### `total_input_satoshis()` <a id="method-i-total_input_satoshis"></a> <a id="total_input_satoshis-instance_method"></a>
Sum of all input source satoshi values.
- **@return** [Integer] total input value in satoshis

### `total_output_satoshis()` <a id="method-i-total_output_satoshis"></a> <a id="total_output_satoshis-instance_method"></a>
Sum of all output satoshi values.
- **@return** [Integer] total output value in satoshis

### `txid_hex()` <a id="method-i-txid_hex"></a> <a id="txid_hex-instance_method"></a>
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)` <a id="method-i-verify"></a> <a id="verify-instance_method"></a>
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:

1.  If a transaction has a merkle path that validates against the chain
    tracker, it is marked verified (inputs are not re-checked).
2.  Otherwise, each input's scripts are executed via the interpreter, and
    source transactions are enqueued for verification.
3.  Optionally validates that the root transaction's fee meets the provided
    fee model.
4.  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>#code</code>,
<code>#message</code>, <code>#cause</code>) that a boolean cannot convey.
Consumers can rescue `VerificationError` and inspect <code>#code</code> for
specifics.

Divergence summary:
*   <code>:output_overflow</code> — Ruby raises; TS/Python return `false`; Go
    omits the check
*   <code>:script_failure</code> — Ruby raises; TS/Python return `false`; Go
    also propagates errors
*   <code>:missing_source</code> — 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)` <a id="method-i-verify_input"></a> <a id="verify_input-instance_method"></a>
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()` <a id="method-i-wtxid"></a> <a id="wtxid-instance_method"></a>
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}.
- **@note** Memoised; see {file:docs/reference/sighash-cache.md} for the invalidation contract.
- **@return** [String] 32-byte transaction ID in wire byte order
