primitives: Remove hashes from the public API
What changed, and why it matters
This is a large internal refactoring of the rust-bitcoin library. It moves Bitcoin hash wrapper types (like transaction IDs and block hashes) into a new private module so that the lower-level `hashes` crate no longer appears directly in the public API. The visible behavior of the library is intended to stay the same. There is no obvious security vulnerability introduced, but any wide-reaching refactor carries a small risk of accidental behavioral changes in serialization or parsing.
Treat as a normal code-quality/API review. Verify that the new manual `Encodable`/`Decodable` implementations produce byte-identical output to the previous macro-generated ones for all affected hash types, and that the `include!("./generic.rs")` pattern correctly preserves `DISPLAY_BACKWARD`, serde, hex, and arbitrary trait behavior. No urgent security action is indicated.
Security signals we found
Large refactor touching consensus-critical hash types and their serialization
Manual replacement of macro-generated Encodable/Decodable impls for block/transaction/merkle hash types
New generic.rs included into multiple modules via include! — shared code path for hash type behavior
No explicit security claim or bug fix in commit message
Evidence from the diff
The commit removes hashes::hash_newtype! macro usage from primitives and instead defines wrapper types in a new private hash_types module, sharing implementation via include!("./generic.rs"). It adds explicit Encodable/Decodable implementations in the bitcoin crate for BlockHash, Txid, Wtxid, TxMerkleNode, and WitnessMerkleNode, replacing the previous impl_hashencode! macro. The change is API-shaping rather than logic-changing, but the manual consensus encoding implementations and the include!-based generic code are new surfaces where a mistake could affect consensus serialization.
Changed components
primitives/src/hash_types/*primitives/src/block.rsprimitives/src/merkle_tree.rsprimitives/src/script/mod.rsprimitives/src/transaction.rsbitcoin/src/blockdata/block.rsbitcoin/src/blockdata/transaction.rsbitcoin/src/merkle_tree/mod.rsInspect captured patch +823 / −293
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 0912d2f5..f84cc826 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -34,7 +34,17 @@ pub use units::block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInter
#[doc(hidden)]
pub type BlockInterval = BlockHeightInterval;
-internal_macros::impl_hashencode!(BlockHash);
+impl Encodable for BlockHash {
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ self.to_byte_array().consensus_encode(w)
+ }
+}
+
+impl Decodable for BlockHash {
+ fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ Ok(BlockHash::from_byte_array(<[u8; 32]>::consensus_decode(r)?))
+ }
+}
#[rustfmt::skip]
internal_macros::impl_consensus_encoding!(Header, version, prev_blockhash, merkle_root, time, bits, nonce);
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 2eba785e..55da0114 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -34,8 +34,29 @@ use crate::{internal_macros, Amount, FeeRate, Sequence, SignedAmount};
#[doc(inline)]
pub use primitives::transaction::{OutPoint, ParseOutPointError, Transaction, Ntxid, Txid, Wtxid, Version, TxIn, TxOut};
-internal_macros::impl_hashencode!(Txid);
-internal_macros::impl_hashencode!(Wtxid);
+impl Encodable for Txid {
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ self.to_byte_array().consensus_encode(w)
+ }
+}
+
+impl Decodable for Txid {
+ fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ Ok(Txid::from_byte_array(<[u8; 32]>::consensus_decode(r)?))
+ }
+}
+
+impl Encodable for Wtxid {
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ self.to_byte_array().consensus_encode(w)
+ }
+}
+
+impl Decodable for Wtxid {
+ fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ Ok(Wtxid::from_byte_array(<[u8; 32]>::consensus_decode(r)?))
+ }
+}
internal_macros::define_extension_trait! {
/// Extension functionality for the [`Txid`] type.
diff --git a/bitcoin/src/merkle_tree/mod.rs b/bitcoin/src/merkle_tree/mod.rs
index 24b8a309..965604e0 100644
--- a/bitcoin/src/merkle_tree/mod.rs
+++ b/bitcoin/src/merkle_tree/mod.rs
@@ -17,18 +17,42 @@
mod block;
use hashes::{sha256d, HashEngine as _};
+use io::{BufRead, Write};
use crate::prelude::Vec;
use crate::transaction::TxIdentifier;
-use crate::{internal_macros, Txid, Wtxid};
+use crate::{Txid, Wtxid};
#[rustfmt::skip]
#[doc(inline)]
pub use self::block::{MerkleBlock, MerkleBlockError, PartialMerkleTree};
-pub use primitives::merkle_tree::{TxMerkleNode, WitnessMerkleNode};
+pub use primitives::{TxMerkleNode, WitnessMerkleNode};
-internal_macros::impl_hashencode!(TxMerkleNode);
-internal_macros::impl_hashencode!(WitnessMerkleNode);
+use crate::consensus::{encode, Decodable, Encodable};
+
+impl Encodable for TxMerkleNode {
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ self.to_byte_array().consensus_encode(w)
+ }
+}
+
+impl Decodable for TxMerkleNode {
+ fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ Ok(TxMerkleNode::from_byte_array(<[u8; 32]>::consensus_decode(r)?))
+ }
+}
+
+impl Encodable for WitnessMerkleNode {
+ fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
+ self.to_byte_array().consensus_encode(w)
+ }
+}
+
+impl Decodable for WitnessMerkleNode {
+ fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
+ Ok(WitnessMerkleNode::from_byte_array(<[u8; 32]>::consensus_decode(r)?))
+ }
+}
/// A node in a Merkle tree of transactions or witness data within a block.
///
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index b4f4d9c0..b19f5b27 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -29,6 +29,9 @@ pub use units::block::{BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInter
#[doc(no_inline)]
pub use units::block::TooBigForRelativeHeightError;
+#[doc(inline)]
+pub use crate::hash_types::{BlockHash, BlockHashEncoder, WitnessCommitment};
+
/// Marker for whether or not a block has been validated.
///
/// We define valid as:
@@ -348,13 +351,6 @@ impl Default for Version {
fn default() -> Version { Self::NO_SOFT_FORK_SIGNALLING }
}
-hashes::hash_newtype! {
- /// A bitcoin block hash.
- pub struct BlockHash(sha256d::Hash);
- /// A hash corresponding to the witness structure commitment in the coinbase transaction.
- pub struct WitnessCommitment(sha256d::Hash);
-}
-
encoding::encoder_newtype! {
/// The encoder for the [`Version`] type.
pub struct VersionEncoder(encoding::ArrayEncoder<4>);
@@ -369,30 +365,6 @@ impl Encodable for Version {
}
}
-#[cfg(feature = "hex")]
-hashes::impl_hex_for_newtype!(BlockHash, WitnessCommitment);
-#[cfg(not(feature = "hex"))]
-hashes::impl_debug_only_for_newtype!(BlockHash, WitnessCommitment);
-#[cfg(feature = "serde")]
-hashes::impl_serde_for_newtype!(BlockHash, WitnessCommitment);
-
-impl BlockHash {
- /// Dummy hash used as the previous blockhash of the genesis block.
- pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]);
-}
-
-encoding::encoder_newtype! {
- /// The encoder for the [`BlockHash`] type.
- pub struct BlockHashEncoder(encoding::ArrayEncoder<32>);
-}
-
-impl Encodable for BlockHash {
- type Encoder<'e> = BlockHashEncoder;
- fn encoder(&self) -> Self::Encoder<'_> {
- BlockHashEncoder(encoding::ArrayEncoder::without_length_prefix(self.to_byte_array()))
- }
-}
-
#[cfg(feature = "arbitrary")]
#[cfg(feature = "alloc")]
impl<'a> Arbitrary<'a> for Block {
@@ -403,13 +375,6 @@ impl<'a> Arbitrary<'a> for Block {
}
}
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for BlockHash {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(BlockHash::from_byte_array(u.arbitrary()?))
- }
-}
-
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Header {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/primitives/src/hash_types/block_hash.rs b/primitives/src/hash_types/block_hash.rs
new file mode 100644
index 00000000..b1d2d139
--- /dev/null
+++ b/primitives/src/hash_types/block_hash.rs
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `BlockHash` type.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use encoding::Encodable;
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A bitcoin block hash.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct BlockHash(sha256d::Hash);
+
+impl BlockHash {
+ /// Dummy hash used as the previous blockhash of the genesis block.
+ pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]);
+}
+
+// The new hash wrapper type.
+type HashType = BlockHash;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
+
+encoding::encoder_newtype! {
+ /// The encoder for the [`BlockHash`] type.
+ pub struct BlockHashEncoder(encoding::ArrayEncoder<32>);
+}
+
+impl Encodable for BlockHash {
+ type Encoder<'e> = BlockHashEncoder;
+ fn encoder(&self) -> Self::Encoder<'_> {
+ BlockHashEncoder(encoding::ArrayEncoder::without_length_prefix(self.to_byte_array()))
+ }
+}
diff --git a/primitives/src/hash_types/generic.rs b/primitives/src/hash_types/generic.rs
new file mode 100644
index 00000000..7206be69
--- /dev/null
+++ b/primitives/src/hash_types/generic.rs
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: CC0-1.0
+
+// NOTE: This is not a normal module.
+//
+// Generic implementation of hash wrapper types.
+//
+// File is included in other files using `include!` allowing us to
+// follow the DRY principle without using macros.
+
+const LEN: usize = <Inner as hashes::Hash>::LEN;
+const REVERSE: bool = <Inner as hashes::Hash>::DISPLAY_BACKWARD;
+
+impl HashType {
+ /// Constructs a new type from the underlying byte array.
+ pub const fn from_byte_array(bytes: [u8; LEN]) -> Self {
+ Self(Inner::from_byte_array(bytes))
+ }
+
+ /// Returns the underlying byte array.
+ pub const fn to_byte_array(self) -> [u8; LEN] { self.0.to_byte_array() }
+
+ /// Returns a reference to the underlying byte array.
+ pub const fn as_byte_array(&self) -> &[u8; LEN] { self.0.as_byte_array() }
+}
+
+#[cfg(feature = "serde")]
+super::impl_serde!(HashType, LEN);
+super::impl_bytelike_traits!(HashType, LEN);
+
+#[cfg(feature = "hex")]
+hex::impl_fmt_traits! {
+ #[display_backward(REVERSE)]
+ impl fmt_traits for HashType {
+ const LENGTH: usize = LEN;
+ }
+}
+
+#[cfg(feature = "hex")]
+impl str::FromStr for HashType {
+ type Err = hex::HexToArrayError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let mut bytes = <[u8; LEN]>::from_hex(s)?;
+
+ if REVERSE {
+ bytes.reverse();
+ }
+ Ok(Self::from_byte_array(bytes))
+ }
+}
+
+#[cfg(not(feature = "hex"))]
+impl fmt::Debug for HashType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ for byte in self.as_byte_array() {
+ write!(f, "{:02x}", byte)?
+ }
+ Ok(())
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for HashType {
+ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+ let arbitrary_bytes = u.arbitrary()?;
+ Ok(HashType::from_byte_array(arbitrary_bytes))
+ }
+}
diff --git a/primitives/src/hash_types/mod.rs b/primitives/src/hash_types/mod.rs
new file mode 100644
index 00000000..e50bddad
--- /dev/null
+++ b/primitives/src/hash_types/mod.rs
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Primitive hash wrapper types.
+//!
+//! Note: To print and parse these hash types enable the "hex" feature.
+
+mod block_hash;
+mod ntxid;
+#[cfg(feature = "alloc")]
+mod script_hash;
+mod transaction_merkle_node;
+mod txid;
+mod witness_commitment;
+mod witness_merkle_node;
+#[cfg(feature = "alloc")]
+mod witness_script_hash;
+mod wtxid;
+
+#[rustfmt::skip] // Keep public re-exports separate.
+#[doc(inline)]
+pub use self::{
+ block_hash::{BlockHash, BlockHashEncoder},
+ ntxid::Ntxid,
+ transaction_merkle_node::{TxMerkleNode, TxMerkleNodeEncoder},
+ txid::Txid,
+ wtxid::Wtxid,
+ witness_commitment::WitnessCommitment,
+ witness_merkle_node::WitnessMerkleNode,
+};
+#[cfg(feature = "alloc")]
+#[doc(inline)]
+pub use self::{
+ script_hash::{RedeemScriptSizeError, ScriptHash},
+ witness_script_hash::{WScriptHash, WitnessScriptSizeError},
+};
+
+/// Adds trait impls to a bytelike type.
+///
+/// Implements:
+///
+/// * `AsRef[u8; $len]`
+/// * `AsRef[u8]`
+/// * `Borrow<[u8; $len]>`
+/// * `Borrow<[u8]>`
+///
+/// # Parameters
+///
+/// * `ty` - the bytelike type to implement the traits on.
+/// * `$len` - the number of bytes this type has.
+/// * `$gen: $gent` - the generic type(s) and trait bound(s).
+macro_rules! impl_bytelike_traits {
+ ($ty:ident, $len:expr $(, $gen:ident: $gent:ident)*) => {
+ impl $crate::_export::_core::convert::AsRef<[u8; { $len }]> for $ty {
+ #[inline]
+ fn as_ref(&self) -> &[u8; { $len }] { self.as_byte_array() }
+ }
+
+ impl $crate::_export::_core::convert::AsRef<[u8]> for $ty {
+ #[inline]
+ fn as_ref(&self) -> &[u8] { self.as_byte_array() }
+ }
+
+ impl $crate::_export::_core::borrow::Borrow<[u8; { $len }]> for $ty {
+ fn borrow(&self) -> &[u8; { $len }] { self.as_byte_array() }
+ }
+
+ impl $crate::_export::_core::borrow::Borrow<[u8]> for $ty {
+ fn borrow(&self) -> &[u8] { self.as_byte_array() }
+ }
+ };
+}
+pub(in crate::hash_types) use impl_bytelike_traits;
+
+/// Implements `Serialize` and `Deserialize` for a hash wrapper type `$t`.
+///
+/// This is equivalent to `hashes::impl_serde_for_newtype` but does not rely on the wrapper type
+/// implementing the `Hash` trait.
+///
+/// Requires `$t` to implement:
+/// * `from_byte_array()`
+/// * `as_byte_array()`
+/// * `str::FromStr`
+/// * `fmt::Display`
+#[cfg(feature = "serde")]
+macro_rules! impl_serde(
+ ($t:ident, $len:expr) => (
+ impl $crate::serde::Serialize for $t {
+ fn serialize<S: $crate::serde::Serializer>(&self, s: S) -> core::result::Result<S::Ok, S::Error> {
+ if s.is_human_readable() {
+ s.collect_str(self)
+ } else {
+ s.serialize_bytes(self.as_byte_array())
+ }
+ }
+ }
+
+ impl<'de> $crate::serde::Deserialize<'de> for $t {
+ fn deserialize<D: $crate::serde::Deserializer<'de>>(d: D) -> core::result::Result<$t, D::Error> {
+ use $crate::hash_types::serde_details::{BytesVisitor, HexVisitor};
+
+ if d.is_human_readable() {
+ d.deserialize_str(HexVisitor::<Self>::default())
+ } else {
+ let bytes = d.deserialize_bytes(BytesVisitor::<$len>::default())?;
+ Ok(Self::from_byte_array(bytes))
+ }
+ }
+ }
+));
+#[cfg(feature = "serde")]
+pub(in crate::hash_types) use impl_serde;
+
+/// Functions used by serde impls of all hashes.
+#[cfg(feature = "serde")]
+pub mod serde_details {
+ use core::marker::PhantomData;
+ use core::str::FromStr;
+ use core::{fmt, str};
+
+ use serde::de;
+
+ /// Type used to implement serde traits for hashes as hex strings.
+ pub struct HexVisitor<ValueT>(PhantomData<ValueT>);
+
+ impl<ValueT> Default for HexVisitor<ValueT> {
+ fn default() -> Self { Self(PhantomData) }
+ }
+
+ impl<ValueT> de::Visitor<'_> for HexVisitor<ValueT>
+ where
+ ValueT: FromStr,
+ <ValueT as FromStr>::Err: fmt::Display,
+ {
+ type Value = ValueT;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("an ASCII hex string")
+ }
+
+ fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ if let Ok(hex) = str::from_utf8(v) {
+ hex.parse::<Self::Value>().map_err(E::custom)
+ } else {
+ Err(E::invalid_value(de::Unexpected::Bytes(v), &self))
+ }
+ }
+
+ fn visit_str<E>(self, v: &str) -> core::result::Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ v.parse::<Self::Value>().map_err(E::custom)
+ }
+ }
+
+ /// Type used to implement serde traits for hashes as bytes.
+ pub struct BytesVisitor<const N: usize>();
+
+ impl<const N: usize> Default for BytesVisitor<N> {
+ fn default() -> Self { Self() }
+ }
+
+ impl<const N: usize> de::Visitor<'_> for BytesVisitor<N> {
+ type Value = [u8; N];
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a bytestring")
+ }
+
+ fn visit_bytes<E>(self, v: &[u8]) -> core::result::Result<Self::Value, E>
+ where
+ E: de::Error,
+ {
+ let bytes = <[u8; N]>::try_from(v).map_err(|_| {
+ // from_slice only errors on incorrect length
+ E::invalid_length(v.len(), &stringify!(N))
+ })?;
+
+ Ok(bytes)
+ }
+ }
+}
+
+#[cfg(test)]
+#[cfg(feature = "alloc")]
+mod tests {
+ use super::*;
+
+ // Creates an arbitrary dummy hash type object.
+ #[cfg(feature = "serde")]
+ fn dummy_test_case() -> Txid {
+ "e567952fb6cc33857f392efa3a46c995a28f69cca4bb1b37e0204dab1ec7a389".parse::<Txid>().unwrap()
+ }
+
+ #[test]
+ #[cfg(feature = "serde")] // Implies alloc and hex
+ fn serde_human_readable_roundtrips() {
+ let tc = dummy_test_case();
+ let ser = serde_json::to_string(&tc).unwrap();
+ let got = serde_json::from_str::<Txid>(&ser).unwrap();
+ assert_eq!(got, tc);
+ }
+
+ #[test]
+ #[cfg(feature = "serde")] // Implies alloc and hex
+ fn serde_non_human_readable_roundtrips() {
+ let tc = dummy_test_case();
+ let ser = bincode::serialize(&tc).unwrap();
+ let got = bincode::deserialize::<Txid>(&ser).unwrap();
+ assert_eq!(got, tc);
+ }
+
+ #[test]
+ // This is solely to test that we can debug print WITH "hex" so its ok to require "alloc".
+ #[cfg(feature = "alloc")]
+ #[cfg(feature = "hex")]
+ fn debug() {
+ let tc = Txid::from_byte_array([0xab; 32]);
+ let got = alloc::format!("{:?}", tc);
+ let want = "abababababababababababababababababababababababababababababababab";
+ assert_eq!(got, want);
+ }
+
+ #[test]
+ // This is solely to test that we can debug print WITHOUT "hex" so its ok to require "alloc".
+ #[cfg(feature = "alloc")]
+ #[cfg(not(feature = "hex"))]
+ fn debug() {
+ let tc = Txid::from_byte_array([0xab; 32]);
+ let got = alloc::format!("{:?}", tc);
+ let want = "abababababababababababababababababababababababababababababababab";
+ assert_eq!(got, want);
+ }
+}
diff --git a/primitives/src/hash_types/ntxid.rs b/primitives/src/hash_types/ntxid.rs
new file mode 100644
index 00000000..f7ab1e80
--- /dev/null
+++ b/primitives/src/hash_types/ntxid.rs
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `Txid` type.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A "normalized TXID".
+///
+/// Computed on a transaction that has had the signatures removed.
+///
+/// This type is needed only for legacy (pre-Segwit or P2SH-wrapped segwit version 0)
+/// applications. This method clears the `script_sig` field of each input, which in Segwit
+/// transactions is already empty, so for Segwit transactions the ntxid will be equal to the
+/// txid, and you should simply use the latter.
+///
+/// This gives a way to identify a transaction that is "the same" as another in the sense of
+/// having the same inputs and outputs.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Ntxid(sha256d::Hash);
+
+// The new hash wrapper type.
+type HashType = Ntxid;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/hash_types/script_hash.rs b/primitives/src/hash_types/script_hash.rs
new file mode 100644
index 00000000..ae7ed390
--- /dev/null
+++ b/primitives/src/hash_types/script_hash.rs
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `ScriptHash` type.
+
+use core::convert::Infallible;
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::hash160;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+use crate::script::{Script, ScriptHashableTag, MAX_REDEEM_SCRIPT_SIZE};
+
+/// A 160-bit hash of Bitcoin Script bytecode.
+///
+/// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
+/// uses 256-bit hash and hashes a semantically different script. Thus, this type cannot
+/// represent it.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ScriptHash(hash160::Hash);
+
+impl ScriptHash {
+ /// Constructs a new `ScriptHash` after first checking the script size.
+ ///
+ /// # 520-byte limitation on serialized script size
+ ///
+ /// > As a consequence of the requirement for backwards compatibility the serialized script is
+ /// > itself subject to the same rules as any other PUSHDATA operation, including the rule that
+ /// > no data greater than 520 bytes may be pushed to the stack. Thus it is not possible to
+ /// > spend a P2SH output if the redemption script it refers to is >520 bytes in length.
+ ///
+ /// ref: [BIP-0016](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size)
+ #[inline]
+ pub fn from_script<T>(redeem_script: &Script<T>) -> Result<Self, RedeemScriptSizeError>
+ where
+ T: ScriptHashableTag,
+ {
+ if redeem_script.len() > MAX_REDEEM_SCRIPT_SIZE {
+ return Err(RedeemScriptSizeError { size: redeem_script.len() });
+ }
+
+ // We've just checked the length
+ Ok(ScriptHash::from_script_unchecked(redeem_script))
+ }
+
+ /// Constructs a new `ScriptHash` from any script irrespective of script size.
+ ///
+ /// If you hash a script that exceeds 520 bytes in size and use it to create a P2SH output
+ /// then the output will be unspendable (see [BIP-0016]).
+ ///
+ /// [BIP-0016]: <https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size>
+ #[inline]
+ pub fn from_script_unchecked<T>(script: &Script<T>) -> Self {
+ ScriptHash(hash160::Hash::hash(script.as_bytes()))
+ }
+}
+
+/// Error while hashing a redeem script.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct RedeemScriptSizeError {
+ /// Invalid redeem script size (cannot exceed 520 bytes).
+ size: usize,
+}
+
+impl RedeemScriptSizeError {
+ /// Returns the invalid redeem script size.
+ pub fn invalid_size(&self) -> usize { self.size }
+}
+
+impl From<Infallible> for RedeemScriptSizeError {
+ #[inline]
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for RedeemScriptSizeError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "redeem script size exceeds {} bytes: {}", MAX_REDEEM_SCRIPT_SIZE, self.size)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RedeemScriptSizeError {}
+
+// The new hash wrapper type.
+type HashType = ScriptHash;
+// The inner hash type from `hashes`.
+type Inner = hash160::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/hash_types/transaction_merkle_node.rs b/primitives/src/hash_types/transaction_merkle_node.rs
new file mode 100644
index 00000000..91d89ea0
--- /dev/null
+++ b/primitives/src/hash_types/transaction_merkle_node.rs
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `TxMerkleNode` type.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A hash of the Merkle tree branch or root for transactions.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct TxMerkleNode(sha256d::Hash);
+
+// The new hash wrapper type.
+type HashType = TxMerkleNode;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
+
+encoding::encoder_newtype! {
+ /// The encoder for the [`TxMerkleNode`] type.
+ pub struct TxMerkleNodeEncoder(encoding::ArrayEncoder<32>);
+}
+
+impl encoding::Encodable for TxMerkleNode {
+ type Encoder<'e> = TxMerkleNodeEncoder;
+ fn encoder(&self) -> Self::Encoder<'_> {
+ TxMerkleNodeEncoder(encoding::ArrayEncoder::without_length_prefix(self.to_byte_array()))
+ }
+}
diff --git a/primitives/src/hash_types/txid.rs b/primitives/src/hash_types/txid.rs
new file mode 100644
index 00000000..4246bce5
--- /dev/null
+++ b/primitives/src/hash_types/txid.rs
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `Txid` type.
+//!
+//! In order to print and parse txids enable the "hex" feature.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+#[cfg(doc)]
+use crate::OutPoint;
+
+/// A bitcoin transaction hash/transaction ID.
+///
+/// For compatibility with the existing Bitcoin infrastructure and historical and current
+/// versions of the Bitcoin Core software itself, this and other [`sha256d::Hash`] types, are
+/// serialized in reverse byte order when converted to a hex string via [`std::fmt::Display`]
+/// trait operations.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Txid(sha256d::Hash);
+
+impl Txid {
+ /// The `Txid` used in a coinbase prevout.
+ ///
+ /// This is used as the "txid" of the dummy input of a coinbase transaction. This is not a real
+ /// TXID and should not be used in any other contexts. See [`OutPoint::COINBASE_PREVOUT`].
+ pub const COINBASE_PREVOUT: Self = Self::from_byte_array([0; 32]);
+}
+
+// The new hash wrapper type.
+type HashType = Txid;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/hash_types/witness_commitment.rs b/primitives/src/hash_types/witness_commitment.rs
new file mode 100644
index 00000000..46fdd61a
--- /dev/null
+++ b/primitives/src/hash_types/witness_commitment.rs
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `WitnessCommitment` type.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A hash corresponding to the witness structure commitment in the coinbase transaction.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct WitnessCommitment(sha256d::Hash);
+
+impl WitnessCommitment {
+ /// Dummy hash used as the previous blockhash of the genesis block.
+ pub const GENESIS_PREVIOUS_BLOCK_HASH: Self = Self::from_byte_array([0; 32]);
+}
+
+// The new hash wrapper type.
+type HashType = WitnessCommitment;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/hash_types/witness_merkle_node.rs b/primitives/src/hash_types/witness_merkle_node.rs
new file mode 100644
index 00000000..9fc6d4d6
--- /dev/null
+++ b/primitives/src/hash_types/witness_merkle_node.rs
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `WitnessMerkleNode` type.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A hash corresponding to the Merkle tree root for witness data.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct WitnessMerkleNode(sha256d::Hash);
+
+// The new hash wrapper type.
+type HashType = WitnessMerkleNode;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/hash_types/witness_script_hash.rs b/primitives/src/hash_types/witness_script_hash.rs
new file mode 100644
index 00000000..927f626a
--- /dev/null
+++ b/primitives/src/hash_types/witness_script_hash.rs
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `WScriptHash` type.
+
+use core::convert::Infallible;
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+use crate::script::{WitnessScript, MAX_WITNESS_SCRIPT_SIZE};
+
+/// SegWit (256-bit) version of a Bitcoin Script bytecode hash.
+///
+/// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
+/// looks similar to this one also being SHA256, however, they hash semantically different
+/// scripts and have reversed representations, so this type cannot be used for both.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct WScriptHash(sha256::Hash);
+
+impl WScriptHash {
+ /// Constructs a new `WScriptHash` after first checking the script size.
+ ///
+ /// # 10,000-byte limit on the witness script
+ ///
+ /// > The witnessScript (≤ 10,000 bytes) is popped off the initial witness stack. SHA256 of the
+ /// > witnessScript must match the 32-byte witness program.
+ ///
+ /// ref: [BIP-0141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
+ #[inline]
+ pub fn from_script(witness_script: &WitnessScript) -> Result<Self, WitnessScriptSizeError> {
+ if witness_script.len() > MAX_WITNESS_SCRIPT_SIZE {
+ return Err(WitnessScriptSizeError { size: witness_script.len() });
+ }
+
+ // We've just checked the length
+ Ok(WScriptHash::from_script_unchecked(witness_script))
+ }
+
+ /// Constructs a new `WScriptHash` from any script irrespective of script size.
+ ///
+ /// If you hash a script that exceeds 10,000 bytes in size and use it to create a Segwit
+ /// output then the output will be unspendable (see [BIP-0141]).
+ ///
+ /// ref: [BIP-0141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
+ #[inline]
+ pub fn from_script_unchecked(script: &WitnessScript) -> Self {
+ WScriptHash(sha256::Hash::hash(script.as_bytes()))
+ }
+}
+
+/// Error while hashing a witness script.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct WitnessScriptSizeError {
+ /// Invalid witness script size (cannot exceed 10,000 bytes).
+ size: usize,
+}
+
+impl WitnessScriptSizeError {
+ /// Returns the invalid witness script size.
+ pub fn invalid_size(&self) -> usize { self.size }
+}
+
+impl From<Infallible> for WitnessScriptSizeError {
+ #[inline]
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for WitnessScriptSizeError {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "witness script size exceeds {} bytes: {}", MAX_WITNESS_SCRIPT_SIZE, self.size)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for WitnessScriptSizeError {}
+
+include!("./generic.rs");
+
+// The new hash wrapper type.
+type HashType = WScriptHash;
+// The inner hash type from `hashes`.
+type Inner = sha256::Hash;
diff --git a/primitives/src/hash_types/wtxid.rs b/primitives/src/hash_types/wtxid.rs
new file mode 100644
index 00000000..6aaa2af1
--- /dev/null
+++ b/primitives/src/hash_types/wtxid.rs
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! The `Txid` type.
+//!
+//! In order to print and parse txids enable the "hex" feature.
+
+#[cfg(not(feature = "hex"))]
+use core::fmt;
+#[cfg(feature = "hex")]
+use core::str;
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hashes::sha256d;
+#[cfg(feature = "hex")]
+use hex::FromHex as _;
+
+/// A bitcoin witness transaction ID.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Wtxid(sha256d::Hash);
+
+impl Wtxid {
+ /// The `Wtxid` of a coinbase transaction.
+ ///
+ /// This is used as the wTXID for the coinbase transaction when constructing blocks (in the
+ /// witness commitment tree) since the coinbase transaction contains a commitment to all
+ /// transactions' wTXIDs but naturally cannot commit to its own.
+ pub const COINBASE: Self = Self::from_byte_array([0; 32]);
+}
+
+// The new hash wrapper type.
+type HashType = Wtxid;
+// The inner hash type from `hashes`.
+type Inner = sha256d::Hash;
+
+include!("./generic.rs");
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 91b0356b..dc99540a 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -40,6 +40,7 @@ pub mod _export {
}
}
+mod hash_types;
mod opcodes;
pub mod block;
diff --git a/primitives/src/merkle_tree.rs b/primitives/src/merkle_tree.rs
index cbe48b9e..b25d2768 100644
--- a/primitives/src/merkle_tree.rs
+++ b/primitives/src/merkle_tree.rs
@@ -2,39 +2,14 @@
//! Bitcoin Merkle tree functions.
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-use hashes::sha256d;
+// This module is unusual in that it exists because of a bunch of (krufty) reasons:
+//
+// - We based the name off of the original `bitcoin` module.
+// - We want the API to be the same here as in `bitcoin`.
+// - We define all the other hash types in some module so the merkle tree hash types need a module.
+//
+// C'est la vie.
-hashes::hash_newtype! {
- /// A hash of the Merkle tree branch or root for transactions.
- pub struct TxMerkleNode(sha256d::Hash);
- /// A hash corresponding to the Merkle tree root for witness data.
- pub struct WitnessMerkleNode(sha256d::Hash);
-}
+#[doc(inline)]
+pub use crate::hash_types::{TxMerkleNode, TxMerkleNodeEncoder, WitnessMerkleNode};
-#[cfg(feature = "hex")]
-hashes::impl_hex_for_newtype!(TxMerkleNode, WitnessMerkleNode);
-#[cfg(not(feature = "hex"))]
-hashes::impl_debug_only_for_newtype!(TxMerkleNode, WitnessMerkleNode);
-#[cfg(feature = "serde")]
-hashes::impl_serde_for_newtype!(TxMerkleNode, WitnessMerkleNode);
-
-encoding::encoder_newtype! {
- /// The encoder for the [`TxMerkleNode`] type.
- pub struct TxMerkleNodeEncoder(encoding::ArrayEncoder<32>);
-}
-
-impl encoding::Encodable for TxMerkleNode {
- type Encoder<'e> = TxMerkleNodeEncoder;
- fn encoder(&self) -> Self::Encoder<'_> {
- TxMerkleNodeEncoder(encoding::ArrayEncoder::without_length_prefix(self.to_byte_array()))
- }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for TxMerkleNode {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(TxMerkleNode::from_byte_array(u.arbitrary()?))
- }
-}
diff --git a/primitives/src/script/mod.rs b/primitives/src/script/mod.rs
index 1888f606..4bc68cac 100644
--- a/primitives/src/script/mod.rs
+++ b/primitives/src/script/mod.rs
@@ -7,12 +7,10 @@ mod owned;
mod tag;
use core::cmp::Ordering;
-use core::convert::Infallible;
use core::fmt;
#[cfg(feature = "serde")]
use core::marker::PhantomData;
-use hashes::{hash160, sha256};
#[cfg(feature = "hex")]
use hex::DisplayHex;
use internals::script::{self, PushDataLenLen};
@@ -32,6 +30,10 @@ pub use self::{
owned::ScriptBuf,
tag::{Tag, RedeemScriptTag, ScriptPubKeyTag, ScriptSigTag, TapScriptTag, WitnessScriptTag},
};
+#[doc(inline)]
+pub use crate::hash_types::{
+ RedeemScriptSizeError, ScriptHash, WScriptHash, WitnessScriptSizeError,
+};
/// A P2SH redeem script.
pub type RedeemScriptBuf = ScriptBuf<RedeemScriptTag>;
@@ -68,29 +70,6 @@ pub const MAX_REDEEM_SCRIPT_SIZE: usize = 520;
/// The maximum allowed redeem script size of the witness script.
pub const MAX_WITNESS_SCRIPT_SIZE: usize = 10_000;
-hashes::hash_newtype! {
- /// A 160-bit hash of Bitcoin Script bytecode.
- ///
- /// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
- /// uses 256-bit hash and hashes a semantically different script. Thus, this type cannot
- /// represent it.
- pub struct ScriptHash(hash160::Hash);
-
- /// SegWit (256-bit) version of a Bitcoin Script bytecode hash.
- ///
- /// Note: there is another "script hash" object in bitcoin ecosystem (Electrum protocol) that
- /// looks similar to this one also being SHA256, however, they hash semantically different
- /// scripts and have reversed representations, so this type cannot be used for both.
- pub struct WScriptHash(sha256::Hash);
-}
-
-#[cfg(feature = "hex")]
-hashes::impl_hex_for_newtype!(ScriptHash, WScriptHash);
-#[cfg(not(feature = "hex"))]
-hashes::impl_debug_only_for_newtype!(ScriptHash, WScriptHash);
-#[cfg(feature = "serde")]
-hashes::impl_serde_for_newtype!(ScriptHash, WScriptHash);
-
/// Either a redeem script or a Segwit version 0 scriptpubkey.
///
/// In the case of P2SH-wrapped Segwit version outputs, we take a Segwit scriptPubKey
@@ -112,73 +91,6 @@ mod sealed {
impl Sealed for super::ScriptPubKeyTag {}
}
-impl ScriptHash {
- /// Constructs a new `ScriptHash` after first checking the script size.
- ///
- /// # 520-byte limitation on serialized script size
- ///
- /// > As a consequence of the requirement for backwards compatibility the serialized script is
- /// > itself subject to the same rules as any other PUSHDATA operation, including the rule that
- /// > no data greater than 520 bytes may be pushed to the stack. Thus it is not possible to
- /// > spend a P2SH output if the redemption script it refers to is >520 bytes in length.
- ///
- /// ref: [BIP-0016](https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size)
- #[inline]
- pub fn from_script<T>(redeem_script: &Script<T>) -> Result<Self, RedeemScriptSizeError>
- where
- T: ScriptHashableTag,
- {
- if redeem_script.len() > MAX_REDEEM_SCRIPT_SIZE {
- return Err(RedeemScriptSizeError { size: redeem_script.len() });
- }
-
- // We've just checked the length
- Ok(ScriptHash::from_script_unchecked(redeem_script))
- }
-
- /// Constructs a new `ScriptHash` from any script irrespective of script size.
- ///
- /// If you hash a script that exceeds 520 bytes in size and use it to create a P2SH output
- /// then the output will be unspendable (see [BIP-0016]).
- ///
- /// [BIP-0016]: <https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki#user-content-520byte_limitation_on_serialized_script_size>
- #[inline]
- pub fn from_script_unchecked<T>(script: &Script<T>) -> Self {
- ScriptHash(hash160::Hash::hash(script.as_bytes()))
- }
-}
-
-impl WScriptHash {
- /// Constructs a new `WScriptHash` after first checking the script size.
- ///
- /// # 10,000-byte limit on the witness script
- ///
- /// > The witnessScript (≤ 10,000 bytes) is popped off the initial witness stack. SHA256 of the
- /// > witnessScript must match the 32-byte witness program.
- ///
- /// ref: [BIP-0141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
- #[inline]
- pub fn from_script(witness_script: &WitnessScript) -> Result<Self, WitnessScriptSizeError> {
- if witness_script.len() > MAX_WITNESS_SCRIPT_SIZE {
- return Err(WitnessScriptSizeError { size: witness_script.len() });
- }
-
- // We've just checked the length
- Ok(WScriptHash::from_script_unchecked(witness_script))
- }
-
- /// Constructs a new `WScriptHash` from any script irrespective of script size.
- ///
- /// If you hash a script that exceeds 10,000 bytes in size and use it to create a Segwit
- /// output then the output will be unspendable (see [BIP-0141]).
- ///
- /// ref: [BIP-0141](https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki)
- #[inline]
- pub fn from_script_unchecked(script: &WitnessScript) -> Self {
- WScriptHash(sha256::Hash::hash(script.as_bytes()))
- }
-}
-
impl<T: ScriptHashableTag> TryFrom<ScriptBuf<T>> for ScriptHash {
type Error = RedeemScriptSizeError;
@@ -233,60 +145,6 @@ impl TryFrom<&WitnessScript> for WScriptHash {
}
}
-/// Error while hashing a redeem script.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct RedeemScriptSizeError {
- /// Invalid redeem script size (cannot exceed 520 bytes).
- size: usize,
-}
-
-impl RedeemScriptSizeError {
- /// Returns the invalid redeem script size.
- pub fn invalid_size(&self) -> usize { self.size }
-}
-
-impl From<Infallible> for RedeemScriptSizeError {
- #[inline]
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for RedeemScriptSizeError {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "redeem script size exceeds {} bytes: {}", MAX_REDEEM_SCRIPT_SIZE, self.size)
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for RedeemScriptSizeError {}
-
-/// Error while hashing a witness script.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct WitnessScriptSizeError {
- /// Invalid witness script size (cannot exceed 10,000 bytes).
- size: usize,
-}
-
-impl WitnessScriptSizeError {
- /// Returns the invalid witness script size.
- pub fn invalid_size(&self) -> usize { self.size }
-}
-
-impl From<Infallible> for WitnessScriptSizeError {
- #[inline]
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for WitnessScriptSizeError {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "witness script size exceeds {} bytes: {}", MAX_WITNESS_SCRIPT_SIZE, self.size)
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for WitnessScriptSizeError {}
-
// We keep all the `Script` and `ScriptBuf` impls together since it's easier to see side-by-side.
impl<T> From<ScriptBuf<T>> for Box<Script<T>> {
@@ -672,6 +530,8 @@ mod tests {
#[cfg(feature = "alloc")]
use alloc::{format, vec};
+ use hashes::{hash160, sha256};
+
use super::*;
// All tests should compile and pass no matter which script type you put here.
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index b9f1ae27..66bfc0e4 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -19,6 +19,7 @@ use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
+#[cfg(feature = "alloc")]
use hashes::sha256d;
#[cfg(feature = "alloc")]
use internals::compact_size;
@@ -34,6 +35,10 @@ use crate::prelude::Vec;
#[cfg(feature = "alloc")]
use crate::{absolute, Amount, ScriptPubKeyBuf, ScriptSigBuf, Sequence, Weight, Witness};
+#[rustfmt::skip] // Keep public re-exports separate.
+#[doc(inline)]
+pub use crate::hash_types::{Ntxid, Txid, Wtxid};
+
/// Bitcoin transaction.
///
/// An authenticated movement of coins.
@@ -584,58 +589,6 @@ impl std::error::Error for ParseOutPointError {
}
}
-hashes::hash_newtype! {
- /// A bitcoin transaction hash/transaction ID.
- ///
- /// For compatibility with the existing Bitcoin infrastructure and historical and current
- /// versions of the Bitcoin Core software itself, this and other [`sha256d::Hash`] types, are
- /// serialized in reverse byte order when converted to a hex string via [`std::fmt::Display`]
- /// trait operations.
- ///
- /// See [`hashes::Hash::DISPLAY_BACKWARD`] for more details.
- pub struct Txid(sha256d::Hash);
-
- /// A bitcoin witness transaction ID.
- pub struct Wtxid(sha256d::Hash);
-
- /// A "normalized TXID".
- ///
- /// Computed on a transaction that has had the signatures removed.
- ///
- /// This type is needed only for legacy (pre-Segwit or P2SH-wrapped segwit version 0)
- /// applications. This method clears the `script_sig` field of each input, which in Segwit
- /// transactions is already empty, so for Segwit transactions the ntxid will be equal to the
- /// txid, and you should simply use the latter.
- ///
- /// This gives a way to identify a transaction that is "the same" as another in the sense of
- /// having the same inputs and outputs.
- pub struct Ntxid(sha256d::Hash);
-}
-
-#[cfg(feature = "hex")]
-hashes::impl_hex_for_newtype!(Txid, Wtxid, Ntxid);
-#[cfg(not(feature = "hex"))]
-hashes::impl_debug_only_for_newtype!(Txid, Wtxid, Ntxid);
-#[cfg(feature = "serde")]
-hashes::impl_serde_for_newtype!(Txid, Wtxid, Ntxid);
-
-impl Txid {
- /// The `Txid` used in a coinbase prevout.
- ///
- /// This is used as the "txid" of the dummy input of a coinbase transaction. This is not a real
- /// TXID and should not be used in any other contexts. See [`OutPoint::COINBASE_PREVOUT`].
- pub const COINBASE_PREVOUT: Self = Self::from_byte_array([0; 32]);
-}
-
-impl Wtxid {
- /// The `Wtxid` of a coinbase transaction.
- ///
- /// This is used as the wTXID for the coinbase transaction when constructing blocks (in the
- /// witness commitment tree) since the coinbase transaction contains a commitment to all
- /// transactions' wTXIDs but naturally cannot commit to its own.
- pub const COINBASE: Self = Self::from_byte_array([0; 32]);
-}
-
/// The transaction version.
///
/// Currently, as specified by [BIP-0068] and [BIP-0431], version 1, 2, and 3 are considered standard.
@@ -747,22 +700,6 @@ impl<'a> Arbitrary<'a> for Version {
}
}
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Txid {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- let arbitrary_bytes = u.arbitrary()?;
- let t = sha256d::Hash::from_byte_array(arbitrary_bytes);
- Ok(Txid(t))
- }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Wtxid {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- Ok(Wtxid::from_byte_array(u.arbitrary()?))
- }
-}
-
#[cfg(feature = "alloc")]
#[cfg(test)]
mod tests {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.