From 0cfad675d09af7d1ca9405ca2fdd690e7312c483 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Fri, 20 Nov 2020 09:45:50 -0600 Subject: [PATCH 1/5] Added sighashtype for transaction --- src/lib.rs | 2 +- src/transaction.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 878ea32a..472d2c55 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,7 +53,7 @@ pub mod slip77; pub use bitcoin::{bech32, hashes, secp256k1}; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use address::{Address, AddressParams, AddressError}; -pub use transaction::{OutPoint, PeginData, PegoutData, TxIn, TxOut, TxInWitness, TxOutWitness, Transaction, AssetIssuance}; +pub use transaction::{OutPoint, PeginData, PegoutData, SigHashType, TxIn, TxOut, TxInWitness, TxOutWitness, Transaction, AssetIssuance}; pub use block::{BlockHeader, Block}; pub use block::ExtData as BlockExtData; pub use ::bitcoin::consensus::encode::VarInt; diff --git a/src/transaction.rs b/src/transaction.rs index 85ffeda4..6f157ebe 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -716,6 +716,61 @@ impl Decodable for Transaction { } } } +/// Hashtype of a transaction, encoded in the last byte of a signature +/// Fixed values so they can be casted as integer types for encoding +#[derive(PartialEq, Eq, Debug, Copy, Clone)] +pub enum SigHashType { + /// 0x1: Sign all outputs + All = 0x01, + /// 0x2: Sign no outputs --- anyone can choose the destination + None = 0x02, + /// 0x3: Sign the output whose index matches this input's index. If none exists, + /// sign the hash `0000000000000000000000000000000000000000000000000000000000000001`. + /// (This rule is probably an unintentional C++ism, but it's consensus so we have + /// to follow it.) + Single = 0x03, + /// 0x81: Sign all outputs but only this input + AllPlusAnyoneCanPay = 0x81, + /// 0x82: Sign no outputs and only this input + NonePlusAnyoneCanPay = 0x82, + /// 0x83: Sign one output and only this input (see `Single` for what "one output" means) + SinglePlusAnyoneCanPay = 0x83, +} + +impl SigHashType { + /// Break the sighash flag into the "real" sighash flag and the ANYONECANPAY boolean + pub(crate) fn split_anyonecanpay_flag(self) -> (SigHashType, bool) { + match self { + SigHashType::All => (SigHashType::All, false), + SigHashType::None => (SigHashType::None, false), + SigHashType::Single => (SigHashType::Single, false), + SigHashType::AllPlusAnyoneCanPay => (SigHashType::All, true), + SigHashType::NonePlusAnyoneCanPay => (SigHashType::None, true), + SigHashType::SinglePlusAnyoneCanPay => (SigHashType::Single, true), + } + } + + /// Reads a 4-byte uint32 as a sighash type + pub fn from_u32(n: u32) -> SigHashType { + match n & 0x9f { + // "real" sighashes + 0x01 => SigHashType::All, + 0x02 => SigHashType::None, + 0x03 => SigHashType::Single, + 0x81 => SigHashType::AllPlusAnyoneCanPay, + 0x82 => SigHashType::NonePlusAnyoneCanPay, + 0x83 => SigHashType::SinglePlusAnyoneCanPay, + // catchalls + x if x & 0x80 == 0x80 => SigHashType::AllPlusAnyoneCanPay, + _ => SigHashType::All, + } + } + + /// Converts to a u32 + pub fn as_u32(self) -> u32 { + self as u32 + } +} #[cfg(test)] mod tests { From 609f2985899b5f77ec434175fee2e4d7477b7d31 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Fri, 20 Nov 2020 10:26:03 -0600 Subject: [PATCH 2/5] Added segwit signature hash --- src/lib.rs | 2 +- src/sighash.rs | 253 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/sighash.rs diff --git a/src/lib.rs b/src/lib.rs index 472d2c55..ac666975 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,7 @@ pub mod opcodes; pub mod script; mod transaction; pub mod slip77; - +pub mod sighash; // re-export bitcoin deps which we re-use pub use bitcoin::{bech32, hashes, secp256k1}; // export everything at the top level so it can be used as `elements::Transaction` etc. diff --git a/src/sighash.rs b/src/sighash.rs new file mode 100644 index 00000000..8e539a42 --- /dev/null +++ b/src/sighash.rs @@ -0,0 +1,253 @@ +// Rust Bitcoin Library +// Written in 2018 by +// Andrew Poelstra +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! BIP143 Implementation +//! +//! Implementation of BIP143 Segwit-style signatures. Should be sufficient +//! to create signatures for Segwit transactions (which should be pushed into +//! the appropriate place in the `Transaction::witness` array) or bcash +//! signatures, which are placed in the scriptSig. +//! + +use encode::{self, Encodable}; +use hash_types::SigHash; +use hashes::{sha256d, Hash}; +use script::Script; +use std::ops::Deref; +use std::io; +use transaction::SigHashType; +use transaction::Transaction; +use confidential; + +/// A replacement for SigHashComponents which supports all sighash modes +pub struct SigHashCache { + /// Access to transaction required for various introspection + tx: T, + /// Hash of all the previous outputs, computed as required + hash_prevouts: Option, + /// Hash of all the input sequence nos, computed as required + hash_sequence: Option, + /// Hash of all the outputs in this transaction, computed as required + hash_outputs: Option, + /// Hash of all the issunaces in this transaction, computed as required + hash_issuances: Option, +} + +impl> SigHashCache { + /// Compute the sighash components from an unsigned transaction and auxiliary + /// in a lazy manner when required. + /// For the generated sighashes to be valid, no fields in the transaction may change except for + /// script_sig and witnesses. + pub fn new(tx: R) -> Self { + SigHashCache { + tx: tx, + hash_prevouts: None, + hash_sequence: None, + hash_outputs: None, + hash_issuances: None, + } + } + + /// Calculate hash for prevouts + pub fn hash_prevouts(&mut self) -> sha256d::Hash { + let hash_prevout = &mut self.hash_prevouts; + let input = &self.tx.input; + *hash_prevout.get_or_insert_with(|| { + let mut enc = sha256d::Hash::engine(); + for txin in input { + txin.previous_output.consensus_encode(&mut enc).unwrap(); + } + sha256d::Hash::from_engine(enc) + }) + } + + /// Calculate hash for input sequence values + pub fn hash_sequence(&mut self) -> sha256d::Hash { + let hash_sequence = &mut self.hash_sequence; + let input = &self.tx.input; + *hash_sequence.get_or_insert_with(|| { + let mut enc = sha256d::Hash::engine(); + for txin in input { + txin.sequence.consensus_encode(&mut enc).unwrap(); + } + sha256d::Hash::from_engine(enc) + }) + } + + /// Calculate hash for issuances + pub fn hash_issuances(&mut self) -> sha256d::Hash { + let hash_issuance = &mut self.hash_issuances; + let input = &self.tx.input; + *hash_issuance.get_or_insert_with(|| { + let mut enc = sha256d::Hash::engine(); + for txin in input { + if txin.has_issuance() { + txin.asset_issuance.consensus_encode(&mut enc).unwrap(); + } else { + 0u8.consensus_encode(&mut enc).unwrap(); + } + } + sha256d::Hash::from_engine(enc) + }) + } + + /// Calculate hash for outputs + pub fn hash_outputs(&mut self) -> sha256d::Hash { + let hash_output = &mut self.hash_outputs; + let output = &self.tx.output; + *hash_output.get_or_insert_with(|| { + let mut enc = sha256d::Hash::engine(); + for txout in output { + txout.consensus_encode(&mut enc).unwrap(); + } + sha256d::Hash::from_engine(enc) + }) + } + + /// Encode the BIP143 signing data for any flag type into a given object implementing a + /// std::io::Write trait. + /// + /// *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 encode_segwitv0_signing_data_to( + &mut self, + mut writer: Write, + input_index: usize, + script_code: &Script, + value: confidential::Value, + 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)?; + + if !anyone_can_pay { + self.hash_prevouts().consensus_encode(&mut writer)?; + } else { + zero_hash.consensus_encode(&mut writer)?; + } + + if !anyone_can_pay && sighash != SigHashType::Single && sighash != SigHashType::None { + self.hash_sequence().consensus_encode(&mut writer)?; + } else { + zero_hash.consensus_encode(&mut writer)?; + } + + // Elements: Push the hash issuance zero hash as required + // If required implement for issuance, but not necessary as of now + if !anyone_can_pay { + self.hash_issuances().consensus_encode(&mut writer)?; + } else { + zero_hash.consensus_encode(&mut writer)?; + } + + // input specific values + { + let txin = &self.tx.input[input_index]; + + txin.previous_output.consensus_encode(&mut writer)?; + script_code.consensus_encode(&mut writer)?; + value.consensus_encode(&mut writer)?; + txin.sequence.consensus_encode(&mut writer)?; + if txin.has_issuance(){ + txin.asset_issuance.consensus_encode(&mut writer)?; + } + } + + // hashoutputs + if sighash != SigHashType::Single && sighash != SigHashType::None { + 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)?; + SigHash::from_engine(single_enc).consensus_encode(&mut writer)?; + } else { + zero_hash.consensus_encode(&mut writer)?; + } + + self.tx.lock_time.consensus_encode(&mut writer)?; + sighash_type.as_u32().consensus_encode(&mut writer)?; + Ok(()) + } + + /// Compute the segwitv0(BIP143) style sighash for any flag type. + /// *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 segwitv0_sighash( + &mut self, + input_index: usize, + script_code: &Script, + value: confidential::Value, + sighash_type: SigHashType + ) -> SigHash { + let mut enc = SigHash::engine(); + self.encode_segwitv0_signing_data_to(&mut enc, input_index, script_code, value, sighash_type) + .expect("engines don't error"); + SigHash::from_engine(enc) + } +} + +#[cfg(test)] +mod tests{ + use super::*; + use encode::deserialize; + use bitcoin::hashes::hex::FromHex; + use bitcoin; + + fn test_segwit_sighash(tx: &str, script: &str, input_index: usize, value: &str, hash_type: SigHashType, expected_result: &str) { + let tx: Transaction = deserialize(&Vec::::from_hex(tx).unwrap()[..]).unwrap(); + let script = Script::from(Vec::::from_hex(script).unwrap()); + // A hack to parse sha256d strings are sha256 so that we don't reverse them... + let raw_expected = bitcoin::hashes::sha256::Hash::from_hex(expected_result).unwrap(); + let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap(); + + let mut cache = SigHashCache::new(&tx); + let value : confidential::Value = deserialize(&Vec::::from_hex(value).unwrap()[..]).unwrap(); + let actual_result = cache.segwitv0_sighash(input_index, &script, value, hash_type); + assert_eq!(actual_result, expected_result); + } + + #[test] + fn test_segwit_sighashes(){ + // generated by script(example_test.py) at https://github.com/sanket1729/elements/commit/8fb4eb9e6020adaf20f3ec25055ffa905ba5b5c4 + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::All, "e201b4019129a03ca0304989731c6dccde232c854d86fce999b7411da1e90048"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::None, "bfc6599816673083334ae82ac3459a2d0fef478d3e580e3ae203a28347502cb4"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::Single, "4bc8546e32d31c5415444138184696e80f49e537a083bfcc89be2ab41d962e76"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::AllPlusAnyoneCanPay, "b70ba5f4a1c2c48cd7f2104b2baa6a5c97987eb560916d39a5d427deb8b1dc2a"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::NonePlusAnyoneCanPay, "6d6a4749c09ffd9a8df4c5de5d939325d896009e18f94bb095c9d7d695a8465e"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::SinglePlusAnyoneCanPay, "7fc34367b42bf0e2bb78d8c20f45a64b81b2d4fbb59cbff8649322f619e88a0f"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::All, "71141639d982f1a1a8901e32fb1a9e15a0ea168b37d33300a3c9619fc3767388"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::None, "00730922d0e1d55b4b5fffafd087b06aeb44c4cedb58d8e182cbb9b87382cddb"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::Single, "100063ea0923ef4432dd51c5756383530f28b31ffe9d50b59a11b94a63c84c78"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::AllPlusAnyoneCanPay, "e1c4ddf5f723759f7d99d4f162155119160b1c6b765fdbdb25aedb2059769b74"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::NonePlusAnyoneCanPay, "b0be275e0c69e89ef5c482fdf330038c3b2994ebce3e3639bb81456d15a95a7a"); + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "010000000005f5e100", SigHashType::SinglePlusAnyoneCanPay, "27c293da7a0f08e161fa2a77aeefa6743c929905597b5bcb28f2015fe648aa0c"); + + // Test a issuance test with only sighash all + test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000003e801000000000000000a0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::All, "ea946ee417d5a16a1038b2c3b54d1b7b12a9f98c0dcb4684bf005eb1c27d0c92"); + } +} \ No newline at end of file From 2e93c127f95a0bb938aaf0758859015101090056 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Tue, 1 Dec 2020 02:30:21 -0600 Subject: [PATCH 3/5] Add more utility functions to script --- src/script.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/src/script.rs b/src/script.rs index 13d58599..8e4ddeb0 100644 --- a/src/script.rs +++ b/src/script.rs @@ -30,7 +30,8 @@ use std::{fmt, io, ops}; #[cfg(feature = "serde")] use serde; use encode::{self, Decodable, Encodable}; -use {opcodes, ScriptHash, WScriptHash}; +use bitcoin::hashes::Hash; +use {opcodes, ScriptHash, WScriptHash, PubkeyHash, WPubkeyHash}; use bitcoin::PublicKey; @@ -208,6 +209,75 @@ impl Script { /// Creates a new empty script pub fn new() -> Script { Script(vec![].into_boxed_slice()) } + /// Generates P2PK-type of scriptPubkey + pub fn new_p2pk(pubkey: &PublicKey) -> Script { + Builder::new() + .push_key(pubkey) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script() + } + + /// Generates P2PKH-type of scriptPubkey + pub fn new_p2pkh(pubkey_hash: &PubkeyHash) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(&pubkey_hash[..]) + .push_opcode(opcodes::all::OP_EQUALVERIFY) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script() + } + + /// Generates P2SH-type of scriptPubkey with a given hash of the redeem script + pub fn new_p2sh(script_hash: &ScriptHash) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(&script_hash[..]) + .push_opcode(opcodes::all::OP_EQUAL) + .into_script() + } + + /// Generates P2WPKH-type of scriptPubkey + pub fn new_v0_wpkh(pubkey_hash: &WPubkeyHash) -> Script { + Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &pubkey_hash.to_vec()) + } + + /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script + pub fn new_v0_wsh(script_hash: &WScriptHash) -> Script { + Script::new_witness_program(::bech32::u5::try_from_u8(0).unwrap(), &script_hash.to_vec()) + } + + /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script + pub fn new_witness_program(ver: ::bech32::u5, program: &[u8]) -> Script { + let mut verop = ver.to_u8(); + assert!(verop <= 16, "incorrect witness version provided: {}", verop); + if verop > 0 { + verop = 0x50 + verop; + } + Builder::new() + .push_opcode(verop.into()) + .push_slice(&program) + .into_script() + } + + /// Generates OP_RETURN-type of scriptPubkey for a given data + pub fn new_op_return(data: &[u8]) -> Script { + Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(data) + .into_script() + } + + /// Returns 160-bit hash of the script + pub fn script_hash(&self) -> ScriptHash { + ScriptHash::hash(&self.as_bytes()) + } + + /// Returns 256-bit hash of the script for P2WSH outputs + pub fn wscript_hash(&self) -> WScriptHash { + WScriptHash::hash(&self.as_bytes()) + } + /// The length in bytes of the script pub fn len(&self) -> usize { self.0.len() } @@ -225,7 +295,6 @@ impl Script { /// Compute the P2SH output corresponding to this redeem script pub fn to_p2sh(&self) -> Script { - use bitcoin::hashes::Hash; Builder::new().push_opcode(opcodes::all::OP_HASH160) .push_slice(&ScriptHash::hash(&self.0)[..]) .push_opcode(opcodes::all::OP_EQUAL) @@ -235,7 +304,6 @@ impl Script { /// Compute the P2WSH output corresponding to this witnessScript (aka the "witness redeem /// script") pub fn to_v0_p2wsh(&self) -> Script { - use bitcoin::hashes::Hash; Builder::new().push_int(0) .push_slice(&WScriptHash::hash(&self.0)[..]) .into_script() From bbf2e670450345e3f7440fef32a98475d8c3d6b1 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Tue, 1 Dec 2020 23:50:11 -0600 Subject: [PATCH 4/5] Add endian file from upstream(not public) --- src/endian.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/endian.rs diff --git a/src/endian.rs b/src/endian.rs new file mode 100644 index 00000000..6a009281 --- /dev/null +++ b/src/endian.rs @@ -0,0 +1,25 @@ +macro_rules! define_le_to_array { + ($name: ident, $type: ty, $byte_len: expr) => { + #[inline] + pub fn $name(val: $type) -> [u8; $byte_len] { + debug_assert_eq!(::std::mem::size_of::<$type>(), $byte_len); // size_of isn't a constfn in 1.22 + let mut res = [0; $byte_len]; + for i in 0..$byte_len { + res[i] = ((val >> i*8) & 0xff) as u8; + } + res + } + } +} + +define_le_to_array!(u32_to_array_le, u32, 4); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn endianness_test() { + assert_eq!(u32_to_array_le(0xdeadbeef), [0xef, 0xbe, 0xad, 0xde]); + } +} From 9d76ae6c196edc3ead7df746334173af899b6519 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 2 Dec 2020 04:31:43 -0600 Subject: [PATCH 5/5] Add legacy sighash --- src/encode.rs | 11 ++++ src/lib.rs | 2 + src/sighash.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/src/encode.rs b/src/encode.rs index 582e5059..b64c605c 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -28,6 +28,8 @@ pub use bitcoin::consensus::encode::MAX_VEC_SIZE; /// Encoding error #[derive(Debug)] pub enum Error { + /// And I/O error + Io(io::Error), /// A Bitcoin encoding error. Bitcoin(btcenc::Error), /// Tried to allocate an oversized vector @@ -46,6 +48,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { + Error::Io(ref e) => write!(f, "I/O error: {}", e), Error::Bitcoin(ref e) => write!(f, "a Bitcoin type encoding error: {}", e), Error::OversizedVectorAllocation { requested: ref r, @@ -73,6 +76,13 @@ impl From for Error { } } +#[doc(hidden)] +impl From for Error { + fn from(error: io::Error) -> Self { + Error::Io(error) + } +} + /// Data which can be encoded in a consensus-consistent way pub trait Encodable { /// Encode an object with a well-defined format, should only ever error if @@ -153,6 +163,7 @@ macro_rules! impl_upstream { impl_upstream!(u8); impl_upstream!(u32); impl_upstream!(u64); +impl_upstream!([u8;4]); impl_upstream!([u8; 32]); impl_upstream!(Box<[u8]>); impl_upstream!(Vec); diff --git a/src/lib.rs b/src/lib.rs index ac666975..0f8e054e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,6 +49,8 @@ pub mod script; mod transaction; pub mod slip77; pub mod sighash; +// consider making upstream public +mod endian; // re-export bitcoin deps which we re-use pub use bitcoin::{bech32, hashes, secp256k1}; // export everything at the top level so it can be used as `elements::Transaction` etc. diff --git a/src/sighash.rs b/src/sighash.rs index 8e539a42..e3f18693 100644 --- a/src/sighash.rs +++ b/src/sighash.rs @@ -25,8 +25,8 @@ use hashes::{sha256d, Hash}; use script::Script; use std::ops::Deref; use std::io; -use transaction::SigHashType; -use transaction::Transaction; +use endian; +use transaction::{SigHashType, Transaction, TxIn, TxOut, TxInWitness}; use confidential; /// A replacement for SigHashComponents which supports all sighash modes @@ -58,6 +58,121 @@ impl> SigHashCache { } } + /// 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. + /// + /// # Panics Panics if `input_index` is greater than or equal to `self.input.len()` + /// + pub fn encode_legacy_signing_data_to( + &self, + mut writer: Write, + input_index: usize, + script_pubkey: &Script, + sighash_type: SigHashType, + ) -> Result<(), encode::Error> { + assert!(input_index < self.tx.input.len()); // Panic on OOB + + let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); + + // Special-case sighash_single bug because this is easy enough. + if sighash == SigHashType::Single && input_index >= self.tx.output.len() { + 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])?; + return Ok(()); + } + + // Build tx to sign + let mut tx = Transaction { + version: self.tx.version, + lock_time: self.tx.lock_time, + input: vec![], + output: vec![], + }; + // Add all inputs necessary.. + if anyone_can_pay { + tx.input = vec![TxIn { + previous_output: self.tx.input[input_index].previous_output, + is_pegin: self.tx.input[input_index].is_pegin, + has_issuance: self.tx.input[input_index].has_issuance, + script_sig: script_pubkey.clone(), + sequence: self.tx.input[input_index].sequence, + asset_issuance: self.tx.input[input_index].asset_issuance, + witness: TxInWitness::default(), + }]; + } else { + tx.input = Vec::with_capacity(self.tx.input.len()); + for (n, input) in self.tx.input.iter().enumerate() { + tx.input.push(TxIn { + previous_output: input.previous_output, + is_pegin: input.is_pegin, + has_issuance: input.has_issuance, + script_sig: if n == input_index { script_pubkey.clone() } else { Script::new() }, + sequence: if n != input_index && (sighash == SigHashType::Single || sighash == SigHashType::None) { 0 } else { input.sequence }, + asset_issuance: input.asset_issuance, + witness: TxInWitness::default(), + }); + } + } + // ..then all outputs + tx.output = match sighash { + SigHashType::All => self.tx.output.clone(), + SigHashType::Single => { + let output_iter = self.tx.output.iter() + .take(input_index + 1) // sign all outputs up to and including this one, but erase + .enumerate() // all of them except for this one + .map(|(n, out)| if n == input_index { out.clone() } else { TxOut::default() }); + output_iter.collect() + } + SigHashType::None => vec![], + _ => unreachable!() + }; + // hash the result + // cannot encode tx directly because of different consensus encoding + // of elements tx(they include witness flag even for non-witness transactions) + tx.version.consensus_encode(&mut writer)?; + tx.input.consensus_encode(&mut writer)?; + tx.output.consensus_encode(&mut writer)?; + tx.lock_time.consensus_encode(&mut writer)?; + + let sighash_arr = endian::u32_to_array_le(sighash_type.as_u32()); + sighash_arr.consensus_encode(&mut writer)?; + Ok(()) + } + + /// 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. + /// Does not take a mutable reference because it does not do any caching. + /// + /// *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 legacy_sighash( + &self, + input_index: usize, + script_pubkey: &Script, + sighash_type: SigHashType, + ) -> SigHash { + let mut engine = SigHash::engine(); + self.encode_legacy_signing_data_to(&mut engine, input_index, script_pubkey, sighash_type) + .expect("engines don't error"); + SigHash::from_engine(engine) + } + /// Calculate hash for prevouts pub fn hash_prevouts(&mut self) -> sha256d::Hash { let hash_prevout = &mut self.hash_prevouts; @@ -250,4 +365,30 @@ mod tests{ // Test a issuance test with only sighash all test_segwit_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000003e801000000000000000a0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, "0850863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352", SigHashType::All, "ea946ee417d5a16a1038b2c3b54d1b7b12a9f98c0dcb4684bf005eb1c27d0c92"); } + + + fn test_legacy_sighash(tx: &str, script: &str, input_index: usize, hash_type: SigHashType, expected_result: &str) { + let tx: Transaction = deserialize(&Vec::::from_hex(tx).unwrap()[..]).unwrap(); + let script = Script::from(Vec::::from_hex(script).unwrap()); + // A hack to parse sha256d strings are sha256 so that we don't reverse them... + let raw_expected = bitcoin::hashes::sha256::Hash::from_hex(expected_result).unwrap(); + let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap(); + let sighash_cache = SigHashCache::new(&tx); + let actual_result = sighash_cache.legacy_sighash(input_index, &script, hash_type); + assert_eq!(actual_result, expected_result); + } + + #[test] + fn test_legacy_sighashes(){ + // generated by script(example_test.py) at https://github.com/sanket1729/elements/commit/5ddfb5a749e85b71c961d29d5689d692ef7cee4b + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::All, "769ad754a77282712895475eb17251bcb8f3cc35dc13406fa1188ef2707556cf"); + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::None, "b399ca018b4fec7d94e47092b72d25983db2d0d16eaa6a672050add66077ef40"); + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::Single, "4efef74996f840ed104c0b69461f33da2e364288f3015c55b2516a68e3ee60bc"); + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::AllPlusAnyoneCanPay, "a70a59ae29f1d9f4461f12e730e5cb75d3a75312666e8d911584aebb8e4afc5c"); + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::NonePlusAnyoneCanPay, "5f3694a35f3b994639d3fb1f6214ec166f9e0721c7ab3f216e465b9b2728d834"); + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af0000000000000000000201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::SinglePlusAnyoneCanPay, "4c18486c473dc31c264c477c55e9c17d70fddb9f567c7d411ce922261577167c"); + + // Test a issuance test with only sighash all + test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000003e801000000000000000a0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::All, "9f00e1758a230aaf6c9bce777701a604f50b2ac5f2a07e1cd478d8a0e70fc195"); + } } \ No newline at end of file