Fix for new partial-move rules; swap hash le_hex_string and be_hex_string

I noticed that the little/big endian hex string functions for Sha256dHash
did not match my intuition. What we should have is that the raw bytes
correspond to a little-endian representation (since we convert to Uint256
by transmuting, and Uint256's have little-endian representation) while
the reversed raw bytes are big-endian.

This means that the output from `sha256sum` is "little-endian", while the
standard "zeros on the left" output from bitcoind is "big-endian". This
is correct since we think of blockhashes as being "below the target" when
they have lots of zeros on the left, and we also notice that when hashing
Bitcoin objects with sha256sum that the output hashes are always reversed.

These two functions le_hex_string and be_hex_string should really not be
used outside of the library; the Encodable trait should give access to a
"big endian" representation while ConsensusEncodable gives access to a
"little endian" representation. That way we describe the split in terms
of user-facing/consensus code rather than big/little endian code, which
is a better way of thinking about it. After all, a hash is a collection
of bytes, not a number --- it doesn't have an intrinsic endianness.

Oh, and by the way, to compute a sha256d hash from sha256sum, you do

  echo -n 'data' | sha256sum | xxd -r -p | sha256dsum
This commit is contained in:
Andrew Poelstra 2014-08-03 14:52:59 -07:00
parent 474d04d154
commit 2986e1f983
5 changed files with 41 additions and 27 deletions

View File

@ -139,7 +139,7 @@ mod test {
assert_eq!(gen.output[0].value, 50 * COIN_VALUE);
assert_eq!(gen.lock_time, 0);
assert_eq!(gen.bitcoin_hash().le_hex_string(),
assert_eq!(gen.bitcoin_hash().be_hex_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".to_string());
}
@ -149,12 +149,12 @@ mod test {
assert_eq!(gen.header.version, 1);
assert_eq!(gen.header.prev_blockhash.as_slice(), zero_hash().as_slice());
assert_eq!(gen.header.merkle_root.le_hex_string(),
assert_eq!(gen.header.merkle_root.be_hex_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".to_string());
assert_eq!(gen.header.time, 1231006505);
assert_eq!(gen.header.bits, 0x1d00ffff);
assert_eq!(gen.header.nonce, 2083236893);
assert_eq!(gen.header.bitcoin_hash().le_hex_string(),
assert_eq!(gen.header.bitcoin_hash().be_hex_string(),
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f".to_string());
}
@ -163,12 +163,12 @@ mod test {
let gen = genesis_block(BitcoinTestnet);
assert_eq!(gen.header.version, 1);
assert_eq!(gen.header.prev_blockhash.as_slice(), zero_hash().as_slice());
assert_eq!(gen.header.merkle_root.le_hex_string(),
assert_eq!(gen.header.merkle_root.be_hex_string(),
"4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b".to_string());
assert_eq!(gen.header.time, 1296688602);
assert_eq!(gen.header.bits, 0x1d00ffff);
assert_eq!(gen.header.nonce, 414098458);
assert_eq!(gen.header.bitcoin_hash().le_hex_string(),
assert_eq!(gen.header.bitcoin_hash().be_hex_string(),
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943".to_string());
}
}

View File

@ -111,13 +111,13 @@ mod tests {
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!(realtx.input[0].prev_hash.le_hex_string(),
assert_eq!(realtx.input[0].prev_hash.be_hex_string(),
"ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string());
assert_eq!(realtx.input[0].prev_index, 1);
assert_eq!(realtx.output.len(), 1);
assert_eq!(realtx.lock_time, 0);
assert_eq!(realtx.bitcoin_hash().le_hex_string(),
assert_eq!(realtx.bitcoin_hash().be_hex_string(),
"a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string());
}
}

View File

@ -164,7 +164,7 @@ impl UtxoSet {
// See bitcoind commit `ab91bf39` and BIP30.
match self.add_utxos(tx) {
Some(mut replace) => {
let blockhash = block.header.bitcoin_hash().le_hex_string();
let blockhash = block.header.bitcoin_hash().be_hex_string();
if blockhash == "00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec".to_string() ||
blockhash == "00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721".to_string() {
// For these specific blocks, overwrite the old UTXOs.

View File

@ -108,13 +108,13 @@ impl Sha256dHash {
from_bytes(self.as_slice())
}
/// Converts a hash to a Uint256, interpreting it as a little endian number.
/// Converts a hash to a little-endian Uint256
pub fn into_uint256(self) -> Uint256 {
let Sha256dHash(data) = self;
unsafe { Uint256(transmute(data)) }
}
/// Converts a hash to a Uint128, interpreting it as a little endian number.
/// Converts a hash to a little-endian Uint128, using only the "low" bytes
pub fn into_uint128(self) -> Uint128 {
let Sha256dHash(data) = self;
// TODO: this function won't work correctly on big-endian machines
@ -128,7 +128,7 @@ impl Sha256dHash {
pub fn le_hex_string(&self) -> String {
let &Sha256dHash(data) = self;
let mut ret = String::with_capacity(64);
for i in range(0u, 32).rev() {
for i in range(0u, 32) {
ret.push_char(from_digit((data[i] / 0x10) as uint, 16).unwrap());
ret.push_char(from_digit((data[i] & 0x0f) as uint, 16).unwrap());
}
@ -139,7 +139,7 @@ impl Sha256dHash {
pub fn be_hex_string(&self) -> String {
let &Sha256dHash(data) = self;
let mut ret = String::with_capacity(64);
for i in range(0u, 32) {
for i in range(0u, 32).rev() {
ret.push_char(from_digit((data[i] / 0x10) as uint, 16).unwrap());
ret.push_char(from_digit((data[i] & 0x0f) as uint, 16).unwrap());
}
@ -293,20 +293,32 @@ mod tests {
use collections::bitv::from_bytes;
use std::io::MemWriter;
use std::str::from_utf8;
use serialize::Encodable;
use serialize::json;
use network::serialize::{serialize, deserialize};
use util::hash::Sha256dHash;
use util::misc::hex_bytes;
#[test]
fn test_sha256d() {
// nb the 5df6... output is the one you get from sha256sum. this is the
// "little-endian" hex string since it matches the in-memory representation
// of a Uint256 (which is little-endian) after transmutation
assert_eq!(Sha256dHash::from_data(&[]).as_slice(),
hex_bytes("5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456").unwrap().as_slice());
assert_eq!(Sha256dHash::from_data(&[]).le_hex_string(),
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456".to_string());
assert_eq!(Sha256dHash::from_data(&[]).be_hex_string(),
"56944c5d3f98413ef45cf54545538103cc9f298e0575820ad3591376e2e0f65d".to_string());
assert_eq!(Sha256dHash::from_data(b"TEST").as_slice(),
hex_bytes("d7bd34bfe44a18d2aa755a344fe3e6b06ed0473772e6dfce16ac71ba0b0a241c").unwrap().as_slice());
}
#[test]
fn test_consenus_encode_roundtrip() {
let hash = Sha256dHash::from_data(&[]);
let serial = serialize(&hash).unwrap();
let deserial = deserialize(serial).unwrap();
assert_eq!(hash, deserial);
}
#[test]

View File

@ -238,17 +238,18 @@ impl<K:BitArray+Eq+Zero+One+BitXor<K,K>+Shl<uint,K>+Shr<uint,K>, V> PatriciaTree
}
match (tree.child_l.take(), tree.child_r.take()) {
(Some(_), Some(_)) => unreachable!(),
(Some(consolidate), None) | (None, Some(consolidate)) => {
tree.data = consolidate.data;
tree.child_l = consolidate.child_l;
tree.child_r = consolidate.child_r;
(Some(box PatriciaTree { data, child_l, child_r, skip_prefix, skip_len }), None) |
(None, Some(box PatriciaTree { data, child_l, child_r, skip_prefix, skip_len })) => {
tree.data = data;
tree.child_l = child_l;
tree.child_r = child_r;
let new_bit = if bit { let ret: K = One::one();
ret << (tree.skip_len as uint) }
else { Zero::zero() };
tree.skip_prefix = tree.skip_prefix +
new_bit +
(consolidate.skip_prefix << (1 + tree.skip_len as uint));
tree.skip_len += 1 + consolidate.skip_len;
(skip_prefix << (1 + tree.skip_len as uint));
tree.skip_len += 1 + skip_len;
return (false, ret);
}
// No children means this node is deletable
@ -295,17 +296,18 @@ impl<K:BitArray+Eq+Zero+One+BitXor<K,K>+Shl<uint,K>+Shr<uint,K>, V> PatriciaTree
return (false, ret);
}
// One child? Consolidate
(bit, Some(consolidate), None) | (bit, None, Some(consolidate)) => {
tree.data = consolidate.data;
tree.child_l = consolidate.child_l;
tree.child_r = consolidate.child_r;
(bit, Some(box PatriciaTree { data, child_l, child_r, skip_prefix, skip_len }), None) |
(bit, None, Some(box PatriciaTree { data, child_l, child_r, skip_prefix, skip_len })) => {
tree.data = data;
tree.child_l = child_l;
tree.child_r = child_r;
let new_bit = if bit { let ret: K = One::one();
ret << (tree.skip_len as uint) }
else { Zero::zero() };
tree.skip_prefix = tree.skip_prefix +
new_bit +
(consolidate.skip_prefix << (1 + tree.skip_len as uint));
tree.skip_len += 1 + consolidate.skip_len;
(skip_prefix << (1 + tree.skip_len as uint));
tree.skip_len += 1 + skip_len;
return (false, ret);
}
// No children? Delete