hashes: drop the `all_zeros` method on arbitrary hashes

Manually implement it for Wtxid, Txid and BlockHash, where the all-zero
"hash" has a consensus meaning. But in general we should not be
implementing this method unless we have a good reason to do so. It can
be emulated or implemeted in terms of from_byte_array.

The use of Wtxid::all_zeros is obscure and specific enough that I am
tempted to drop it. But for txid and blockhash, the 0 hash appears in
actual blockdata and we should keep it.

All other uses of all_zeros were either in test code or in places where
the specific hash was not important and [u8; 32] was a more appropriate
type.
This commit is contained in:
Andrew Poelstra 2024-06-16 15:34:18 +00:00
parent 9f8797f486
commit 8869f35a69
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
12 changed files with 38 additions and 50 deletions

View File

@ -28,6 +28,13 @@ hashes::hash_newtype! {
pub struct WitnessCommitment(sha256d::Hash); pub struct WitnessCommitment(sha256d::Hash);
} }
impl_hashencode!(BlockHash); impl_hashencode!(BlockHash);
impl BlockHash {
/// The "all zeros" blockhash.
///
/// This is not the hash of a real block. It is used as the previous blockhash
/// of the genesis block and in other placeholder contexts.
pub fn all_zeros() -> Self { Self::from_byte_array([0; 32]) }
}
/// Bitcoin block header. /// Bitcoin block header.
/// ///

View File

@ -49,6 +49,24 @@ hashes::hash_newtype! {
impl_hashencode!(Txid); impl_hashencode!(Txid);
impl_hashencode!(Wtxid); impl_hashencode!(Wtxid);
impl Txid {
/// The "all zeros" TXID.
///
/// This is used as the "txid" of the dummy input of a coinbase transaction. It is
/// not a real TXID and should not be used in other contexts.
pub fn all_zeros() -> Self { Self::from_byte_array([0; 32]) }
}
impl Wtxid {
/// The "all zeros" wTXID.
///
/// This is used as the wTXID for the coinbase transaction when constructing blocks,
/// since the coinbase transaction contains a commitment to all transactions' wTXIDs
/// but naturally cannot commit to its own. It is not a real wTXID and should not be
/// used in other contexts.
pub fn all_zeros() -> Self { Self::from_byte_array([0; 32]) }
}
/// The marker MUST be a 1-byte zero value: 0x00. (BIP-141) /// The marker MUST be a 1-byte zero value: 0x00. (BIP-141)
const SEGWIT_MARKER: u8 = 0x00; const SEGWIT_MARKER: u8 = 0x00;
/// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141) /// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141)

View File

@ -781,7 +781,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
value: Amount, value: Amount,
sighash_type: EcdsaSighashType, sighash_type: EcdsaSighashType,
) -> Result<(), SigningDataError<transaction::InputsIndexError>> { ) -> Result<(), SigningDataError<transaction::InputsIndexError>> {
let zero_hash = sha256d::Hash::all_zeros(); let zero_hash = [0; 32];
let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag(); let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();
@ -819,7 +819,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
let hash = LegacySighash::from_engine(single_enc); let hash = LegacySighash::from_engine(single_enc);
writer.write_all(hash.as_byte_array())?; writer.write_all(hash.as_byte_array())?;
} else { } else {
writer.write_all(zero_hash.as_byte_array())?; writer.write_all(&zero_hash)?;
} }
self.tx.borrow().lock_time.consensus_encode(writer)?; self.tx.borrow().lock_time.consensus_encode(writer)?;

View File

@ -603,7 +603,7 @@ mod tests {
// Check that it has the same merkle root as the original, and a valid one // Check that it has the same merkle root as the original, and a valid one
assert_eq!(merkle_root_1, merkle_root_2); assert_eq!(merkle_root_1, merkle_root_2);
assert_ne!(merkle_root_2, TxMerkleNode::all_zeros()); assert_ne!(merkle_root_2, TxMerkleNode::from_byte_array([0; 32]));
// check that it contains the matched transactions (in the same order!) // check that it contains the matched transactions (in the same order!)
assert_eq!(match_txid1, match_txid2); assert_eq!(match_txid1, match_txid2);

View File

@ -5,7 +5,6 @@
//! This module describes network messages which are used for passing //! This module describes network messages which are used for passing
//! Bitcoin data (blocks and transactions) around. //! Bitcoin data (blocks and transactions) around.
use hashes::sha256d;
use io::{BufRead, Write}; use io::{BufRead, Write};
use crate::block::BlockHash; use crate::block::BlockHash;
@ -67,7 +66,7 @@ impl Encodable for Inventory {
}; };
} }
Ok(match *self { Ok(match *self {
Inventory::Error => encode_inv!(0, sha256d::Hash::all_zeros()), Inventory::Error => encode_inv!(0, [0; 32]),
Inventory::Transaction(ref t) => encode_inv!(1, t), Inventory::Transaction(ref t) => encode_inv!(1, t),
Inventory::Block(ref b) => encode_inv!(2, b), Inventory::Block(ref b) => encode_inv!(2, b),
Inventory::CompactBlock(ref b) => encode_inv!(4, b), Inventory::CompactBlock(ref b) => encode_inv!(4, b),

View File

@ -1795,7 +1795,7 @@ mod tests {
let current = Header { let current = Header {
version: Version::ONE, version: Version::ONE,
prev_blockhash: BlockHash::all_zeros(), prev_blockhash: BlockHash::all_zeros(),
merkle_root: TxMerkleNode::all_zeros(), merkle_root: TxMerkleNode::from_byte_array([0; 32]),
time: 1599332177, time: 1599332177,
bits: epoch_start.bits, bits: epoch_start.bits,
nonce: epoch_start.nonce, nonce: epoch_start.nonce,
@ -1817,7 +1817,7 @@ mod tests {
let epoch_start = Header { let epoch_start = Header {
version: Version::ONE, version: Version::ONE,
prev_blockhash: BlockHash::all_zeros(), prev_blockhash: BlockHash::all_zeros(),
merkle_root: TxMerkleNode::all_zeros(), merkle_root: TxMerkleNode::from_byte_array([0; 32]),
time: 1599332844, time: 1599332844,
bits: starting_bits, bits: starting_bits,
nonce: 0, nonce: 0,
@ -1827,7 +1827,7 @@ mod tests {
let current = Header { let current = Header {
version: Version::ONE, version: Version::ONE,
prev_blockhash: BlockHash::all_zeros(), prev_blockhash: BlockHash::all_zeros(),
merkle_root: TxMerkleNode::all_zeros(), merkle_root: TxMerkleNode::from_byte_array([0; 32]),
time: 1600591200, time: 1600591200,
bits: starting_bits, bits: starting_bits,
nonce: 0, nonce: 0,

View File

@ -404,6 +404,7 @@ mod tests {
#[test] #[test]
fn taptree_hidden() { fn taptree_hidden() {
let dummy_hash = TapNodeHash::from_byte_array([0x12; 32]);
let mut builder = compose_taproot_builder(0x51, &[2, 2, 2]); let mut builder = compose_taproot_builder(0x51, &[2, 2, 2]);
builder = builder builder = builder
.add_leaf_with_ver( .add_leaf_with_ver(
@ -412,7 +413,7 @@ mod tests {
LeafVersion::from_consensus(0xC2).unwrap(), LeafVersion::from_consensus(0xC2).unwrap(),
) )
.unwrap(); .unwrap();
builder = builder.add_hidden_node(3, TapNodeHash::all_zeros()).unwrap(); builder = builder.add_hidden_node(3, dummy_hash).unwrap();
assert!(TapTree::try_from(builder).is_err()); assert!(TapTree::try_from(builder).is_err());
} }

View File

@ -147,11 +147,6 @@ impl<T: Hash> Hash for Hmac<T> {
fn as_byte_array(&self) -> &Self::Bytes { self.0.as_byte_array() } fn as_byte_array(&self) -> &Self::Bytes { self.0.as_byte_array() }
fn from_byte_array(bytes: T::Bytes) -> Self { Hmac(T::from_byte_array(bytes)) } fn from_byte_array(bytes: T::Bytes) -> Self { Hmac(T::from_byte_array(bytes)) }
fn all_zeros() -> Self {
let zeros = T::all_zeros();
Hmac(zeros)
}
} }
#[cfg(feature = "serde")] #[cfg(feature = "serde")]

View File

@ -119,8 +119,6 @@ macro_rules! hash_trait_impls {
fn as_byte_array(&self) -> &Self::Bytes { self.as_byte_array() } fn as_byte_array(&self) -> &Self::Bytes { self.as_byte_array() }
fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) } fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) }
fn all_zeros() -> Self { Self::all_zeros() }
} }
} }
} }
@ -218,13 +216,6 @@ macro_rules! hash_type {
pub const fn from_byte_array(bytes: [u8; $bits / 8]) -> Self { pub const fn from_byte_array(bytes: [u8; $bits / 8]) -> Self {
Self::internal_new(bytes) Self::internal_new(bytes)
} }
/// Returns an all zero hash.
///
/// An all zeros hash is a made up construct because there is not a known input that can create
/// it, however it is used in various places in Bitcoin e.g., the Bitcoin genesis block's
/// previous blockhash and the coinbase transaction's outpoint txid.
pub const fn all_zeros() -> Self { Hash::internal_new([0x00; $bits / 8]) }
} }
#[cfg(feature = "schemars")] #[cfg(feature = "schemars")]

View File

@ -214,13 +214,6 @@ pub trait Hash:
/// Constructs a hash from the underlying byte array. /// Constructs a hash from the underlying byte array.
fn from_byte_array(bytes: Self::Bytes) -> Self; fn from_byte_array(bytes: Self::Bytes) -> Self;
/// Returns an all zero hash.
///
/// An all zeros hash is a made up construct because there is not a known input that can create
/// it, however it is used in various places in Bitcoin e.g., the Bitcoin genesis block's
/// previous blockhash and the coinbase transaction's outpoint txid.
fn all_zeros() -> Self;
} }
/// Attempted to create a hash from an invalid length slice. /// Attempted to create a hash from an invalid length slice.

View File

@ -117,13 +117,6 @@ where
/// Constructs a hash from the underlying byte array. /// Constructs a hash from the underlying byte array.
pub const fn from_byte_array(bytes: [u8; 32]) -> Self { Self::internal_new(bytes) } pub const fn from_byte_array(bytes: [u8; 32]) -> Self { Self::internal_new(bytes) }
/// Returns an all zero hash.
///
/// An all zeros hash is a made up construct because there is not a known input that can create
/// it, however it is used in various places in Bitcoin e.g., the Bitcoin genesis block's
/// previous blockhash and the coinbase transaction's outpoint txid.
pub fn all_zeros() -> Self { Hash::internal_new([0x00; 32]) }
} }
impl<T: Tag> Copy for Hash<T> {} impl<T: Tag> Copy for Hash<T> {}

View File

@ -263,17 +263,6 @@ macro_rules! hash_newtype {
pub const fn from_byte_array(bytes: <$hash as $crate::Hash>::Bytes) -> Self { pub const fn from_byte_array(bytes: <$hash as $crate::Hash>::Bytes) -> Self {
$newtype(<$hash>::from_byte_array(bytes)) $newtype(<$hash>::from_byte_array(bytes))
} }
/// Returns an all zero hash.
///
/// An all zeros hash is a made up construct because there is not a known input that can create
/// it, however it is used in various places in Bitcoin e.g., the Bitcoin genesis block's
/// previous blockhash and the coinbase transaction's outpoint txid.
pub fn all_zeros() -> Self {
let zeros = <$hash>::all_zeros();
$newtype(zeros)
}
} }
impl $crate::_export::_core::convert::From<$hash> for $newtype { impl $crate::_export::_core::convert::From<$hash> for $newtype {
@ -309,8 +298,6 @@ macro_rules! hash_newtype {
fn as_byte_array(&self) -> &Self::Bytes { self.as_byte_array() } fn as_byte_array(&self) -> &Self::Bytes { self.as_byte_array() }
fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) } fn from_byte_array(bytes: Self::Bytes) -> Self { Self::from_byte_array(bytes) }
fn all_zeros() -> Self { Self::all_zeros() }
} }
impl $crate::_export::_core::str::FromStr for $newtype { impl $crate::_export::_core::str::FromStr for $newtype {
@ -462,6 +449,10 @@ mod test {
struct TestHash(crate::sha256d::Hash); struct TestHash(crate::sha256d::Hash);
} }
impl TestHash {
fn all_zeros() -> Self { Self::from_byte_array([0; 32]) }
}
#[test] #[test]
fn display() { fn display() {
let want = "0000000000000000000000000000000000000000000000000000000000000000"; let want = "0000000000000000000000000000000000000000000000000000000000000000";