Move script hex parsing to primitives
What changed, and why it matters
This commit is a routine code reorganization: it moves functions that parse Bitcoin script hex strings from the main `bitcoin` crate into the lower-level `primitives` crate. The old functions in `bitcoin` are kept as deprecated aliases so existing code keeps working. There is no security fix or vulnerability here.
No security action needed. Treat as a normal API migration; update downstream code to use the new `primitives` location when convenient and note the deprecation of `bitcoin::script::ScriptBufExt::from_hex`.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates ScriptBuf::from_hex_prefixed, from_hex_no_length_prefix, and the FromHexError type from bitcoin/src/blockdata/script/owned.rs to primitives/src/script/owned.rs, gated by the hex feature. The bitcoin crate removes the original implementations and leaves a deprecated from_hex wrapper that delegates to the new location. Call sites throughout tests and examples are updated to remove now-unnecessary ScriptBufExt imports. This is purely an API refactoring enabled by stable error types; no parsing logic or behavior was changed.
Changed components
bitcoin/src/blockdata/script/owned.rsprimitives/src/script/owned.rsInspect captured patch +87 / −75
diff --git a/bitcoin/examples/script.rs b/bitcoin/examples/script.rs
index 25585e05..1c48e292 100644
--- a/bitcoin/examples/script.rs
+++ b/bitcoin/examples/script.rs
@@ -9,7 +9,7 @@
use bitcoin::consensus::encode;
use bitcoin::key::WPubkeyHash;
-use bitcoin::script::{self, ScriptBufExt as _, ScriptExt as _};
+use bitcoin::script::{self, ScriptExt as _};
use bitcoin::WitnessScriptBuf;
fn main() {
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index c591599f..e9b8653b 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -1030,7 +1030,7 @@ mod tests {
use super::*;
use crate::network::Network::{Bitcoin, Testnet};
use crate::network::{params, TestnetVersion};
- use crate::script::{RedeemScriptBuf, ScriptBufExt as _, WitnessScriptBuf};
+ use crate::script::{RedeemScriptBuf, WitnessScriptBuf};
fn roundtrips(addr: &Address, network: Network) {
assert_eq!(
diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs
index d22bbd60..363f6698 100644
--- a/bitcoin/src/blockdata/script/owned.rs
+++ b/bitcoin/src/blockdata/script/owned.rs
@@ -1,17 +1,14 @@
// SPDX-License-Identifier: CC0-1.0
-use core::convert::Infallible;
#[cfg(doc)]
use core::ops::Deref;
-use core::fmt;
-use internals::{write_err, ToU64 as _};
+use internals::ToU64 as _;
use super::{
opcode_to_verify, write_scriptint, Builder, Error, Instruction, PushBytes, ScriptBuf,
ScriptExtPriv as _, ScriptPubKeyBuf,
};
-use crate::hex;
use crate::key::{
PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, WPubkeyHash,
};
@@ -20,7 +17,7 @@ use crate::opcodes::{self, Opcode};
use crate::prelude::Vec;
use crate::script::witness_program::{WitnessProgram, P2A_PROGRAM};
use crate::script::witness_version::WitnessVersion;
-use crate::script::{self, ScriptBufDecoderError, ScriptHash, WScriptHash};
+use crate::script::{self, ScriptHash, WScriptHash};
use crate::taproot::TapNodeHash;
use crate::internal_macros;
@@ -147,16 +144,6 @@ internal_macros::define_extension_trait! {
/// multiple times.
fn scan_and_push_verify(&mut self) { self.push_verify(self.last_opcode()); }
- /// Constructs a new [`ScriptBuf`] from a hex string.
- ///
- /// The input string is expected to be consensus encoded i.e., includes the length prefix.
- fn from_hex_prefixed(s: &str) -> Result<Self, FromHexError>
- where Self: Sized
- {
- let v = hex::decode_to_vec(s)?;
- Ok(encoding::decode_from_slice(&v)?)
- }
-
/// Constructs a new [`ScriptBuf`] from a hex string.
#[deprecated(since = "TBD", note = "use `from_hex_no_length_prefix()` instead")]
fn from_hex(s: &str) -> Result<Self, hex::DecodeVariableLengthBytesError>
@@ -165,17 +152,6 @@ internal_macros::define_extension_trait! {
Self::from_hex_no_length_prefix(s)
}
- /// Constructs a new [`ScriptBuf`] from a hex string.
- ///
- /// This is **not** consensus encoding. If your hex string is a consensus encoded script
- /// then use `ScriptBuf::from_hex_prefixed`.
- fn from_hex_no_length_prefix(s: &str) -> Result<Self, hex::DecodeVariableLengthBytesError>
- where Self: Sized
- {
- let v = hex::decode_to_vec(s)?;
- Ok(Self::from_bytes(v))
- }
-
// This belongs only on RedeemScript and ScriptPubKey
/// Generates P2WPKH-type of scriptPubkey.
fn new_p2wpkh(pubkey_hash: WPubkeyHash) -> Self {
@@ -410,44 +386,3 @@ impl<T> Drop for ScriptBufAsVec<'_, T> {
*(self.0) = ScriptBuf::from_bytes(vec);
}
}
-
-/// An error parsing a script from hex.
-#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum FromHexError {
- /// Error parsing the hex input string.
- Hex(hex::DecodeVariableLengthBytesError),
- /// Error when decoding the script.
- Decoder(encoding::DecodeError<ScriptBufDecoderError>),
-}
-
-impl From<Infallible> for FromHexError {
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for FromHexError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- Self::Hex(ref e) => write_err!(f, "script hex"; e),
- Self::Decoder(ref e) => write_err!(f, "script decoder"; e),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for FromHexError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match *self {
- Self::Hex(ref e) => Some(e),
- Self::Decoder(ref e) => Some(e),
- }
- }
-}
-
-impl From<hex::DecodeVariableLengthBytesError> for FromHexError {
- fn from(e: hex::DecodeVariableLengthBytesError) -> Self { Self::Hex(e) }
-}
-
-impl From<encoding::DecodeError<ScriptBufDecoderError>> for FromHexError {
- fn from(e: encoding::DecodeError<ScriptBufDecoderError>) -> Self { Self::Decoder(e) }
-}
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 886129cb..e1693a13 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -1539,7 +1539,7 @@ mod tests {
use crate::hex;
use crate::locktime::absolute;
use crate::script::{
- ScriptBufExt as _, ScriptPubKey, ScriptPubKeyBuf, TapScriptBuf, WitnessScriptBuf,
+ ScriptPubKey, ScriptPubKeyBuf, TapScriptBuf, WitnessScriptBuf,
};
use crate::TxIn;
diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs
index 7465d3e7..8829d18a 100644
--- a/bitcoin/src/psbt/mod.rs
+++ b/bitcoin/src/psbt/mod.rs
@@ -1313,8 +1313,10 @@ mod tests {
use crate::network::NetworkKind;
use crate::psbt::serialize::{Deserialize, Serialize};
use crate::script::{
- RedeemScriptBuf, ScriptBufExt as _, ScriptPubKeyBuf, ScriptSigBuf, WitnessScriptBuf,
+ RedeemScriptBuf, ScriptPubKeyBuf, ScriptSigBuf, WitnessScriptBuf,
};
+ #[cfg(all(feature = "rand", feature = "std"))]
+ use crate::script::ScriptBufExt as _;
use crate::transaction::{self, OutPoint, TxIn};
use crate::witness::Witness;
use crate::Sequence;
diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs
index 9af786c4..f8435b21 100644
--- a/bitcoin/src/psbt/serialize.rs
+++ b/bitcoin/src/psbt/serialize.rs
@@ -441,7 +441,6 @@ fn key_source_len(key_source: &KeySource) -> usize { 4 + 4 * (key_source.1).as_r
#[cfg(test)]
mod tests {
use super::*;
- use crate::script::ScriptBufExt as _;
use crate::TapScriptBuf;
// Composes tree matching a given depth map, filled with dumb script leaves,
diff --git a/bitcoin/src/taproot/mod.rs b/bitcoin/src/taproot/mod.rs
index 1cb61b6b..0f354889 100644
--- a/bitcoin/src/taproot/mod.rs
+++ b/bitcoin/src/taproot/mod.rs
@@ -1659,7 +1659,6 @@ mod test {
use hex_unstable::DisplayHex;
use super::*;
- use crate::script::ScriptBufExt as _;
use crate::sighash::TapSighashTag;
use crate::{Address, KnownHrp, ScriptPubKeyBuf};
extern crate serde_json;
diff --git a/bitcoin/tests/bip_174.rs b/bitcoin/tests/bip_174.rs
index 1106b25d..2ed96275 100644
--- a/bitcoin/tests/bip_174.rs
+++ b/bitcoin/tests/bip_174.rs
@@ -9,7 +9,7 @@ use bitcoin::consensus::encode::{deserialize, serialize_hex};
use bitcoin::hex;
use bitcoin::opcodes::all::OP_0;
use bitcoin::psbt::{Psbt, PsbtSighashType};
-use bitcoin::script::{PushBytes, ScriptBuf, ScriptBufExt as _};
+use bitcoin::script::{PushBytes, ScriptBuf};
use bitcoin::{
absolute, script, transaction, NetworkKind, OutPoint, PrivateKey, PublicKey, ScriptPubKeyBuf,
ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
diff --git a/bitcoin/tests/serde.rs b/bitcoin/tests/serde.rs
index c680e1e1..79a610ff 100644
--- a/bitcoin/tests/serde.rs
+++ b/bitcoin/tests/serde.rs
@@ -27,7 +27,6 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d};
use bitcoin::hex;
use bitcoin::locktime::{absolute, relative};
use bitcoin::psbt::{raw, Input, Output, Psbt, PsbtSighashType};
-use bitcoin::script::ScriptBufExt as _;
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapTree, TaprootBuilder};
use bitcoin::witness::Witness;
diff --git a/primitives/src/script/owned.rs b/primitives/src/script/owned.rs
index 961d27bb..d7019d36 100644
--- a/primitives/src/script/owned.rs
+++ b/primitives/src/script/owned.rs
@@ -51,6 +51,38 @@ impl<T> ScriptBuf<T> {
#[inline]
pub const fn from_bytes(bytes: Vec<u8>) -> Self { Self(PhantomData, bytes) }
+ /// Constructs a new [`ScriptBuf`] from a hex string.
+ ///
+ /// The input string is expected to be consensus encoded i.e., includes the length prefix.
+ ///
+ /// # Errors
+ ///
+ /// * If `s` cannot be parsed into a vector.
+ /// * If the parsed bytes cannot be decoded as a valid script (incl.the length prefix).
+ #[cfg(feature = "hex")]
+ pub fn from_hex_prefixed(s: &str) -> Result<Self, FromHexError> {
+ let v = hex::decode_to_vec(s)?;
+ Ok(encoding::decode_from_slice(&v)?)
+ }
+
+ /// Constructs a new [`ScriptBuf`] from a hex string.
+ ///
+ /// This is **not** consensus encoding. If your hex string is a consensus encoded script
+ /// then use `ScriptBuf::from_hex_prefixed`.
+ ///
+ /// There is no script decoding error path because what ever is in the hex input string is
+ /// assumed to be the script. This means if you pass a consensus encoded hex string into this
+ /// function there will be no error and the script will not be what you expect.
+ ///
+ /// # Errors
+ ///
+ /// Errors if `s` cannot be parsed into a vector.
+ #[cfg(feature = "hex")]
+ pub fn from_hex_no_length_prefix(s: &str) -> Result<Self, hex::DecodeVariableLengthBytesError> {
+ let v = hex::decode_to_vec(s)?;
+ Ok(Self::from_bytes(v))
+ }
+
/// Returns a reference to unsized script.
#[inline]
pub fn as_script(&self) -> &Script<T> { Script::from_bytes(&self.1) }
@@ -203,6 +235,52 @@ impl std::error::Error for ScriptBufDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
}
+/// An error parsing a script from hex.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+#[cfg(feature = "hex")]
+pub enum FromHexError {
+ /// Error parsing the hex input string.
+ Hex(hex::DecodeVariableLengthBytesError),
+ /// Error when decoding the script.
+ Decoder(encoding::DecodeError<ScriptBufDecoderError>),
+}
+
+#[cfg(feature = "hex")]
+impl From<Infallible> for FromHexError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+#[cfg(feature = "hex")]
+impl fmt::Display for FromHexError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Self::Hex(ref e) => write_err!(f, "script hex"; e),
+ Self::Decoder(ref e) => write_err!(f, "script decoder"; e),
+ }
+ }
+}
+
+#[cfg(all(feature = "std", feature = "hex"))]
+impl std::error::Error for FromHexError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match *self {
+ Self::Hex(ref e) => Some(e),
+ Self::Decoder(ref e) => Some(e),
+ }
+ }
+}
+
+#[cfg(feature = "hex")]
+impl From<hex::DecodeVariableLengthBytesError> for FromHexError {
+ fn from(e: hex::DecodeVariableLengthBytesError) -> Self { Self::Hex(e) }
+}
+
+#[cfg(feature = "hex")]
+impl From<encoding::DecodeError<ScriptBufDecoderError>> for FromHexError {
+ fn from(e: encoding::DecodeError<ScriptBufDecoderError>) -> Self { Self::Decoder(e) }
+}
+
#[cfg(feature = "arbitrary")]
impl<'a, T> Arbitrary<'a> for ScriptBuf<T> {
#[inline]
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.