taproot-primitives: Implement encoding traits for TapLeafHash
What changed, and why it matters
This commit adds standard data-encoding support for a Bitcoin Taproot hash type (TapLeafHash) so it can be properly serialized and deserialized in PSBT data. There is no indication of a security bug being fixed; it appears to be a missing-feature addition.
No security action required; review as normal feature/maintenance code.
Security signals we found
No security-relevant signal in commit message or diff
Change is a feature addition (encoding trait implementations)
No bounds-checking, memory-safety, or cryptographic flaws evident in diff
Evidence from the diff
The patch implements Encode/Decode traits for TapLeafHash in the taproot-primitives crate, adds TapLeafHashEncoder and TapLeafHashDecoder, and exposes a TapLeafHashDecoderError. It also adds a symlink to a shared include directory and pulls in the decoder_newtype! macro. The change is additive and aligns TapLeafHash with other consensus-encoded hash types used in PSBT.
Changed components
taproot-primitives crateTapLeafHash typePSBT serialization/deserialization supportInspect captured patch +59 / −1
diff --git a/taproot_primitives/include b/taproot_primitives/include
new file mode 120000
index 00000000..f5030fe8
--- /dev/null
+++ b/taproot_primitives/include
@@ -0,0 +1 @@
+../include
\ No newline at end of file
diff --git a/taproot_primitives/src/lib.rs b/taproot_primitives/src/lib.rs
index 57442e46..f4363ac9 100644
--- a/taproot_primitives/src/lib.rs
+++ b/taproot_primitives/src/lib.rs
@@ -30,7 +30,7 @@ pub extern crate serde;
#[rustfmt::skip] // Keep pub re-exports separate
#[doc(no_inline)]
-pub use self::error::InvalidTaprootLeafVersionError;
+pub use self::error::{InvalidTaprootLeafVersionError, TapLeafHashDecoderError};
#[cfg(feature = "alloc")]
use alloc::string::String;
@@ -87,6 +87,38 @@ hashes::impl_hex_for_newtype!(TapLeafHash);
#[cfg(feature = "hex")]
hashes::impl_serde_for_newtype!(TapLeafHash);
+impl encoding::Encode for TapLeafHash {
+ type Encoder<'e> = TapLeafHashEncoder<'e>;
+ #[inline]
+ fn encoder(&self) -> Self::Encoder<'_> {
+ TapLeafHashEncoder::new(encoding::ArrayRefEncoder::without_length_prefix(self.as_byte_array()))
+ }
+}
+
+impl encoding::Decode for TapLeafHash {
+ type Decoder = TapLeafHashDecoder;
+}
+
+encoding::encoder_newtype_exact! {
+ /// The encoder for the [`TapLeafHash`] type.
+ #[derive(Debug, Clone)]
+ pub struct TapLeafHashEncoder<'e>(encoding::ArrayRefEncoder<'e, 32>);
+}
+
+crate::decoder_newtype! {
+ /// The decoder for the [`TapLeafHash`] type.
+ #[derive(Debug, Clone)]
+ pub struct TapLeafHashDecoder(encoding::ArrayDecoder<32>);
+
+ /// Constructs a new [`TapLeafHash`] decoder.
+ pub const fn new() -> Self { Self(encoding::ArrayDecoder::new()) }
+
+ fn end(result: Result<[u8; 32], encoding::UnexpectedEofError>) -> Result<TapLeafHash, TapLeafHashDecoderError> {
+ let array = result.map_err(TapLeafHashDecoderError)?;
+ Ok(TapLeafHash::from_byte_array(array))
+ }
+}
+
/// The tag used for [`TapNodeHash`].
#[derive(Copy, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
pub struct TapBranchTag;
@@ -410,6 +442,8 @@ pub mod error {
use core::convert::Infallible;
use core::fmt;
+ use internals::write_err;
+
/// The last bit of tapleaf version must be zero.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InvalidTaprootLeafVersionError(pub(super) u8);
@@ -436,6 +470,26 @@ pub mod error {
None
}
}
+
+ /// An error consensus decoding a `TapLeafHash`.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct TapLeafHashDecoderError(pub(super) encoding::UnexpectedEofError);
+
+ impl From<Infallible> for TapLeafHashDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for TapLeafHashDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "tapleaf hash decoder error"; self.0)
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for TapLeafHashDecoderError {
+ #[inline]
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+ }
}
#[cfg(feature = "arbitrary")]
@@ -472,6 +526,9 @@ impl<'a> Arbitrary<'a> for LeafVersion {
}
}
+// decoder_newtype! macro
+include!("../include/decoder_newtype.rs");
+
#[cfg(test)]
#[cfg(feature = "alloc")]
mod test {
Why this scored 17/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.