Merge rust-bitcoin/rust-bitcoin#3512: Manually implement `compute_txid` and `compute_wtxid`

5633b10f5c 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 5633b10f5c826e0b2ac47dd85f697f12710898d7; successfully ran local tests; nice

Tree-SHA512: 66a955d3d896801cfefe0388aade3a31f22fac5b6da7b996be61f374b93772487c0c203320aaf5165fcef26874564bce375ecb364175b0a01c3008b7ea8db981
This commit is contained in:
merge-script 2024-10-26 15:05:58 +00:00
commit b11bd9a6b5
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 62 additions and 9 deletions

View File

@ -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.
@ -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.
@ -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);