From c691d0e382713c862a4fae14f5be4438e18b2f36 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 6 Dec 2017 16:46:31 +0000 Subject: [PATCH] util: add Sha256dEncoder to allow streaming data into a hash This is needed to for a sane BIP143 implementation. Should be exactly equivalent to serializing data into a vector then hashing that vector for all types. --- Cargo.toml | 2 +- src/blockdata/transaction.rs | 7 +- src/util/hash.rs | 122 ++++++++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9b190133..bfde24e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitcoin" -version = "0.10.2" +version = "0.10.3" authors = ["Andrew Poelstra "] license = "CC0-1.0" homepage = "https://github.com/apoelstra/rust-bitcoin/" diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index 584777b3..f181ba28 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -191,8 +191,11 @@ impl Transaction { impl BitcoinHash for Transaction { fn bitcoin_hash(&self) -> Sha256dHash { - use network::serialize::serialize; - Sha256dHash::from_data(&serialize(self).unwrap()) + use util::hash::Sha256dEncoder; + + let mut enc = Sha256dEncoder::new(); + self.consensus_encode(&mut enc).unwrap(); + enc.into_hash() } } diff --git a/src/util/hash.rs b/src/util/hash.rs index cbd32547..cf6748f9 100644 --- a/src/util/hash.rs +++ b/src/util/hash.rs @@ -24,12 +24,13 @@ use std::io::Cursor; use std::mem; use serde; +use byteorder::{LittleEndian, WriteBytesExt}; use crypto::digest::Digest; use crypto::sha2::Sha256; use crypto::ripemd160::Ripemd160; use network::encodable::{ConsensusDecodable, ConsensusEncodable}; -use network::serialize::{RawEncoder, BitcoinHash}; +use network::serialize::{SimpleEncoder, RawEncoder, BitcoinHash}; use util::uint::Uint256; /// Hex deserialization error @@ -64,6 +65,9 @@ impl error::Error for HexError { pub struct Sha256dHash([u8; 32]); impl_array_newtype!(Sha256dHash, u8, 32); +/// An object that allows serializing data into a sha256d +pub struct Sha256dEncoder(Sha256); + /// A RIPEMD-160 hash pub struct Ripemd160Hash([u8; 20]); impl_array_newtype!(Ripemd160Hash, u8, 20); @@ -84,6 +88,84 @@ pub struct Hash48((u8, u8, u8, u8, u8, u8)); #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Hash64((u8, u8, u8, u8, u8, u8, u8, u8)); +impl Sha256dEncoder { + /// Create a new encoder + pub fn new() -> Sha256dEncoder { + Sha256dEncoder(Sha256::new()) + } + + /// Extract the hash from an encoder + pub fn into_hash(mut self) -> Sha256dHash { + let mut second_sha = Sha256::new(); + let mut tmp = [0; 32]; + self.0.result(&mut tmp); + second_sha.input(&tmp); + second_sha.result(&mut tmp); + Sha256dHash(tmp) + } +} + +impl SimpleEncoder for Sha256dEncoder { + type Error = (); + + fn emit_u64(&mut self, v: u64) -> Result<(), ()> { + let mut data = [0; 8]; + (&mut data[..]).write_u64::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_u32(&mut self, v: u32) -> Result<(), ()> { + let mut data = [0; 4]; + (&mut data[..]).write_u32::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_u16(&mut self, v: u16) -> Result<(), ()> { + let mut data = [0; 2]; + (&mut data[..]).write_u16::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_i64(&mut self, v: i64) -> Result<(), ()> { + let mut data = [0; 8]; + (&mut data[..]).write_i64::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_i32(&mut self, v: i32) -> Result<(), ()> { + let mut data = [0; 4]; + (&mut data[..]).write_i32::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_i16(&mut self, v: i16) -> Result<(), ()> { + let mut data = [0; 2]; + (&mut data[..]).write_i16::(v).unwrap(); + self.0.input(&data); + Ok(()) + } + + fn emit_i8(&mut self, v: i8) -> Result<(), ()> { + self.0.input(&[v as u8]); + Ok(()) + } + + fn emit_u8(&mut self, v: u8) -> Result<(), ()> { + self.0.input(&[v]); + Ok(()) + } + + fn emit_bool(&mut self, v: bool) -> Result<(), ()> { + self.0.input(&[if v {1} else {0}]); + Ok(()) + } +} + impl Ripemd160Hash { /// Create a hash by hashing some data pub fn from_data(data: &[u8]) -> Ripemd160Hash { @@ -350,8 +432,9 @@ mod tests { use num::FromPrimitive; use strason; + use network::encodable::VarInt; use network::serialize::{serialize, deserialize}; - use util::hash::Sha256dHash; + use super::*; #[test] fn test_sha256d() { @@ -373,6 +456,41 @@ mod tests { "56944C5D3F98413EF45CF54545538103CC9F298E0575820AD3591376E2E0F65D"); } + #[test] + fn sha256d_encoder() { + let test = vec![true, false, true, true, false]; + let mut enc = Sha256dEncoder::new(); + assert!(test.consensus_encode(&mut enc).is_ok()); + assert_eq!(enc.into_hash(), Sha256dHash::from_data(&serialize(&test).unwrap())); + + macro_rules! array_encode_test ( + ($ty:ty) => ({ + // try serializing the whole array + let test: [$ty; 1000] = [1; 1000]; + let mut enc = Sha256dEncoder::new(); + assert!((&test[..]).consensus_encode(&mut enc).is_ok()); + assert_eq!(enc.into_hash(), Sha256dHash::from_data(&serialize(&test[..]).unwrap())); + + // try doing it just one object at a time + let mut enc = Sha256dEncoder::new(); + assert!(VarInt(test.len() as u64).consensus_encode(&mut enc).is_ok()); + for obj in &test[..] { + assert!(obj.consensus_encode(&mut enc).is_ok()); + } + assert_eq!(enc.into_hash(), Sha256dHash::from_data(&serialize(&test[..]).unwrap())); + }) + ); + + array_encode_test!(u64); + array_encode_test!(u32); + array_encode_test!(u16); + array_encode_test!(u8); + array_encode_test!(i64); + array_encode_test!(i32); + array_encode_test!(i16); + array_encode_test!(i8); + } + #[test] fn test_consenus_encode_roundtrip() { let hash = Sha256dHash::from_data(&[]);