What changed, and why it matters
This commit is a routine internal code reorganization. It moves the PushBytes family of types and helpers from the main bitcoin crate into a lower-level primitives crate, and adjusts re-exports and trait implementations accordingly. There is no indication of a security bug being fixed or introduced.
No security action required. Treat as normal refactoring; review for API compatibility if you depend on the exact crate path of PushBytes internals.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates PushBytes, PushBytesBuf, PushBytesError, and PushBytesErrorReport from bitcoin/src/blockdata/script/push_bytes.rs to a new primitives/src/script/push_bytes.rs. The old location becomes a thin re-export. Implementations of AsRef
Changed components
bitcoin/src/blockdata/script/push_bytes.rsbitcoin/src/blockdata/script/mod.rsbitcoin/src/crypto/key.rscrypto/src/lib.rsprimitives/src/script/push_bytes.rsprimitives/src/script/mod.rsprimitives/src/script/error.rsprimitives/src/hash_types/script_hash.rsprimitives/src/hash_types/witness_script_hash.rsinclude/asref_push_bytes.rsInspect captured patch +495 / −464
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index 82da4f23..f6be5a0d 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -92,8 +92,6 @@ pub use self::error::{
};
pub(crate) use self::owned::ScriptBufExtPriv;
-impl_asref_push_bytes!(ScriptHash, WScriptHash);
-
/// Constructs a new [`WitnessScriptBuf`] containing the script code used for spending a P2WPKH output.
///
/// The `scriptCode` is described in [BIP-0143].
@@ -250,10 +248,11 @@ pub mod error {
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
- pub use super::push_bytes::{PushBytesError, ScriptIntError};
+ pub use super::push_bytes::ScriptIntError;
#[doc(no_inline)]
pub use primitives::script::error::{
- FromHexError, RedeemScriptSizeError, ScriptBufDecoderError, WitnessScriptSizeError,
+ FromHexError, PushBytesError, RedeemScriptSizeError, ScriptBufDecoderError,
+ WitnessScriptSizeError,
};
/// Ways that a script might fail. Not everything is split up as
diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs
index aa6bb639..f3e128be 100644
--- a/bitcoin/src/blockdata/script/push_bytes.rs
+++ b/bitcoin/src/blockdata/script/push_bytes.rs
@@ -4,297 +4,12 @@
use core::convert::Infallible;
use core::fmt;
-use core::ops::{Deref, DerefMut};
-use crate::crypto::{ecdsa, taproot};
-use crate::prelude::{Borrow, BorrowMut};
use crate::script;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
-// This is not the usual re-export, `primitive` here is a code audit thing.
-pub use self::primitive::{PushBytes, PushBytesBuf};
-
-/// This module only contains required operations so that outside functions wouldn't accidentally
-/// break invariants. Therefore auditing this module should be sufficient.
-mod primitive {
- use core::ops::{
- Bound, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
- RangeToInclusive,
- };
-
- use super::PushBytesError;
- use crate::prelude::{ToOwned, Vec};
-
- #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
- fn check_limit(_: usize) -> Result<(), PushBytesError> { Ok(()) }
-
- #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
- fn check_limit(len: usize) -> Result<(), PushBytesError> {
- if len < 0x100000000 {
- Ok(())
- } else {
- Err(PushBytesError { len })
- }
- }
-
- // Defined in `REPO_DIR/include/newtype.rs`.
- transparent_newtype! {
- /// Byte slices that can be in Bitcoin script.
- ///
- /// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long.
- /// This type represents slices that are guaranteed to be within the limit so they can be put in
- /// the script safely.
- #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
- pub struct PushBytes([u8]);
-
- impl PushBytes {
- /// Constructs a new `&PushBytes` without checking the length.
- ///
- /// The caller is responsible for checking that the length is less than the 2^32.
- fn from_slice_unchecked(bytes: &_) -> &Self;
-
- /// Constructs a new `&mut PushBytes` without checking the length.
- ///
- /// The caller is responsible for checking that the length is less than the 2^32.
- fn from_mut_slice_unchecked(bytes: &mut _) -> &mut Self;
- }
- }
-
- impl PushBytes {
- /// Constructs an empty `&PushBytes`.
- pub fn empty() -> &'static Self { Self::from_slice_unchecked(&[]) }
-
- /// Returns the underlying bytes.
- pub fn as_bytes(&self) -> &[u8] { &self.0 }
-
- /// Returns the underlying mutable bytes.
- pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 }
- }
-
- macro_rules! delegate_index {
- ($($type:ty),* $(,)?) => {
- $(
- impl Index<$type> for PushBytes {
- type Output = Self;
-
- #[inline]
- #[track_caller]
- fn index(&self, index: $type) -> &Self::Output {
- Self::from_slice_unchecked(&self.0[index])
- }
- }
-
- impl IndexMut<$type> for PushBytes {
- #[inline]
- #[track_caller]
- fn index_mut(&mut self, index: $type) -> &mut Self::Output {
- Self::from_mut_slice_unchecked(&mut self.0[index])
- }
- }
- )*
- }
- }
-
- delegate_index!(
- Range<usize>,
- RangeFrom<usize>,
- RangeTo<usize>,
- RangeFull,
- RangeInclusive<usize>,
- RangeToInclusive<usize>,
- (Bound<usize>, Bound<usize>)
- );
-
- impl Index<usize> for PushBytes {
- type Output = u8;
-
- #[inline]
- #[track_caller]
- fn index(&self, index: usize) -> &Self::Output { &self.0[index] }
- }
-
- impl IndexMut<usize> for PushBytes {
- #[inline]
- #[track_caller]
- fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] }
- }
-
- impl<'a> TryFrom<&'a [u8]> for &'a PushBytes {
- type Error = PushBytesError;
-
- fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
- check_limit(bytes.len())?;
- Ok(PushBytes::from_slice_unchecked(bytes))
- }
- }
-
- impl<'a> TryFrom<&'a mut [u8]> for &'a mut PushBytes {
- type Error = PushBytesError;
-
- fn try_from(bytes: &'a mut [u8]) -> Result<Self, Self::Error> {
- check_limit(bytes.len())?;
- Ok(PushBytes::from_mut_slice_unchecked(bytes))
- }
- }
-
- macro_rules! from_array {
- ($($len:literal),* $(,)?) => {
- $(
- impl<'a> From<&'a [u8; $len]> for &'a PushBytes {
- fn from(bytes: &'a [u8; $len]) -> Self {
- // Check that the macro wasn't called with a wrong number.
- const _: () = [(); 1][($len >= 0x100000000u64) as usize];
- PushBytes::from_slice_unchecked(bytes)
- }
- }
-
- impl<'a> From<&'a mut [u8; $len]> for &'a mut PushBytes {
- fn from(bytes: &'a mut [u8; $len]) -> Self {
- // Macro check already above, no need to duplicate.
- // We know the size of array statically and we checked macro input.
- PushBytes::from_mut_slice_unchecked(bytes)
- }
- }
-
- impl AsRef<PushBytes> for [u8; $len] {
- fn as_ref(&self) -> &PushBytes {
- self.into()
- }
- }
-
- impl AsMut<PushBytes> for [u8; $len] {
- fn as_mut(&mut self) -> &mut PushBytes {
- self.into()
- }
- }
-
- impl From<[u8; $len]> for PushBytesBuf {
- fn from(bytes: [u8; $len]) -> Self {
- PushBytesBuf(Vec::from(&bytes))
- }
- }
-
- impl<'a> From<&'a [u8; $len]> for PushBytesBuf {
- fn from(bytes: &'a [u8; $len]) -> Self {
- PushBytesBuf(Vec::from(bytes))
- }
- }
- )*
- }
- }
-
- // Sizes up to 76 to support all pubkey and signature sizes
- from_array! {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
- 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
- 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
- 71, 72, 73, 74, 75, 76
- }
-
- /// Owned, growable counterpart to `PushBytes`.
- #[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
- pub struct PushBytesBuf(Vec<u8>);
-
- impl PushBytesBuf {
- /// Constructs an empty `PushBytesBuf`.
- #[inline]
- pub const fn new() -> Self { Self(Vec::new()) }
-
- /// Constructs an empty `PushBytesBuf` with reserved capacity.
- pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) }
-
- /// Reserve capacity for `additional_capacity` bytes.
- pub fn reserve(&mut self, additional_capacity: usize) {
- self.0.reserve(additional_capacity)
- }
-
- /// Try pushing a single byte.
- ///
- /// # Errors
- ///
- /// This method fails if `self` would exceed the limit.
- #[allow(deprecated)]
- pub fn push(&mut self, byte: u8) -> Result<(), PushBytesError> {
- // This is OK on 32 bit archs since vec has its own check and this check is pointless.
- check_limit(self.0.len().saturating_add(1))?;
- self.0.push(byte);
- Ok(())
- }
-
- /// Try appending a slice to `PushBytesBuf`
- ///
- /// # Errors
- ///
- /// This method fails if `self` would exceed the limit.
- pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), PushBytesError> {
- let len = self.0.len().saturating_add(bytes.len());
- check_limit(len)?;
- self.0.extend_from_slice(bytes);
- Ok(())
- }
-
- /// Remove the last byte from buffer if any.
- pub fn pop(&mut self) -> Option<u8> { self.0.pop() }
-
- /// Remove the byte at `index` and return it.
- ///
- /// # Panics
- ///
- /// This method panics if `index` is out of bounds.
- #[track_caller]
- pub fn remove(&mut self, index: usize) -> u8 { self.0.remove(index) }
-
- /// Remove all bytes from buffer without affecting capacity.
- pub fn clear(&mut self) { self.0.clear() }
-
- /// Remove bytes from buffer past `len`.
- pub fn truncate(&mut self, len: usize) { self.0.truncate(len) }
-
- /// Extracts `PushBytes` slice
- pub fn as_push_bytes(&self) -> &PushBytes {
- // length guaranteed by our invariant
- PushBytes::from_slice_unchecked(&self.0)
- }
-
- /// Extracts mutable `PushBytes` slice
- pub fn as_mut_push_bytes(&mut self) -> &mut PushBytes {
- // length guaranteed by our invariant
- PushBytes::from_mut_slice_unchecked(&mut self.0)
- }
-
- /// Accesses inner `Vec` - provided for `super` to impl other methods.
- pub(super) fn inner(&self) -> &Vec<u8> { &self.0 }
- }
-
- impl From<PushBytesBuf> for Vec<u8> {
- fn from(value: PushBytesBuf) -> Self { value.0 }
- }
-
- impl TryFrom<Vec<u8>> for PushBytesBuf {
- type Error = PushBytesError;
-
- fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
- // check len
- let _: &PushBytes = vec.as_slice().try_into()?;
- Ok(Self(vec))
- }
- }
-
- impl ToOwned for PushBytes {
- type Owned = PushBytesBuf;
-
- fn to_owned(&self) -> Self::Owned { PushBytesBuf(self.0.to_owned()) }
- }
-}
-
-impl PushBytes {
- /// Returns the number of bytes in buffer.
- pub fn len(&self) -> usize { self.as_bytes().len() }
-
- /// Returns true if the buffer contains zero bytes.
- pub fn is_empty(&self) -> bool { self.as_bytes().is_empty() }
-}
+pub use primitives::script::{PushBytes, PushBytesBuf, PushBytesErrorReport};
crate::internal_macros::define_extension_trait! {
/// Extension functionality for the [`PushBytes`] type.
@@ -378,83 +93,6 @@ mod sealed {
impl Sealed for super::PushBytes {}
}
-impl PushBytesBuf {
- /// Returns the number of bytes in buffer.
- pub fn len(&self) -> usize { self.inner().len() }
-
- /// Returns the number of bytes the buffer can contain without reallocating.
- pub fn capacity(&self) -> usize { self.inner().capacity() }
-
- /// Returns true if the buffer contains zero bytes.
- pub fn is_empty(&self) -> bool { self.inner().is_empty() }
-}
-
-impl AsRef<[u8]> for PushBytes {
- fn as_ref(&self) -> &[u8] { self.as_bytes() }
-}
-
-impl AsMut<[u8]> for PushBytes {
- fn as_mut(&mut self) -> &mut [u8] { self.as_mut_bytes() }
-}
-
-impl Deref for PushBytesBuf {
- type Target = PushBytes;
-
- fn deref(&self) -> &Self::Target { self.as_push_bytes() }
-}
-
-impl DerefMut for PushBytesBuf {
- fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_push_bytes() }
-}
-
-impl AsRef<Self> for PushBytes {
- fn as_ref(&self) -> &Self { self }
-}
-
-impl AsMut<Self> for PushBytes {
- fn as_mut(&mut self) -> &mut Self { self }
-}
-
-impl AsRef<PushBytes> for PushBytesBuf {
- fn as_ref(&self) -> &PushBytes { self.as_push_bytes() }
-}
-
-impl AsMut<PushBytes> for PushBytesBuf {
- fn as_mut(&mut self) -> &mut PushBytes { self.as_mut_push_bytes() }
-}
-
-impl Borrow<PushBytes> for PushBytesBuf {
- fn borrow(&self) -> &PushBytes { self.as_push_bytes() }
-}
-
-impl BorrowMut<PushBytes> for PushBytesBuf {
- fn borrow_mut(&mut self) -> &mut PushBytes { self.as_mut_push_bytes() }
-}
-
-impl AsRef<PushBytes> for ecdsa::SerializedSignature {
- #[inline]
- fn as_ref(&self) -> &PushBytes {
- <&PushBytes>::try_from(<Self as AsRef<[u8]>>::as_ref(self))
- .expect("max length 73 bytes is valid")
- }
-}
-
-impl AsRef<PushBytes> for taproot::SerializedSignature {
- #[inline]
- fn as_ref(&self) -> &PushBytes {
- <&PushBytes>::try_from(<Self as AsRef<[u8]>>::as_ref(self))
- .expect("max length 65 bytes is valid")
- }
-}
-
-crate::impl_asref_push_bytes! {
- hashes::ripemd160::Hash,
- hashes::hash160::Hash,
- hashes::sha1::Hash,
- hashes::sha256::Hash,
- hashes::sha256d::Hash,
-}
-
/// Possible errors that can arise from [`PushBytes::read_scriptint`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -487,79 +125,3 @@ impl fmt::Display for ScriptIntError {
}
}
}
-
-/// Reports information about failed conversion into `PushBytes`.
-///
-/// This should not be needed by general public, except as an additional bound on `TryFrom` when
-/// converting to `WitnessProgram`.
-pub trait PushBytesErrorReport {
- /// How many bytes the input had.
- fn input_len(&self) -> usize;
-}
-
-impl PushBytesErrorReport for core::convert::Infallible {
- #[inline]
- fn input_len(&self) -> usize { match *self {} }
-}
-
-#[doc(no_inline)]
-pub use error::PushBytesError;
-
-#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
-mod error {
- use core::fmt;
-
- /// Error returned on attempt to create too large `PushBytes`.
- #[allow(unused)]
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct PushBytesError {
- never: core::convert::Infallible,
- }
-
- impl super::PushBytesErrorReport for PushBytesError {
- #[inline]
- fn input_len(&self) -> usize { match self.never {} }
- }
-
- impl fmt::Display for PushBytesError {
- fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { match self.never {} }
- }
-}
-
-// we have 64 bits in mind, but even for esoteric sizes, this code is correct, since it's the
-// conservative one that checks for errors
-#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
-mod error {
- use core::fmt;
-
- /// Error returned on attempt to create too large `PushBytes`.
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct PushBytesError {
- /// How long the input was.
- pub(super) len: usize,
- }
-
- impl super::PushBytesErrorReport for PushBytesError {
- #[inline]
- fn input_len(&self) -> usize { self.len }
- }
-
- impl fmt::Display for PushBytesError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "attempt to prepare {} bytes to be pushed into script but the limit is 2^32-1",
- self.len
- )
- }
- }
-}
-
-impl From<Infallible> for PushBytesError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for PushBytesError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
-}
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index b7f31708..4d7a7ee3 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -5,11 +5,8 @@
//! This module provides keys used in Bitcoin that can be roundtrip
//! (de)serialized.
-use core::borrow::Borrow;
-use core::ops::Deref;
-
use crate::internal_macros::define_extension_trait;
-use crate::script::{self, PushBytes, WitnessScriptBuf};
+use crate::script::{self, WitnessScriptBuf};
#[cfg(feature = "secp-recovery")]
use crate::sign_message::MessageSignature;
use crate::taproot::{TapNodeHash, TapTweakHash, TapTweakHashExt as _};
@@ -29,14 +26,6 @@ pub use crypto::key::{
UntweakedPublicKey, WPubkeyHash, WifKey, XOnlyPublicKey,
};
-impl AsRef<PushBytes> for SerializedLegacyPublicKey {
- fn as_ref(&self) -> &PushBytes { self.borrow() }
-}
-
-impl Borrow<PushBytes> for SerializedLegacyPublicKey {
- fn borrow(&self) -> &PushBytes { <&PushBytes>::try_from(self.deref()).expect("65 <= u32::MAX") }
-}
-
#[deprecated(since = "TBD", note = "use `LegacyPublicKey` instead")]
#[doc(hidden)]
pub type PublicKey = LegacyPublicKey;
@@ -194,8 +183,6 @@ impl TapTweak for UntweakedKeypair {
}
}
-crate::impl_asref_push_bytes!(PubkeyHash, WPubkeyHash);
-
#[cfg(test)]
mod tests {
use alloc::string::ToString;
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 4178e170..fc1a53e0 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -80,7 +80,6 @@ pub extern crate serde;
mod internal_macros;
include!("../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
-include!("../include/asref_push_bytes.rs"); // impl_asref_push_bytes! macro
pub mod ext {
//! Re-export all the extension traits so downstream can use wildcard imports.
diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs
index bc035d1f..3d7d4cd7 100644
--- a/crypto/src/lib.rs
+++ b/crypto/src/lib.rs
@@ -33,3 +33,44 @@ pub mod taproot;
pub use self::key::{FullPublicKey, Keypair, LegacyPublicKey, PrivateKey, XOnlyPublicKey};
include!("../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
+#[cfg(feature = "alloc")]
+include!("../include/asref_push_bytes.rs");
+
+// Encapsulation module for the `PushBytes` code to be removed before 1.0.
+#[cfg(feature = "alloc")]
+mod push_bytes {
+ use core::borrow::Borrow;
+ use core::ops::Deref;
+
+ use primitives::script::{PushBytes, PushBytesBuf};
+
+ use super::key::{PubkeyHash, SerializedLegacyPublicKey, WPubkeyHash};
+
+ impl AsRef<PushBytes> for super::ecdsa::SerializedSignature {
+ #[inline]
+ fn as_ref(&self) -> &PushBytes {
+ <&PushBytes>::try_from(<Self as AsRef<[u8]>>::as_ref(self))
+ .expect("max length 73 bytes is valid")
+ }
+ }
+
+ impl AsRef<PushBytes> for super::taproot::SerializedSignature {
+ #[inline]
+ fn as_ref(&self) -> &PushBytes {
+ <&PushBytes>::try_from(<Self as AsRef<[u8]>>::as_ref(self))
+ .expect("max length 65 bytes is valid")
+ }
+ }
+
+ crate::impl_asref_push_bytes!(PubkeyHash, WPubkeyHash);
+
+ impl AsRef<PushBytes> for SerializedLegacyPublicKey {
+ fn as_ref(&self) -> &PushBytes { self.borrow() }
+ }
+
+ impl Borrow<PushBytes> for SerializedLegacyPublicKey {
+ fn borrow(&self) -> &PushBytes {
+ <&PushBytes>::try_from(self.deref()).expect("65 <= u32::MAX")
+ }
+ }
+}
diff --git a/include/asref_push_bytes.rs b/include/asref_push_bytes.rs
index e9ef6c22..3cd46f33 100644
--- a/include/asref_push_bytes.rs
+++ b/include/asref_push_bytes.rs
@@ -1,16 +1,18 @@
// SPDX-License-Identifier: CC0-1.0
/// Implement `AsRef<PushBytes>` and From<$type> for `PushBytesBuf`.
+///
+/// This macro requires `PushBytes` and `PushBytesBuf` to be visible in the calling scope.
macro_rules! impl_asref_push_bytes {
($($hashtype:ty),* $(,)?) => {
$(
- impl AsRef<$crate::script::PushBytes> for $hashtype {
- fn as_ref(&self) -> &$crate::script::PushBytes {
+ impl AsRef<PushBytes> for $hashtype {
+ fn as_ref(&self) -> &PushBytes {
self.as_byte_array().into()
}
}
- impl From<$hashtype> for $crate::script::PushBytesBuf {
+ impl From<$hashtype> for PushBytesBuf {
fn from(hash: $hashtype) -> Self {
hash.as_byte_array().into()
}
diff --git a/primitives/src/hash_types/script_hash.rs b/primitives/src/hash_types/script_hash.rs
index b9f9d0ce..460bb5b2 100644
--- a/primitives/src/hash_types/script_hash.rs
+++ b/primitives/src/hash_types/script_hash.rs
@@ -11,7 +11,7 @@ use core::str;
use arbitrary::{Arbitrary, Unstructured};
use hashes::hash160;
-use crate::script::{Script, ScriptHashableTag, MAX_REDEEM_SCRIPT_SIZE};
+use crate::script::{PushBytes, PushBytesBuf, Script, ScriptHashableTag, MAX_REDEEM_SCRIPT_SIZE};
/// A 160-bit hash of Bitcoin Script bytecode.
///
@@ -22,6 +22,7 @@ use crate::script::{Script, ScriptHashableTag, MAX_REDEEM_SCRIPT_SIZE};
pub struct ScriptHash(hash160::Hash);
super::impl_debug!(ScriptHash);
+crate::impl_asref_push_bytes!(ScriptHash);
impl ScriptHash {
/// Constructs a new `ScriptHash` after first checking the script size.
diff --git a/primitives/src/hash_types/witness_script_hash.rs b/primitives/src/hash_types/witness_script_hash.rs
index e281e80e..66d5af6d 100644
--- a/primitives/src/hash_types/witness_script_hash.rs
+++ b/primitives/src/hash_types/witness_script_hash.rs
@@ -11,7 +11,7 @@ use core::str;
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256;
-use crate::script::{WitnessScript, MAX_WITNESS_SCRIPT_SIZE};
+use crate::script::{PushBytes, PushBytesBuf, WitnessScript, MAX_WITNESS_SCRIPT_SIZE};
/// SegWit (256-bit) version of a Bitcoin Script bytecode hash.
///
@@ -22,6 +22,7 @@ use crate::script::{WitnessScript, MAX_WITNESS_SCRIPT_SIZE};
pub struct WScriptHash(sha256::Hash);
super::impl_debug!(WScriptHash);
+crate::impl_asref_push_bytes!(WScriptHash);
impl WScriptHash {
/// Constructs a new `WScriptHash` after first checking the script size.
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 4452c26b..1488fc87 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -131,3 +131,5 @@ pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
#[cfg(feature = "alloc")]
include!("../include/newtype.rs"); // Explained in `REPO_DIR/docs/README.md`.
include!("../include/decoder_newtype.rs"); // decoder_newtype! macro
+#[cfg(feature = "alloc")]
+include!("../include/asref_push_bytes.rs"); // impl_asref_push_bytes! macro
diff --git a/primitives/src/script/error.rs b/primitives/src/script/error.rs
index 4db13146..639d4b55 100644
--- a/primitives/src/script/error.rs
+++ b/primitives/src/script/error.rs
@@ -11,6 +11,8 @@ use internals::write_err;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use crate::hash_types::{RedeemScriptSizeError, WitnessScriptSizeError};
+#[doc(inline)]
+pub use super::push_bytes::PushBytesError;
/// An error consensus decoding a `ScriptBuf<T>`.
#[derive(Debug, Clone, PartialEq, Eq)]
diff --git a/primitives/src/script/mod.rs b/primitives/src/script/mod.rs
index 3d7e4520..024da01f 100644
--- a/primitives/src/script/mod.rs
+++ b/primitives/src/script/mod.rs
@@ -4,6 +4,7 @@
mod borrowed;
mod owned;
+mod push_bytes;
mod tag;
#[cfg(test)]
mod tests;
@@ -29,13 +30,16 @@ use crate::prelude::{Borrow, BorrowMut, Box, Cow, ToOwned, Vec};
pub use self::{
borrowed::{Script, ScriptEncoder},
owned::{ScriptBuf, ScriptBufDecoder},
+ push_bytes::{PushBytes, PushBytesBuf, PushBytesErrorReport},
tag::{Tag, RedeemScriptTag, ScriptPubKeyTag, ScriptSigTag, SignetBlockScriptTag, TapScriptTag, WitnessScriptTag},
};
#[doc(no_inline)]
#[cfg(feature = "hex")]
pub use self::error::FromHexError;
#[doc(no_inline)]
-pub use self::error::{RedeemScriptSizeError, ScriptBufDecoderError, WitnessScriptSizeError};
+pub use self::error::{
+ PushBytesError, RedeemScriptSizeError, ScriptBufDecoderError, WitnessScriptSizeError,
+};
#[doc(inline)]
pub use crate::hash_types::{ScriptHash, WScriptHash};
diff --git a/primitives/src/script/push_bytes.rs b/primitives/src/script/push_bytes.rs
new file mode 100644
index 00000000..9227d988
--- /dev/null
+++ b/primitives/src/script/push_bytes.rs
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Contains `PushBytes` & co
+
+use core::convert::Infallible;
+use core::borrow::{Borrow, BorrowMut};
+use core::ops::{Deref, DerefMut};
+
+#[rustfmt::skip] // Keep public re-exports separate.
+#[doc(inline)]
+// This is not the usual re-export, `primitive` here is a code audit thing.
+pub use self::primitive::{PushBytes, PushBytesBuf};
+
+/// This module only contains required operations so that outside functions wouldn't accidentally
+/// break invariants. Therefore auditing this module should be sufficient.
+mod primitive {
+ use alloc::borrow::ToOwned;
+ use alloc::vec::Vec;
+ use core::ops::{
+ Bound, Index, IndexMut, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
+ RangeToInclusive,
+ };
+
+ use super::PushBytesError;
+
+ #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
+ fn check_limit(_: usize) -> Result<(), PushBytesError> { Ok(()) }
+
+ #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
+ fn check_limit(len: usize) -> Result<(), PushBytesError> {
+ if len < 0x100000000 {
+ Ok(())
+ } else {
+ Err(PushBytesError { len })
+ }
+ }
+
+ // Defined in `REPO_DIR/include/newtype.rs`.
+ crate::transparent_newtype! {
+ /// Byte slices that can be in Bitcoin script.
+ ///
+ /// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long.
+ /// This type represents slices that are guaranteed to be within the limit so they can be put in
+ /// the script safely.
+ #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+ pub struct PushBytes([u8]);
+
+ impl PushBytes {
+ /// Constructs a new `&PushBytes` without checking the length.
+ ///
+ /// The caller is responsible for checking that the length is less than the 2^32.
+ fn from_slice_unchecked(bytes: &_) -> &Self;
+
+ /// Constructs a new `&mut PushBytes` without checking the length.
+ ///
+ /// The caller is responsible for checking that the length is less than the 2^32.
+ fn from_mut_slice_unchecked(bytes: &mut _) -> &mut Self;
+ }
+ }
+
+ impl PushBytes {
+ /// Constructs an empty `&PushBytes`.
+ pub fn empty() -> &'static Self { Self::from_slice_unchecked(&[]) }
+
+ /// Returns the underlying bytes.
+ pub fn as_bytes(&self) -> &[u8] { &self.0 }
+
+ /// Returns the underlying mutable bytes.
+ pub fn as_mut_bytes(&mut self) -> &mut [u8] { &mut self.0 }
+ }
+
+ macro_rules! delegate_index {
+ ($($type:ty),* $(,)?) => {
+ $(
+ impl Index<$type> for PushBytes {
+ type Output = Self;
+
+ #[inline]
+ #[track_caller]
+ fn index(&self, index: $type) -> &Self::Output {
+ Self::from_slice_unchecked(&self.0[index])
+ }
+ }
+
+ impl IndexMut<$type> for PushBytes {
+ #[inline]
+ #[track_caller]
+ fn index_mut(&mut self, index: $type) -> &mut Self::Output {
+ Self::from_mut_slice_unchecked(&mut self.0[index])
+ }
+ }
+ )*
+ }
+ }
+
+ delegate_index!(
+ Range<usize>,
+ RangeFrom<usize>,
+ RangeTo<usize>,
+ RangeFull,
+ RangeInclusive<usize>,
+ RangeToInclusive<usize>,
+ (Bound<usize>, Bound<usize>)
+ );
+
+ impl Index<usize> for PushBytes {
+ type Output = u8;
+
+ #[inline]
+ #[track_caller]
+ fn index(&self, index: usize) -> &Self::Output { &self.0[index] }
+ }
+
+ impl IndexMut<usize> for PushBytes {
+ #[inline]
+ #[track_caller]
+ fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] }
+ }
+
+ impl<'a> TryFrom<&'a [u8]> for &'a PushBytes {
+ type Error = PushBytesError;
+
+ fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
+ check_limit(bytes.len())?;
+ Ok(PushBytes::from_slice_unchecked(bytes))
+ }
+ }
+
+ impl<'a> TryFrom<&'a mut [u8]> for &'a mut PushBytes {
+ type Error = PushBytesError;
+
+ fn try_from(bytes: &'a mut [u8]) -> Result<Self, Self::Error> {
+ check_limit(bytes.len())?;
+ Ok(PushBytes::from_mut_slice_unchecked(bytes))
+ }
+ }
+
+ macro_rules! from_array {
+ ($($len:literal),* $(,)?) => {
+ $(
+ impl<'a> From<&'a [u8; $len]> for &'a PushBytes {
+ fn from(bytes: &'a [u8; $len]) -> Self {
+ // Check that the macro wasn't called with a wrong number.
+ const _: () = [(); 1][($len >= 0x100000000u64) as usize];
+ PushBytes::from_slice_unchecked(bytes)
+ }
+ }
+
+ impl<'a> From<&'a mut [u8; $len]> for &'a mut PushBytes {
+ fn from(bytes: &'a mut [u8; $len]) -> Self {
+ // Macro check already above, no need to duplicate.
+ // We know the size of array statically and we checked macro input.
+ PushBytes::from_mut_slice_unchecked(bytes)
+ }
+ }
+
+ impl AsRef<PushBytes> for [u8; $len] {
+ fn as_ref(&self) -> &PushBytes {
+ self.into()
+ }
+ }
+
+ impl AsMut<PushBytes> for [u8; $len] {
+ fn as_mut(&mut self) -> &mut PushBytes {
+ self.into()
+ }
+ }
+
+ impl From<[u8; $len]> for PushBytesBuf {
+ fn from(bytes: [u8; $len]) -> Self {
+ PushBytesBuf(Vec::from(&bytes))
+ }
+ }
+
+ impl<'a> From<&'a [u8; $len]> for PushBytesBuf {
+ fn from(bytes: &'a [u8; $len]) -> Self {
+ PushBytesBuf(Vec::from(bytes))
+ }
+ }
+ )*
+ }
+ }
+
+ // Sizes up to 76 to support all pubkey and signature sizes
+ from_array! {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76
+ }
+
+ /// Owned, growable counterpart to `PushBytes`.
+ #[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+ pub struct PushBytesBuf(Vec<u8>);
+
+ impl PushBytesBuf {
+ /// Constructs an empty `PushBytesBuf`.
+ #[inline]
+ pub const fn new() -> Self { Self(Vec::new()) }
+
+ /// Constructs an empty `PushBytesBuf` with reserved capacity.
+ pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) }
+
+ /// Reserve capacity for `additional_capacity` bytes.
+ pub fn reserve(&mut self, additional_capacity: usize) {
+ self.0.reserve(additional_capacity)
+ }
+
+ /// Try pushing a single byte.
+ ///
+ /// # Errors
+ ///
+ /// This method fails if `self` would exceed the limit.
+ #[allow(deprecated)]
+ pub fn push(&mut self, byte: u8) -> Result<(), PushBytesError> {
+ // This is OK on 32 bit archs since vec has its own check and this check is pointless.
+ check_limit(self.0.len().saturating_add(1))?;
+ self.0.push(byte);
+ Ok(())
+ }
+
+ /// Try appending a slice to `PushBytesBuf`
+ ///
+ /// # Errors
+ ///
+ /// This method fails if `self` would exceed the limit.
+ pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), PushBytesError> {
+ let len = self.0.len().saturating_add(bytes.len());
+ check_limit(len)?;
+ self.0.extend_from_slice(bytes);
+ Ok(())
+ }
+
+ /// Remove the last byte from buffer if any.
+ pub fn pop(&mut self) -> Option<u8> { self.0.pop() }
+
+ /// Remove the byte at `index` and return it.
+ ///
+ /// # Panics
+ ///
+ /// This method panics if `index` is out of bounds.
+ #[track_caller]
+ pub fn remove(&mut self, index: usize) -> u8 { self.0.remove(index) }
+
+ /// Remove all bytes from buffer without affecting capacity.
+ pub fn clear(&mut self) { self.0.clear() }
+
+ /// Remove bytes from buffer past `len`.
+ pub fn truncate(&mut self, len: usize) { self.0.truncate(len) }
+
+ /// Extracts `PushBytes` slice
+ pub fn as_push_bytes(&self) -> &PushBytes {
+ // length guaranteed by our invariant
+ PushBytes::from_slice_unchecked(&self.0)
+ }
+
+ /// Extracts mutable `PushBytes` slice
+ pub fn as_mut_push_bytes(&mut self) -> &mut PushBytes {
+ // length guaranteed by our invariant
+ PushBytes::from_mut_slice_unchecked(&mut self.0)
+ }
+
+ /// Accesses inner `Vec` - provided for `super` to impl other methods.
+ pub(super) fn inner(&self) -> &Vec<u8> { &self.0 }
+ }
+
+ impl From<PushBytesBuf> for Vec<u8> {
+ fn from(value: PushBytesBuf) -> Self { value.0 }
+ }
+
+ impl TryFrom<Vec<u8>> for PushBytesBuf {
+ type Error = PushBytesError;
+
+ fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
+ // check len
+ let _: &PushBytes = vec.as_slice().try_into()?;
+ Ok(Self(vec))
+ }
+ }
+
+ impl ToOwned for PushBytes {
+ type Owned = PushBytesBuf;
+
+ fn to_owned(&self) -> Self::Owned { PushBytesBuf(self.0.to_owned()) }
+ }
+}
+
+impl PushBytes {
+ /// Returns the number of bytes in buffer.
+ pub fn len(&self) -> usize { self.as_bytes().len() }
+
+ /// Returns true if the buffer contains zero bytes.
+ pub fn is_empty(&self) -> bool { self.as_bytes().is_empty() }
+}
+
+impl PushBytesBuf {
+ /// Returns the number of bytes in buffer.
+ pub fn len(&self) -> usize { self.inner().len() }
+
+ /// Returns the number of bytes the buffer can contain without reallocating.
+ pub fn capacity(&self) -> usize { self.inner().capacity() }
+
+ /// Returns true if the buffer contains zero bytes.
+ pub fn is_empty(&self) -> bool { self.inner().is_empty() }
+}
+
+impl AsRef<[u8]> for PushBytes {
+ fn as_ref(&self) -> &[u8] { self.as_bytes() }
+}
+
+impl AsMut<[u8]> for PushBytes {
+ fn as_mut(&mut self) -> &mut [u8] { self.as_mut_bytes() }
+}
+
+impl Deref for PushBytesBuf {
+ type Target = PushBytes;
+
+ fn deref(&self) -> &Self::Target { self.as_push_bytes() }
+}
+
+impl DerefMut for PushBytesBuf {
+ fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_push_bytes() }
+}
+
+impl AsRef<Self> for PushBytes {
+ fn as_ref(&self) -> &Self { self }
+}
+
+impl AsMut<Self> for PushBytes {
+ fn as_mut(&mut self) -> &mut Self { self }
+}
+
+impl AsRef<PushBytes> for PushBytesBuf {
+ fn as_ref(&self) -> &PushBytes { self.as_push_bytes() }
+}
+
+impl AsMut<PushBytes> for PushBytesBuf {
+ fn as_mut(&mut self) -> &mut PushBytes { self.as_mut_push_bytes() }
+}
+
+impl Borrow<PushBytes> for PushBytesBuf {
+ fn borrow(&self) -> &PushBytes { self.as_push_bytes() }
+}
+
+impl BorrowMut<PushBytes> for PushBytesBuf {
+ fn borrow_mut(&mut self) -> &mut PushBytes { self.as_mut_push_bytes() }
+}
+
+crate::impl_asref_push_bytes! {
+ hashes::ripemd160::Hash,
+ hashes::hash160::Hash,
+ hashes::sha1::Hash,
+ hashes::sha256::Hash,
+ hashes::sha256d::Hash,
+}
+
+/// Reports information about failed conversion into `PushBytes`.
+///
+/// This should not be needed by general public, except as an additional bound on `TryFrom` when
+/// converting to `WitnessProgram`.
+pub trait PushBytesErrorReport {
+ /// How many bytes the input had.
+ fn input_len(&self) -> usize;
+}
+
+impl PushBytesErrorReport for core::convert::Infallible {
+ #[inline]
+ fn input_len(&self) -> usize { match *self {} }
+}
+
+#[doc(no_inline)]
+pub use error::PushBytesError;
+
+#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
+mod error {
+ use core::fmt;
+
+ /// Error returned on attempt to create too large `PushBytes`.
+ #[allow(unused)]
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct PushBytesError {
+ never: core::convert::Infallible,
+ }
+
+ impl super::PushBytesErrorReport for PushBytesError {
+ #[inline]
+ fn input_len(&self) -> usize { match self.never {} }
+ }
+
+ impl fmt::Display for PushBytesError {
+ fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { match self.never {} }
+ }
+}
+
+// we have 64 bits in mind, but even for esoteric sizes, this code is correct, since it's the
+// conservative one that checks for errors
+#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))]
+mod error {
+ use core::fmt;
+
+ /// Error returned on attempt to create too large `PushBytes`.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct PushBytesError {
+ /// How long the input was.
+ pub(super) len: usize,
+ }
+
+ impl super::PushBytesErrorReport for PushBytesError {
+ #[inline]
+ fn input_len(&self) -> usize { self.len }
+ }
+
+ impl fmt::Display for PushBytesError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "attempt to prepare {} bytes to be pushed into script but the limit is 2^32-1",
+ self.len
+ )
+ }
+ }
+}
+
+impl From<Infallible> for PushBytesError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for PushBytesError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
+}
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.