From 5fc24dea33ac8524e5e404fb8acd893d871cdfbd Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Wed, 18 Dec 2019 12:40:46 +0100 Subject: [PATCH] Multiple fixes for hash types and their computing Unit test for wtxid and SegWit transactions --- Cargo.toml | 2 +- src/blockdata/block.rs | 38 +++++----- src/blockdata/transaction.rs | 33 +++++++- src/consensus/encode.rs | 26 ++++--- src/hash_types.rs | 19 ++--- src/network/message.rs | 8 +- src/network/message_blockdata.rs | 124 ++++++++++++++----------------- src/network/message_filter.rs | 10 +-- src/util/hash.rs | 59 ++++++++++----- src/util/merkleblock.rs | 31 ++++---- 10 files changed, 198 insertions(+), 152 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b9ae51b..4a828c47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ use-serde = ["hex", "serde", "bitcoin_hashes/serde", "secp256k1/serde"] [dependencies] bech32 = "0.7.1" -bitcoin_hashes = "0.7" +bitcoin_hashes = "0.7.3" bitcoinconsensus = { version = "0.17", optional = true } serde = { version = "1", optional = true } hex = { version = "=0.3.2", optional = true } diff --git a/src/blockdata/block.rs b/src/blockdata/block.rs index 044494c8..f67e8586 100644 --- a/src/blockdata/block.rs +++ b/src/blockdata/block.rs @@ -22,9 +22,9 @@ use util; use util::Error::{BlockBadTarget, BlockBadProofOfWork}; -use util::hash::{BitcoinHash, MerkleRooted, bitcoin_merkle_root}; -use hashes::{Hash, sha256d, HashEngine}; -use hash_types::{Txid, Wtxid, BlockHash, TxMerkleRoot, WitnessMerkleRoot, WitnessCommitment}; +use util::hash::{BitcoinHash, bitcoin_merkle_root}; +use hashes::{Hash, HashEngine}; +use hash_types::{Wtxid, BlockHash, TxMerkleNode, WitnessMerkleNode, WitnessCommitment}; use util::uint::Uint256; use consensus::encode::Encodable; use network::constants::Network; @@ -40,7 +40,7 @@ pub struct BlockHeader { /// Reference to the previous block in the chain pub prev_blockhash: BlockHash, /// The root hash of the merkle tree of transactions in the block - pub merkle_root: TxMerkleRoot, + pub merkle_root: TxMerkleNode, /// The timestamp of the block, as claimed by the miner pub time: u32, /// The target value below which the blockhash must lie, encoded as a @@ -93,8 +93,14 @@ impl Block { false } + /// Calculate the transaction merkle root. + pub fn merkle_root(&self) -> TxMerkleNode { + let hashes = self.txdata.iter().map(|obj| obj.txid().as_hash()); + bitcoin_merkle_root(hashes).into() + } + /// compute witness commitment for the transaction list - pub fn compute_witness_commitment (witness_root: &WitnessMerkleRoot, witness_reserved_value: &[u8]) -> WitnessCommitment { + pub fn compute_witness_commitment (witness_root: &WitnessMerkleNode, witness_reserved_value: &[u8]) -> WitnessCommitment { let mut encoder = WitnessCommitment::engine(); witness_root.consensus_encode(&mut encoder).unwrap(); encoder.input(witness_reserved_value); @@ -102,17 +108,16 @@ impl Block { } /// Merkle root of transactions hashed for witness - pub fn witness_root(&self) -> WitnessMerkleRoot { - let mut txhashes = vec!(Wtxid::default()); - txhashes.extend(self.txdata.iter().skip(1).map(|t|t.wtxid())); - let hash_value: sha256d::Hash = bitcoin_merkle_root(txhashes).into(); - hash_value.into() - } -} - -impl MerkleRooted for Block { - fn merkle_root(&self) -> TxMerkleRoot { - bitcoin_merkle_root::(self.txdata.iter().map(|obj| obj.txid().into()).collect()) + pub fn witness_root(&self) -> WitnessMerkleNode { + let hashes = self.txdata.iter().enumerate().map(|(i, t)| + if i == 0 { + // Replace the first hash with zeroes. + Wtxid::default().as_hash() + } else { + t.wtxid().as_hash() + } + ); + bitcoin_merkle_root(hashes).into() } } @@ -212,7 +217,6 @@ mod tests { use blockdata::block::{Block, BlockHeader}; use consensus::encode::{deserialize, serialize}; - use util::hash::MerkleRooted; #[test] fn block_test() { diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 356853dc..7beac7f5 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -694,7 +694,7 @@ mod tests { } #[test] - fn test_transaction() { + fn test_nonsegwit_transaction() { let hex_tx = Vec::::from_hex("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000").unwrap(); let tx: Result = deserialize(&hex_tx); assert!(tx.is_ok()); @@ -718,6 +718,37 @@ mod tests { assert_eq!(realtx.get_weight(), 193*4); } + #[test] + fn test_segwit_transaction() { + let hex_tx = Vec::::from_hex( + "02000000000101595895ea20179de87052b4046dfe6fd515860505d6511a9004cf12a1f93cac7c01000000\ + 00ffffffff01deb807000000000017a9140f3444e271620c736808aa7b33e370bd87cb5a078702483045022\ + 100fb60dad8df4af2841adc0346638c16d0b8035f5e3f3753b88db122e70c79f9370220756e6633b17fd271\ + 0e626347d28d60b0a2d6cbb41de51740644b9fb3ba7751040121028fa937ca8cba2197a37c007176ed89410\ + 55d3bcb8627d085e94553e62f057dcc00000000" + ).unwrap(); + let tx: Result = deserialize(&hex_tx); + assert!(tx.is_ok()); + let realtx = tx.unwrap(); + // All these tests aren't really needed because if they fail, the hash check at the end + // will also fail. But these will show you where the failure is so I'll leave them in. + assert_eq!(realtx.version, 2); + assert_eq!(realtx.input.len(), 1); + // In particular this one is easy to get backward -- in bitcoin hashes are encoded + // as little-endian 256-bit numbers rather than as data strings. + assert_eq!(format!("{:x}", realtx.input[0].previous_output.txid), + "7cac3cf9a112cf04901a51d605058615d56ffe6d04b45270e89d1720ea955859".to_string()); + assert_eq!(realtx.input[0].previous_output.vout, 1); + assert_eq!(realtx.output.len(), 1); + assert_eq!(realtx.lock_time, 0); + + assert_eq!(format!("{:x}", realtx.txid()), + "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string()); + assert_eq!(format!("{:x}", realtx.wtxid()), + "80b7d8a82d5d5bf92905b06f2014dd699e03837ca172e3a59d51426ebbe3e7f5".to_string()); + assert_eq!(realtx.get_weight(), 442); + } + #[test] fn tx_no_input_deserialization() { let hex_tx = Vec::::from_hex( diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs index ed37afb4..402a4b51 100644 --- a/src/consensus/encode.rs +++ b/src/consensus/encode.rs @@ -34,8 +34,8 @@ use std::borrow::Cow; use std::io::{Cursor, Read, Write}; use hashes::hex::ToHex; -use hashes::{sha256d, Hash as HashTrait}; -use hash_types::{FilterHash, TxMerkleBranch}; +use hashes::{sha256d, Hash}; +use hash_types::{BlockHash, FilterHash, TxMerkleNode}; use util::endian; use util::psbt; @@ -72,8 +72,8 @@ pub enum Error { /// The invalid checksum actual: [u8; 4], }, - /// VarInt was encoded in a non-minimal way - NonMinimalVarInt, + /// VarInt was encoded in a non-minimal way + NonMinimalVarInt, /// Network magic was unknown UnknownNetworkMagic(u32), /// Parsing error @@ -82,6 +82,8 @@ pub enum Error { UnsupportedSegwitFlag(u8), /// Unrecognized network command UnrecognizedNetworkCommand(String), + /// Invalid Inventory type + UnknownInventoryType(u32), } impl fmt::Display for Error { @@ -95,13 +97,14 @@ impl fmt::Display for Error { "allocation of oversized vector: requested {}, maximum {}", r, m), Error::InvalidChecksum { expected: ref e, actual: ref a } => write!(f, "invalid checksum: expected {}, actual {}", e.to_hex(), a.to_hex()), - Error::NonMinimalVarInt => write!(f, "non-minimal varint"), + Error::NonMinimalVarInt => write!(f, "non-minimal varint"), Error::UnknownNetworkMagic(ref m) => write!(f, "unknown network magic: {}", m), Error::ParseFailed(ref e) => write!(f, "parse failed: {}", e), Error::UnsupportedSegwitFlag(ref swflag) => write!(f, "unsupported segwit version: {}", swflag), Error::UnrecognizedNetworkCommand(ref nwcmd) => write!(f, "unrecognized network command: {}", nwcmd), + Error::UnknownInventoryType(ref tp) => write!(f, "Unknown Inventory type: {}", tp), } } } @@ -114,11 +117,12 @@ impl error::Error for Error { Error::UnexpectedNetworkMagic { .. } | Error::OversizedVectorAllocation { .. } | Error::InvalidChecksum { .. } - | Error::NonMinimalVarInt + | Error::NonMinimalVarInt | Error::UnknownNetworkMagic(..) | Error::ParseFailed(..) | Error::UnsupportedSegwitFlag(..) - | Error::UnrecognizedNetworkCommand(..) => None, + | Error::UnrecognizedNetworkCommand(..) + | Error::UnknownInventoryType(..) => None, } } @@ -591,8 +595,9 @@ macro_rules! impl_vec { } } } +impl_vec!(BlockHash); impl_vec!(FilterHash); -impl_vec!(TxMerkleBranch); +impl_vec!(TxMerkleNode); impl_vec!(Transaction); impl_vec!(TxOut); impl_vec!(TxIn); @@ -651,7 +656,7 @@ impl Decodable for Box<[u8]> { /// Do a double-SHA256 on some data and return the first 4 bytes fn sha2_checksum(data: &[u8]) -> [u8; 4] { - let checksum = ::hash(data); + let checksum = ::hash(data); [checksum[0], checksum[1], checksum[2], checksum[3]] } @@ -732,8 +737,7 @@ impl Encodable for sha256d::Hash { impl Decodable for sha256d::Hash { fn consensus_decode(d: D) -> Result { - let inner = <[u8; 32]>::consensus_decode(d)?; - Ok(sha256d::Hash::from_slice(&inner).unwrap()) + Ok(Self::from_inner(<::Inner>::consensus_decode(d)?)) } } diff --git a/src/hash_types.rs b/src/hash_types.rs index 54c51e5e..514c5309 100644 --- a/src/hash_types.rs +++ b/src/hash_types.rs @@ -32,8 +32,7 @@ macro_rules! impl_hashencode { impl Decodable for $hashtype { fn consensus_decode(d: D) -> Result { - let inner = <<$hashtype as Hash>::Inner>::consensus_decode(d)?; - Ok(Self::from_slice(&inner).unwrap()) + Ok(Self::from_inner(<<$hashtype as Hash>::Inner>::consensus_decode(d)?)) } } } @@ -49,9 +48,8 @@ hash_newtype!(ScriptHash, hash160::Hash, 20, doc="A hash of Bitcoin Script bytec hash_newtype!(WPubkeyHash, hash160::Hash, 20, doc="SegWit version of a public key hash."); hash_newtype!(WScriptHash, sha256::Hash, 32, doc="SegWit version of a Bitcoin Script bytecode hash."); -hash_newtype!(TxMerkleRoot, sha256d::Hash, 32, doc="A hash corresponding to the Merkle tree root for transactions"); -hash_newtype!(TxMerkleBranch, sha256d::Hash, 32, doc="A hash of the Merkle tree branch for transactions"); -hash_newtype!(WitnessMerkleRoot, sha256d::Hash, 32, doc="A hash corresponding to the Merkle tree root for witness data"); +hash_newtype!(TxMerkleNode, sha256d::Hash, 32, doc="A hash of the Merkle tree branch or root for transactions"); +hash_newtype!(WitnessMerkleNode, sha256d::Hash, 32, doc="A hash corresponding to the Merkle tree root for witness data"); hash_newtype!(WitnessCommitment, sha256d::Hash, 32, doc="A hash corresponding to the witness structure commitment in the coinbase transaction"); hash_newtype!(XpubIdentifier, hash160::Hash, 20, doc="XpubIdentifier as defined in BIP-32."); @@ -62,13 +60,6 @@ impl_hashencode!(Txid); impl_hashencode!(Wtxid); impl_hashencode!(SigHash); impl_hashencode!(BlockHash); -impl_hashencode!(TxMerkleRoot); -impl_hashencode!(TxMerkleBranch); -impl_hashencode!(WitnessMerkleRoot); +impl_hashencode!(TxMerkleNode); +impl_hashencode!(WitnessMerkleNode); impl_hashencode!(FilterHash); - -/// Generic trait for functions which may either take a Txid type of Wtxid type as an imput. -/// For instance, used for Merklization procedure -pub trait TxidType: Hash + Encodable + Decodable { } -impl TxidType for Txid { } -impl TxidType for Wtxid { } diff --git a/src/network/message.rs b/src/network/message.rs index ff196bdd..82c74060 100644 --- a/src/network/message.rs +++ b/src/network/message.rs @@ -341,7 +341,7 @@ mod test { use hashes::Hash as HashTrait; use network::address::Address; use super::message_network::{Reject, RejectReason, VersionMessage}; - use network::message_blockdata::{Inventory, GetBlocksMessage, GetHeadersMessage, InvType}; + use network::message_blockdata::{Inventory, GetBlocksMessage, GetHeadersMessage}; use blockdata::block::{Block, BlockHeader}; use network::message_filter::{GetCFilters, CFilter, GetCFHeaders, CFHeaders, GetCFCheckpt, CFCheckpt}; use blockdata::transaction::Transaction; @@ -362,9 +362,9 @@ mod test { NetworkMessage::Version(version_msg), NetworkMessage::Verack, NetworkMessage::Addr(vec![(45, Address::new(&([123,255,000,100], 833).into(), ServiceFlags::NETWORK))]), - NetworkMessage::Inv(vec![Inventory{inv_type: InvType::Block, hash: hash([8u8; 32]).into()}]), - NetworkMessage::GetData(vec![Inventory{inv_type: InvType::Transaction, hash: hash([45u8; 32]).into()}]), - NetworkMessage::NotFound(vec![Inventory{inv_type: InvType::Error, hash: hash([45u8; 32]).into()}]), + NetworkMessage::Inv(vec![Inventory::Block(hash([8u8; 32]).into())]), + NetworkMessage::GetData(vec![Inventory::Transaction(hash([45u8; 32]).into())]), + NetworkMessage::NotFound(vec![Inventory::Error]), NetworkMessage::GetBlocks(GetBlocksMessage::new(vec![hash([1u8; 32]).into(), hash([4u8; 32]).into()], hash([5u8; 32]).into())), NetworkMessage::GetHeaders(GetHeadersMessage::new(vec![hash([10u8; 32]).into(), hash([40u8; 32]).into()], hash([50u8; 32]).into())), NetworkMessage::MemPool, diff --git a/src/network/message_blockdata.rs b/src/network/message_blockdata.rs index 16714458..a758c3c4 100644 --- a/src/network/message_blockdata.rs +++ b/src/network/message_blockdata.rs @@ -18,25 +18,64 @@ //! Bitcoin data (blocks and transactions) around. //! -use network::constants; -use consensus::encode::{self, Decodable, Encodable}; -use hash_types::FilterHash; - use std::io; -#[derive(PartialEq, Eq, Clone, Debug, Copy)] -/// The type of an inventory object -pub enum InvType { +use hashes::sha256d; + +use network::constants; +use consensus::encode::{self, Decodable, Encodable}; +use hash_types::{BlockHash, Txid, Wtxid}; + +/// An inventory item. +#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)] +pub enum Inventory { /// Error --- these inventories can be ignored Error, /// Transaction - Transaction, + Transaction(Txid), /// Block - Block, - /// Witness Block - WitnessBlock, + Block(BlockHash), /// Witness Transaction - WitnessTransaction + WitnessTransaction(Wtxid), + /// Witness Block + WitnessBlock(BlockHash), +} + +impl Encodable for Inventory { + #[inline] + fn consensus_encode( + &self, + mut s: S, + ) -> Result { + macro_rules! encode_inv { + ($code:expr, $item:expr) => { + u32::consensus_encode(&$code, &mut s)? + + $item.consensus_encode(&mut s)? + } + } + Ok(match *self { + Inventory::Error => encode_inv!(0, sha256d::Hash::default()), + Inventory::Transaction(ref t) => encode_inv!(1, t), + Inventory::Block(ref b) => encode_inv!(2, b), + Inventory::WitnessTransaction(ref t) => encode_inv!(0x40000001, t), + Inventory::WitnessBlock(ref b) => encode_inv!(0x40000002, b), + }) + } +} + +impl Decodable for Inventory { + #[inline] + fn consensus_decode(mut d: D) -> Result { + let inv_type: u32 = Decodable::consensus_decode(&mut d)?; + Ok(match inv_type { + 0 => Inventory::Error, + 1 => Inventory::Transaction(Decodable::consensus_decode(&mut d)?), + 2 => Inventory::Block(Decodable::consensus_decode(&mut d)?), + 0x40000001 => Inventory::WitnessTransaction(Decodable::consensus_decode(&mut d)?), + 0x40000002 => Inventory::WitnessBlock(Decodable::consensus_decode(&mut d)?), + tp => return Err(encode::Error::UnknownInventoryType(tp)), + }) + } } // Some simple messages @@ -49,9 +88,9 @@ pub struct GetBlocksMessage { /// Locator hashes --- ordered newest to oldest. The remote peer will /// reply with its longest known chain, starting from a locator hash /// if possible and block 1 otherwise. - pub locator_hashes: Vec, + pub locator_hashes: Vec, /// References the block to stop at, or zero to just fetch the maximum 500 blocks - pub stop_hash: FilterHash, + pub stop_hash: BlockHash, } /// The `getheaders` message @@ -62,29 +101,14 @@ pub struct GetHeadersMessage { /// Locator hashes --- ordered newest to oldest. The remote peer will /// reply with its longest known chain, starting from a locator hash /// if possible and block 1 otherwise. - pub locator_hashes: Vec, + pub locator_hashes: Vec, /// References the header to stop at, or zero to just fetch the maximum 2000 headers - pub stop_hash: FilterHash -} - -/// An inventory object --- a reference to a Bitcoin object -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Inventory { - /// The type of object that is referenced - pub inv_type: InvType, - /// The object's hash - pub hash: FilterHash -} - -impl ::std::hash::Hash for Inventory { - fn hash(&self, state: &mut H) { - self.hash.hash(state) - } + pub stop_hash: BlockHash } impl GetBlocksMessage { /// Construct a new `getblocks` message - pub fn new(locator_hashes: Vec, stop_hash: FilterHash) -> GetBlocksMessage { + pub fn new(locator_hashes: Vec, stop_hash: BlockHash) -> GetBlocksMessage { GetBlocksMessage { version: constants::PROTOCOL_VERSION, locator_hashes: locator_hashes.clone(), @@ -97,7 +121,7 @@ impl_consensus_encoding!(GetBlocksMessage, version, locator_hashes, stop_hash); impl GetHeadersMessage { /// Construct a new `getheaders` message - pub fn new(locator_hashes: Vec, stop_hash: FilterHash) -> GetHeadersMessage { + pub fn new(locator_hashes: Vec, stop_hash: BlockHash) -> GetHeadersMessage { GetHeadersMessage { version: constants::PROTOCOL_VERSION, locator_hashes: locator_hashes, @@ -108,40 +132,6 @@ impl GetHeadersMessage { impl_consensus_encoding!(GetHeadersMessage, version, locator_hashes, stop_hash); -impl Encodable for Inventory { - #[inline] - fn consensus_encode( - &self, - mut s: S, - ) -> Result { - let inv_len = match self.inv_type { - InvType::Error => 0u32, - InvType::Transaction => 1, - InvType::Block => 2, - InvType::WitnessBlock => 0x40000002, - InvType::WitnessTransaction => 0x40000001 - }.consensus_encode(&mut s)?; - Ok(inv_len + self.hash.consensus_encode(&mut s)?) - } -} - -impl Decodable for Inventory { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let int_type: u32 = Decodable::consensus_decode(&mut d)?; - Ok(Inventory { - inv_type: match int_type { - 0 => InvType::Error, - 1 => InvType::Transaction, - 2 => InvType::Block, - // TODO do not fail here - _ => { panic!("bad inventory type field") } - }, - hash: Decodable::consensus_decode(d)? - }) - } -} - #[cfg(test)] mod tests { use super::{GetHeadersMessage, GetBlocksMessage}; diff --git a/src/network/message_filter.rs b/src/network/message_filter.rs index 0d293a3e..17d3318c 100644 --- a/src/network/message_filter.rs +++ b/src/network/message_filter.rs @@ -36,7 +36,7 @@ pub struct GetCFHeaders { /// The height of the first block in the requested range pub start_height: u32, /// The hash of the last block in the requested range - pub stop_hash: FilterHash, + pub stop_hash: BlockHash, } impl_consensus_encoding!(GetCFHeaders, filter_type, start_height, stop_hash); @@ -46,7 +46,7 @@ pub struct CFHeaders { /// Filter type for which headers are requested pub filter_type: u8, /// The hash of the last block in the requested range - pub stop_hash: FilterHash, + pub stop_hash: BlockHash, /// The filter header preceding the first block in the requested range pub previous_filter: FilterHash, /// The filter hashes for each block in the requested range @@ -60,7 +60,7 @@ pub struct GetCFCheckpt { /// Filter type for which headers are requested pub filter_type: u8, /// The hash of the last block in the requested range - pub stop_hash: FilterHash, + pub stop_hash: BlockHash, } impl_consensus_encoding!(GetCFCheckpt, filter_type, stop_hash); @@ -70,8 +70,8 @@ pub struct CFCheckpt { /// Filter type for which headers are requested pub filter_type: u8, /// The hash of the last block in the requested range - pub stop_hash: FilterHash, + pub stop_hash: BlockHash, /// The filter headers at intervals of 1,000 pub filter_headers: Vec, } -impl_consensus_encoding!(CFCheckpt, filter_type, stop_hash, filter_headers); \ No newline at end of file +impl_consensus_encoding!(CFCheckpt, filter_type, stop_hash, filter_headers); diff --git a/src/util/hash.rs b/src/util/hash.rs index 1aac8fbf..0de4731f 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -16,39 +16,64 @@ //! Utility functions related to hashing data, including merkleization use std::cmp::min; -use std::default::Default; +use std::io; use hashes::Hash; -use hash_types::{Txid, TxidType, TxMerkleRoot}; +use consensus::encode::Encodable; - -/// Any collection of objects for which a merkle root makes sense to calculate -pub trait MerkleRooted { - /// Construct a merkle tree from a collection, with elements ordered as - /// they were in the original collection, and return the merkle root. - fn merkle_root(&self) -> TxMerkleRoot; -} - -/// Calculates the merkle root of a list of txids hashes directly -pub fn bitcoin_merkle_root(data: Vec) -> TxMerkleRoot { +/// Calculates the merkle root of a list of hashes inline +/// into the allocated slice. +/// +/// In most cases, you'll want to use [bitcoin_merkle_root] instead. +pub fn bitcoin_merkle_root_inline(data: &mut [T]) -> T + where T: Hash + Encodable, + ::Engine: io::Write, +{ // Base case if data.len() < 1 { return Default::default(); } if data.len() < 2 { - return TxMerkleRoot::from_inner(data[0].into_inner()); + return T::from_inner(data[0].into_inner()); } // Recursion - let mut next = vec![]; for idx in 0..((data.len() + 1) / 2) { let idx1 = 2 * idx; let idx2 = min(idx1 + 1, data.len() - 1); - let mut encoder = TxMerkleRoot::engine(); + let mut encoder = T::engine(); data[idx1].consensus_encode(&mut encoder).unwrap(); data[idx2].consensus_encode(&mut encoder).unwrap(); - next.push(Txid::from_engine(encoder)); + data[idx] = T::from_engine(encoder); } - bitcoin_merkle_root(next) + let half_len = data.len() / 2 + data.len() % 2; + bitcoin_merkle_root_inline(&mut data[0..half_len]) +} + +/// Calculates the merkle root of an iterator of hashes. +pub fn bitcoin_merkle_root(mut iter: I) -> T + where T: Hash + Encodable, + ::Engine: io::Write, + I: ExactSizeIterator, +{ + // Base case + if iter.len() == 0 { + return Default::default(); + } + if iter.len() == 1 { + return T::from_inner(iter.nth(0).unwrap().into_inner()); + } + // Recursion + let half_len = iter.len() / 2 + iter.len() % 2; + let mut alloc = Vec::with_capacity(half_len); + while let Some(hash1) = iter.next() { + // If the size is odd, use the last element twice. + let hash2 = iter.next().unwrap_or(hash1); + let mut encoder = T::engine(); + hash1.consensus_encode(&mut encoder).unwrap(); + hash2.consensus_encode(&mut encoder).unwrap(); + alloc.push(T::from_engine(encoder)); + } + bitcoin_merkle_root_inline(&mut alloc) } /// Objects which are referred to by hash diff --git a/src/util/merkleblock.rs b/src/util/merkleblock.rs index 974a9623..3dca69d8 100644 --- a/src/util/merkleblock.rs +++ b/src/util/merkleblock.rs @@ -59,7 +59,7 @@ use std::collections::HashSet; use std::io; use hashes::Hash; -use hash_types::{Txid, TxMerkleRoot, TxMerkleBranch}; +use hash_types::{Txid, TxMerkleNode}; use blockdata::transaction::Transaction; use blockdata::constants::{MAX_BLOCK_WEIGHT, MIN_TRANSACTION_WEIGHT}; @@ -120,7 +120,7 @@ pub struct PartialMerkleTree { /// node-is-parent-of-matched-txid bits bits: Vec, /// Transaction ids and internal hashes - hashes: Vec, + hashes: Vec, } impl PartialMerkleTree { @@ -181,7 +181,7 @@ impl PartialMerkleTree { &self, matches: &mut Vec, indexes: &mut Vec, - ) -> Result { + ) -> Result { matches.clear(); indexes.clear(); // An empty set will not work @@ -221,7 +221,7 @@ impl PartialMerkleTree { if hash_used != self.hashes.len() as u32 { return Err(BadFormat("Not all hashes were consumed".to_owned())); } - Ok(TxMerkleRoot::from_inner(hash_merkle_root.into_inner())) + Ok(TxMerkleNode::from_inner(hash_merkle_root.into_inner())) } /// Helper function to efficiently calculate the number of nodes at given height @@ -232,10 +232,10 @@ impl PartialMerkleTree { } /// Calculate the hash of a node in the merkle tree (at leaf level: the txid's themselves) - fn calc_hash(&self, height: u32, pos: u32, txids: &[Txid]) -> TxMerkleBranch { + fn calc_hash(&self, height: u32, pos: u32, txids: &[Txid]) -> TxMerkleNode { if height == 0 { // Hash at height 0 is the txid itself - TxMerkleBranch::from_inner(txids[pos as usize].into_inner()) + TxMerkleNode::from_inner(txids[pos as usize].into_inner()) } else { // Calculate left hash let left = self.calc_hash(height - 1, pos * 2, txids); @@ -291,7 +291,7 @@ impl PartialMerkleTree { hash_used: &mut u32, matches: &mut Vec, indexes: &mut Vec, - ) -> Result { + ) -> Result { if *bits_used as usize >= self.bits.len() { return Err(BadFormat("Overflowed the bits array".to_owned())); } @@ -344,11 +344,11 @@ impl PartialMerkleTree { } /// Helper method to produce SHA256D(left + right) - fn parent_hash(left: TxMerkleBranch, right: TxMerkleBranch) -> TxMerkleBranch { - let mut encoder = TxMerkleBranch::engine(); + fn parent_hash(left: TxMerkleNode, right: TxMerkleNode) -> TxMerkleNode { + let mut encoder = TxMerkleNode::engine(); left.consensus_encode(&mut encoder).unwrap(); right.consensus_encode(&mut encoder).unwrap(); - TxMerkleBranch::from_engine(encoder) + TxMerkleNode::from_engine(encoder) } } @@ -370,7 +370,7 @@ impl Encodable for PartialMerkleTree { impl Decodable for PartialMerkleTree { fn consensus_decode(mut d: D) -> Result { let num_transactions: u32 = Decodable::consensus_decode(&mut d)?; - let hashes: Vec = Decodable::consensus_decode(&mut d)?; + let hashes: Vec = Decodable::consensus_decode(&mut d)?; let bytes: Vec = Decodable::consensus_decode(d)?; let mut bits: Vec = vec![false; bytes.len() * 8]; @@ -498,7 +498,7 @@ mod tests { use hashes::Hash; use hashes::hex::{FromHex, ToHex}; - use hash_types::{Txid, TxMerkleRoot, TxMerkleBranch}; + use hash_types::{Txid, TxMerkleNode}; use secp256k1::rand::prelude::*; use consensus::encode::{deserialize, serialize}; @@ -518,7 +518,8 @@ mod tests { .collect::>(); // Calculate the merkle root and height - let merkle_root_1 = bitcoin_merkle_root(txids.clone()); + let hashes = txids.iter().map(|t| t.as_hash()); + let merkle_root_1: TxMerkleNode = bitcoin_merkle_root(hashes).into(); let mut height = 1; let mut ntx = num_tx; while ntx > 1 { @@ -565,7 +566,7 @@ mod tests { // Check that it has the same merkle root as the original, and a valid one assert_eq!(merkle_root_1, merkle_root_2); - assert_ne!(merkle_root_2, TxMerkleRoot::default()); + assert_ne!(merkle_root_2, TxMerkleNode::default()); // check that it contains the matched transactions (in the same order!) assert_eq!(match_txid1, match_txid2); @@ -701,7 +702,7 @@ mod tests { let hashes = &mut self.hashes; let mut hash = hashes[n].into_inner(); hash[(bit >> 3) as usize] ^= 1 << (bit & 7); - hashes[n] = TxMerkleBranch::from_slice(&hash).unwrap(); + hashes[n] = TxMerkleNode::from_slice(&hash).unwrap(); } }