Skip to content

Commit

Permalink
Merge rust-bitcoin#3512: Manually implement compute_txid and `compu…
Browse files Browse the repository at this point in the history
…te_wtxid`

5633b10 Manually implement compute_txid and compute_wtxid (Tobin C. Harding)

Pull request description:

  We would like to move the `Transaction` type over to `primitives` including the `compute_txid` and `compute_wtxid` functions however currently the implementations, as expected, use `Encodable`.

  Manually implement `Encodable` by hashing all the fields in the correct order.

  Note we have unit tests already that check the output string of the txid returned so these act as regression tests for this patch.

ACKs for top commit:
  apoelstra:
    ACK 5633b10; successfully ran local tests; nice

Tree-SHA512: 66a955d3d896801cfefe0388aade3a31f22fac5b6da7b996be61f374b93772487c0c203320aaf5165fcef26874564bce375ecb364175b0a01c3008b7ea8db981
  • Loading branch information
apoelstra committed Oct 26, 2024
2 parents 856503d + 5633b10 commit b11bd9a
Showing 1 changed file with 62 additions and 9 deletions.
71 changes: 62 additions & 9 deletions bitcoin/src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,8 @@ impl Transaction {
/// this will be equal to [`Transaction::compute_wtxid()`].
#[doc(alias = "txid")]
pub fn compute_txid(&self) -> Txid {
let mut enc = sha256d::Hash::engine();
self.version.consensus_encode(&mut enc).expect("engines don't error");
self.input.consensus_encode(&mut enc).expect("engines don't error");
self.output.consensus_encode(&mut enc).expect("engines don't error");
self.lock_time.consensus_encode(&mut enc).expect("engines don't error");
Txid::from_byte_array(sha256d::Hash::from_engine(enc).to_byte_array())
let hash = hash_transaction(self, false);
Txid::from_byte_array(hash.to_byte_array())
}

/// Computes the segwit version of the transaction id.
Expand All @@ -368,9 +364,8 @@ impl Transaction {
/// this will be equal to [`Transaction::txid()`].
#[doc(alias = "wtxid")]
pub fn compute_wtxid(&self) -> Wtxid {
let mut enc = sha256d::Hash::engine();
self.consensus_encode(&mut enc).expect("engines don't error");
Wtxid::from_byte_array(sha256d::Hash::from_engine(enc).to_byte_array())
let hash = hash_transaction(self, self.uses_segwit_serialization());
Wtxid::from_byte_array(hash.to_byte_array())
}

/// Returns the weight of this transaction, as defined by BIP-141.
Expand Down Expand Up @@ -651,6 +646,64 @@ impl Transaction {
}
}

// This is equivalent to consensus encoding but hashes the fields manually.
fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256d::Hash {
use hashes::HashEngine as _;

let mut enc = sha256d::Hash::engine();
enc.input(&tx.version.0.to_le_bytes()); // Same as `encode::emit_i32`.

if uses_segwit_serialization {
// BIP-141 (segwit) transaction serialization also includes marker and flag.
enc.input(&[SEGWIT_MARKER]);
enc.input(&[SEGWIT_FLAG]);
}

// Encode inputs (excluding witness data) with leading compact size encoded int.
let input_len = tx.input.len();
enc.input(compact_size::encode(input_len).as_slice());
for input in &tx.input {
// Encode each input same as we do in `Encodable for TxIn`.
enc.input(input.previous_output.txid.as_byte_array());
enc.input(&input.previous_output.vout.to_le_bytes());

let script_sig_bytes = input.script_sig.as_bytes();
enc.input(compact_size::encode(script_sig_bytes.len()).as_slice());
enc.input(script_sig_bytes);

enc.input(&input.sequence.0.to_le_bytes())
}

// Encode outputs with leading compact size encoded int.
let output_len = tx.output.len();
enc.input(compact_size::encode(output_len).as_slice());
for output in &tx.output {
// Encode each output same as we do in `Encodable for TxOut`.
enc.input(&output.value.to_sat().to_le_bytes());

let script_pubkey_bytes = output.script_pubkey.as_bytes();
enc.input(compact_size::encode(script_pubkey_bytes.len()).as_slice());
enc.input(script_pubkey_bytes);
}

if uses_segwit_serialization {
// BIP-141 (segwit) transaction serialization also includes the witness data.
for input in &tx.input {
// Same as `Encodable for Witness`.
enc.input(compact_size::encode(input.witness.len()).as_slice());
for element in input.witness.iter() {
enc.input(compact_size::encode(element.len()).as_slice());
enc.input(element);
}
}
}

// Same as `Encodable for absolute::LockTime`.
enc.input(&tx.lock_time.to_consensus_u32().to_le_bytes());

sha256d::Hash::from_engine(enc)
}

/// Error attempting to do an out of bounds access on the transaction inputs vector.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputsIndexError(pub IndexOutOfBoundsError);
Expand Down

0 comments on commit b11bd9a

Please sign in to comment.