Optimize `encode_signing_data_to_inner`

The `encode_signing_data_to_inner` function previously constructed a
transaction internally, requiring a bunch of allocations, which it'd
then consensus-serialize into a writer (hasher). It also used a dummy
`TxOut::NULL` value which we want to get rid of.

To get rid of both allocations and the NULL value we serialize the
transaction on-the-fly. Because the encoding doesn't involve witnesses
it's not too complicated and the consensus encoding will never change so
there are no desync bugs possible. We may later change this to an
abstract transaction though.
This commit is contained in:
Martin Habovstiak 2025-01-29 22:24:51 +01:00
parent 98db7bca74
commit 313406d6ab
1 changed files with 44 additions and 47 deletions

View File

@ -22,11 +22,11 @@ use io::Write;
use crate::address::script_pubkey::ScriptExt as _; use crate::address::script_pubkey::ScriptExt as _;
use crate::consensus::{encode, Encodable}; use crate::consensus::{encode, Encodable};
use crate::prelude::{Borrow, BorrowMut, String, ToOwned, Vec}; use crate::prelude::{Borrow, BorrowMut, String, ToOwned};
use crate::taproot::{LeafVersion, TapLeafHash, TapLeafTag, TAPROOT_ANNEX_PREFIX}; use crate::taproot::{LeafVersion, TapLeafHash, TapLeafTag, TAPROOT_ANNEX_PREFIX};
use crate::transaction::TransactionExt as _; use crate::transaction::TransactionExt as _;
use crate::witness::Witness; use crate::witness::Witness;
use crate::{transaction, Amount, Script, ScriptBuf, Sequence, Transaction, TxIn, TxOut}; use crate::{transaction, Amount, Script, Sequence, Transaction, TxOut};
/// Used for signature hash for invalid use of SIGHASH_SINGLE. /// Used for signature hash for invalid use of SIGHASH_SINGLE.
#[rustfmt::skip] #[rustfmt::skip]
@ -959,63 +959,59 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
script_pubkey: &Script, script_pubkey: &Script,
sighash_type: u32, sighash_type: u32,
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
use crate::consensus::encode::WriteExt;
let (sighash, anyone_can_pay) = let (sighash, anyone_can_pay) =
EcdsaSighashType::from_consensus(sighash_type).split_anyonecanpay_flag(); EcdsaSighashType::from_consensus(sighash_type).split_anyonecanpay_flag();
// Build tx to sign self_.version.consensus_encode(writer)?;
let mut tx = Transaction {
version: self_.version,
lock_time: self_.lock_time,
input: vec![],
output: vec![],
};
// Add all inputs necessary.. // Add all inputs necessary..
if anyone_can_pay { if anyone_can_pay {
tx.input = vec![TxIn { writer.emit_compact_size(1u8)?;
previous_output: self_.input[input_index].previous_output, self_.input[input_index].previous_output.consensus_encode(writer)?;
script_sig: script_pubkey.to_owned(), script_pubkey.consensus_encode(writer)?;
sequence: self_.input[input_index].sequence, self_.input[input_index].sequence.consensus_encode(writer)?;
witness: Witness::default(),
}];
} else { } else {
tx.input = Vec::with_capacity(self_.input.len()); writer.emit_compact_size(self_.input.len())?;
for (n, input) in self_.input.iter().enumerate() { for (n, input) in self_.input.iter().enumerate() {
tx.input.push(TxIn { input.previous_output.consensus_encode(writer)?;
previous_output: input.previous_output, if n == input_index {
script_sig: if n == input_index { script_pubkey.consensus_encode(writer)?;
script_pubkey.to_owned() } else {
} else { Script::new().consensus_encode(writer)?;
ScriptBuf::new() }
}, if n != input_index
sequence: if n != input_index && (sighash == EcdsaSighashType::Single
&& (sighash == EcdsaSighashType::Single || sighash == EcdsaSighashType::None)
|| sighash == EcdsaSighashType::None) {
{ Sequence::ZERO.consensus_encode(writer)?;
Sequence::ZERO } else {
} else { input.sequence.consensus_encode(writer)?;
input.sequence }
},
witness: Witness::default(),
});
} }
} }
// ..then all outputs // ..then all outputs
tx.output = match sighash { match sighash {
EcdsaSighashType::All => self_.output.clone(), EcdsaSighashType::All => {
self_.output.consensus_encode(writer)?;
},
EcdsaSighashType::Single => { EcdsaSighashType::Single => {
let output_iter = self_ // sign all outputs up to and including this one, but erase
.output // all of them except for this one
.iter() let count = input_index.min(self_.output.len() - 1);
.take(input_index + 1) // sign all outputs up to and including this one, but erase writer.emit_compact_size(count + 1)?;
.enumerate() // all of them except for this one for _ in 0..count {
.map(|(n, out)| if n == input_index { out.clone() } else { TxOut::NULL }); // consensus encoding of the "NULL txout" - max amount, empty script_pubkey
output_iter.collect() writer.write_all(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00])?;
} }
EcdsaSighashType::None => vec![], self_.output[count].consensus_encode(writer)?;
},
EcdsaSighashType::None => {
writer.emit_compact_size(0u8)?;
},
_ => unreachable!(), _ => unreachable!(),
}; };
// hash the result self_.lock_time.consensus_encode(writer)?;
tx.consensus_encode(writer)?;
sighash_type.to_le_bytes().consensus_encode(writer)?; sighash_type.to_le_bytes().consensus_encode(writer)?;
Ok(()) Ok(())
} }
@ -1529,7 +1525,8 @@ mod tests {
use super::*; use super::*;
use crate::consensus::deserialize; use crate::consensus::deserialize;
use crate::locktime::absolute; use crate::locktime::absolute;
use crate::script::ScriptBufExt as _; use crate::script::{ScriptBuf, ScriptBufExt as _};
use crate::TxIn;
extern crate serde_json; extern crate serde_json;