From c21dabb824ca14fb643970c54b589619d2562323 Mon Sep 17 00:00:00 2001 From: Ivan Paljak Date: Wed, 23 Sep 2020 16:07:25 +0200 Subject: [PATCH 1/2] Expose serialized data for transaction signatures --- src/blockdata/transaction.rs | 57 ++++++++++++++++++++++++------------ src/util/bip143.rs | 51 ++++++++++++++++++++------------ 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 8cc37d84..ca81e7c7 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -307,30 +307,34 @@ impl Transaction { Wtxid::from_engine(enc) } - /// Computes a signature hash for a given input index with a given sighash flag. - /// To actually produce a scriptSig, this hash needs to be run through an - /// ECDSA signer, the SigHashType appended to the resulting sig, and a - /// script written around this, but this is the general (and hard) part. + /// Encodes the signing data from which a signature hash for a given input index with a given + /// sighash flag can be computed. To actually produce a scriptSig, this hash needs to be run + /// through an ECDSA signer, the SigHashType appended to the resulting sig, and a script + /// written around this, but this is the general (and hard) part. /// - /// *Warning* This does NOT attempt to support OP_CODESEPARATOR. In general - /// this would require evaluating `script_pubkey` to determine which separators - /// get evaluated and which don't, which we don't have the information to - /// determine. + /// *Warning* This does NOT attempt to support OP_CODESEPARATOR. In general this would require + /// evaluating `script_pubkey` to determine which separators get evaluated and which don't, + /// which we don't have the information to determine. /// - /// # Panics - /// Panics if `input_index` is greater than or equal to `self.input.len()` + /// # Panics Panics if `input_index` is greater than or equal to `self.input.len()` /// - pub fn signature_hash(&self, input_index: usize, script_pubkey: &Script, sighash_u32: u32) -> SigHash { + pub fn encode_signing_data_to( + &self, + mut writer: Write, + input_index: usize, + script_pubkey: &Script, + sighash_u32: u32 + ) { assert!(input_index < self.input.len()); // Panic on OOB let (sighash, anyone_can_pay) = SigHashType::from_u32(sighash_u32).split_anyonecanpay_flag(); // Special-case sighash_single bug because this is easy enough. if sighash == SigHashType::Single && input_index >= self.output.len() { - return SigHash::from_slice(&[1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); + writer.write_all(&[1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); } // Build tx to sign @@ -373,10 +377,27 @@ impl Transaction { _ => unreachable!() }; // hash the result - let mut engine = SigHash::engine(); - tx.consensus_encode(&mut engine).unwrap(); + tx.consensus_encode(&mut writer).unwrap(); let sighash_arr = endian::u32_to_array_le(sighash_u32); - sighash_arr.consensus_encode(&mut engine).unwrap(); + sighash_arr.consensus_encode(&mut writer).unwrap(); + } + + /// Computes a signature hash for a given input index with a given sighash flag. + /// To actually produce a scriptSig, this hash needs to be run through an + /// ECDSA signer, the SigHashType appended to the resulting sig, and a + /// script written around this, but this is the general (and hard) part. + /// + /// *Warning* This does NOT attempt to support OP_CODESEPARATOR. In general + /// this would require evaluating `script_pubkey` to determine which separators + /// get evaluated and which don't, which we don't have the information to + /// determine. + /// + /// # Panics + /// Panics if `input_index` is greater than or equal to `self.input.len()` + /// + pub fn signature_hash(&self, input_index: usize, script_pubkey: &Script, sighash_u32: u32) -> SigHash { + let mut engine = SigHash::engine(); + self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32); SigHash::from_engine(engine) } diff --git a/src/util/bip143.rs b/src/util/bip143.rs index fdaaf405..688d9cef 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -25,6 +25,7 @@ use blockdata::script::Script; use blockdata::transaction::{Transaction, TxIn, SigHashType}; use consensus::encode::Encodable; +use std::io; use std::ops::{Deref, DerefMut}; /// Parts of a sighash which are common across inputs or signatures, and which are @@ -168,27 +169,32 @@ impl> SigHashCache { }) } - /// Compute the BIP143 sighash for any flag type. See SighashComponents::sighash_all simpler - /// API for the most common case - pub fn signature_hash(&mut self, input_index: usize, script_code: &Script, value: u64, sighash_type: SigHashType) -> SigHash { - + /// Encode the BIP143 signing data for any flag type into a given object implementing a + /// std::io::Write trait. + pub fn encode_signing_data_to( + &mut self, + mut writer: Write, + input_index: usize, + script_code: &Script, + value: u64, + sighash_type: SigHashType, + ) { let zero_hash = sha256d::Hash::default(); let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); - let mut enc = SigHash::engine(); - self.tx.version.consensus_encode(&mut enc).unwrap(); + self.tx.version.consensus_encode(&mut writer).unwrap(); if !anyone_can_pay { - self.hash_prevouts().consensus_encode(&mut enc).unwrap(); + self.hash_prevouts().consensus_encode(&mut writer).unwrap(); } else { - zero_hash.consensus_encode(&mut enc).unwrap(); + zero_hash.consensus_encode(&mut writer).unwrap(); } if !anyone_can_pay && sighash != SigHashType::Single && sighash != SigHashType::None { - self.hash_sequence().consensus_encode(&mut enc).unwrap(); + self.hash_sequence().consensus_encode(&mut writer).unwrap(); } else { - zero_hash.consensus_encode(&mut enc).unwrap(); + zero_hash.consensus_encode(&mut writer).unwrap(); } { @@ -196,25 +202,32 @@ impl> SigHashCache { txin .previous_output - .consensus_encode(&mut enc) + .consensus_encode(&mut writer) .unwrap(); - script_code.consensus_encode(&mut enc).unwrap(); - value.consensus_encode(&mut enc).unwrap(); - txin.sequence.consensus_encode(&mut enc).unwrap(); + script_code.consensus_encode(&mut writer).unwrap(); + value.consensus_encode(&mut writer).unwrap(); + txin.sequence.consensus_encode(&mut writer).unwrap(); } if sighash != SigHashType::Single && sighash != SigHashType::None { - self.hash_outputs().consensus_encode(&mut enc).unwrap(); + self.hash_outputs().consensus_encode(&mut writer).unwrap(); } else if sighash == SigHashType::Single && input_index < self.tx.output.len() { let mut single_enc = SigHash::engine(); self.tx.output[input_index].consensus_encode(&mut single_enc).unwrap(); - SigHash::from_engine(single_enc).consensus_encode(&mut enc).unwrap(); + SigHash::from_engine(single_enc).consensus_encode(&mut writer).unwrap(); } else { - zero_hash.consensus_encode(&mut enc).unwrap(); + zero_hash.consensus_encode(&mut writer).unwrap(); } - self.tx.lock_time.consensus_encode(&mut enc).unwrap(); - sighash_type.as_u32().consensus_encode(&mut enc).unwrap(); + self.tx.lock_time.consensus_encode(&mut writer).unwrap(); + sighash_type.as_u32().consensus_encode(&mut writer).unwrap(); + } + + /// Compute the BIP143 sighash for any flag type. See SighashComponents::sighash_all simpler + /// API for the most common case + pub fn signature_hash(&mut self, input_index: usize, script_code: &Script, value: u64, sighash_type: SigHashType) -> SigHash { + let mut enc = SigHash::engine(); + self.encode_signing_data_to(&mut enc, input_index, script_code, value, sighash_type); SigHash::from_engine(enc) } } From e66caab956ff003bba3f15b0fcb08944d5aacd3c Mon Sep 17 00:00:00 2001 From: Ivan Paljak Date: Thu, 8 Oct 2020 16:40:30 +0200 Subject: [PATCH 2/2] Improve error handling, fix forgotten early return --- src/blockdata/transaction.rs | 23 +++++++++++------ src/util/bip143.rs | 50 ++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index ca81e7c7..eb3c343a 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -324,7 +324,7 @@ impl Transaction { input_index: usize, script_pubkey: &Script, sighash_u32: u32 - ) { + ) -> Result<(), encode::Error> { assert!(input_index < self.input.len()); // Panic on OOB let (sighash, anyone_can_pay) = SigHashType::from_u32(sighash_u32).split_anyonecanpay_flag(); @@ -334,7 +334,8 @@ impl Transaction { writer.write_all(&[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0]).unwrap(); + 0, 0, 0, 0, 0, 0, 0, 0])?; + return Ok(()); } // Build tx to sign @@ -377,9 +378,10 @@ impl Transaction { _ => unreachable!() }; // hash the result - tx.consensus_encode(&mut writer).unwrap(); + tx.consensus_encode(&mut writer)?; let sighash_arr = endian::u32_to_array_le(sighash_u32); - sighash_arr.consensus_encode(&mut writer).unwrap(); + sighash_arr.consensus_encode(&mut writer)?; + Ok(()) } /// Computes a signature hash for a given input index with a given sighash flag. @@ -395,10 +397,15 @@ impl Transaction { /// # Panics /// Panics if `input_index` is greater than or equal to `self.input.len()` /// - pub fn signature_hash(&self, input_index: usize, script_pubkey: &Script, sighash_u32: u32) -> SigHash { + pub fn signature_hash( + &self, + input_index: usize, + script_pubkey: &Script, + sighash_u32: u32 + ) -> Result { let mut engine = SigHash::engine(); - self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32); - SigHash::from_engine(engine) + self.encode_signing_data_to(&mut engine, input_index, script_pubkey, sighash_u32)?; + Ok(SigHash::from_engine(engine)) } /// Gets the "weight" of this transaction, as defined by BIP141. For transactions with an empty @@ -895,7 +902,7 @@ mod tests { raw_expected.reverse(); let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap(); - let actual_result = tx.signature_hash(input_index, &script, hash_type as u32); + let actual_result = tx.signature_hash(input_index, &script, hash_type as u32).unwrap(); assert_eq!(actual_result, expected_result); } diff --git a/src/util/bip143.rs b/src/util/bip143.rs index 688d9cef..f5cb5c1c 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -23,7 +23,7 @@ use hashes::{Hash, sha256d}; use hash_types::SigHash; use blockdata::script::Script; use blockdata::transaction::{Transaction, TxIn, SigHashType}; -use consensus::encode::Encodable; +use consensus::{encode, Encodable}; use std::io; use std::ops::{Deref, DerefMut}; @@ -178,23 +178,23 @@ impl> SigHashCache { script_code: &Script, value: u64, sighash_type: SigHashType, - ) { + ) -> Result<(), encode::Error> { let zero_hash = sha256d::Hash::default(); let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); - self.tx.version.consensus_encode(&mut writer).unwrap(); + self.tx.version.consensus_encode(&mut writer)?; if !anyone_can_pay { - self.hash_prevouts().consensus_encode(&mut writer).unwrap(); + self.hash_prevouts().consensus_encode(&mut writer)?; } else { - zero_hash.consensus_encode(&mut writer).unwrap(); + zero_hash.consensus_encode(&mut writer)?; } if !anyone_can_pay && sighash != SigHashType::Single && sighash != SigHashType::None { - self.hash_sequence().consensus_encode(&mut writer).unwrap(); + self.hash_sequence().consensus_encode(&mut writer)?; } else { - zero_hash.consensus_encode(&mut writer).unwrap(); + zero_hash.consensus_encode(&mut writer)?; } { @@ -202,33 +202,39 @@ impl> SigHashCache { txin .previous_output - .consensus_encode(&mut writer) - .unwrap(); - script_code.consensus_encode(&mut writer).unwrap(); - value.consensus_encode(&mut writer).unwrap(); - txin.sequence.consensus_encode(&mut writer).unwrap(); + .consensus_encode(&mut writer)?; + script_code.consensus_encode(&mut writer)?; + value.consensus_encode(&mut writer)?; + txin.sequence.consensus_encode(&mut writer)?; } if sighash != SigHashType::Single && sighash != SigHashType::None { - self.hash_outputs().consensus_encode(&mut writer).unwrap(); + self.hash_outputs().consensus_encode(&mut writer)?; } else if sighash == SigHashType::Single && input_index < self.tx.output.len() { let mut single_enc = SigHash::engine(); - self.tx.output[input_index].consensus_encode(&mut single_enc).unwrap(); - SigHash::from_engine(single_enc).consensus_encode(&mut writer).unwrap(); + self.tx.output[input_index].consensus_encode(&mut single_enc)?; + SigHash::from_engine(single_enc).consensus_encode(&mut writer)?; } else { - zero_hash.consensus_encode(&mut writer).unwrap(); + zero_hash.consensus_encode(&mut writer)?; } - self.tx.lock_time.consensus_encode(&mut writer).unwrap(); - sighash_type.as_u32().consensus_encode(&mut writer).unwrap(); + self.tx.lock_time.consensus_encode(&mut writer)?; + sighash_type.as_u32().consensus_encode(&mut writer)?; + Ok(()) } /// Compute the BIP143 sighash for any flag type. See SighashComponents::sighash_all simpler /// API for the most common case - pub fn signature_hash(&mut self, input_index: usize, script_code: &Script, value: u64, sighash_type: SigHashType) -> SigHash { + pub fn signature_hash( + &mut self, + input_index: usize, + script_code: &Script, + value: u64, + sighash_type: SigHashType + ) -> Result { let mut enc = SigHash::engine(); - self.encode_signing_data_to(&mut enc, input_index, script_code, value, sighash_type); - SigHash::from_engine(enc) + self.encode_signing_data_to(&mut enc, input_index, script_code, value, sighash_type)?; + Ok(SigHash::from_engine(enc)) } } @@ -286,7 +292,7 @@ mod tests { let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap(); let mut cache = SigHashCache::new(&tx); let sighash_type = SigHashType::from_u32(hash_type); - let actual_result = cache.signature_hash(input_index, &script, value, sighash_type); + let actual_result = cache.signature_hash(input_index, &script, value, sighash_type).unwrap(); assert_eq!(actual_result, expected_result); }