What changed, and why it matters
This commit is a routine internal code reorganization. It moves several helper methods off the `Builder` type into a new `BuilderExt` extension trait so that the core `Builder` can be kept clean and moved to a more basic crate. There is no bug fix, behavior change, or security patch here.
No security action required. Treat as a normal API refactor; verify downstream code imports `BuilderExt` if it uses the moved methods.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors bitcoin::blockdata::script::builder::Builder. Methods such as with_capacity, len, is_empty, push_int, push_int_unchecked, push_verify, push_key, push_x_only_key, push_lock_time, push_relative_lock_time, push_sequence, into_bytes, and as_bytes are relocated from inherent impl<T> Builder<T> methods into a new BuilderExt<T> extension trait defined via define_extension_trait!. The trait is sealed and re-exported. Call sites are updated to import BuilderExt as _. The actual method bodies are copied almost verbatim, so semantics are preserved. This is API surface restructuring, not a vulnerability fix.
Changed components
bitcoin/src/blockdata/script/builder.rsbitcoin/src/blockdata/script/mod.rsbitcoin/src/blockdata/script/owned.rsbitcoin/src/address/mod.rsbitcoin/src/blockdata/constants.rsbitcoin/src/lib.rsInspect captured patch +129 / −123
diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs
index 17cc91a2..bc078a6b 100644
--- a/bitcoin/src/address/mod.rs
+++ b/bitcoin/src/address/mod.rs
@@ -64,9 +64,10 @@ use crate::prelude::{String, ToOwned};
use crate::script::witness_program::WitnessProgram;
use crate::script::witness_version::WitnessVersion;
use crate::script::{
- self, RedeemScriptSizeError, Script, ScriptExt as _, ScriptHash, ScriptHashableTag,
- ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyBufExt as _, ScriptPubKeyExt as _, WScriptHash,
- WitnessScript, WitnessScriptExt as _, WitnessScriptSizeError,
+ self, BuilderExt as _, RedeemScriptSizeError, Script, ScriptExt as _, ScriptHash,
+ ScriptHashableTag, ScriptPubKey, ScriptPubKeyBuf, ScriptPubKeyBufExt as _,
+ ScriptPubKeyExt as _, WScriptHash, WitnessScript, WitnessScriptExt as _,
+ WitnessScriptSizeError,
};
use crate::taproot::TapNodeHash;
diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs
index 2f1053e5..622df916 100644
--- a/bitcoin/src/blockdata/constants.rs
+++ b/bitcoin/src/blockdata/constants.rs
@@ -12,7 +12,7 @@ use crate::locktime::absolute;
use crate::network::{Network, Params};
use crate::opcodes::all::*;
use crate::pow::CompactTarget;
-use crate::script::{self, BuilderExtPriv as _};
+use crate::script::{self, BuilderExt as _, BuilderExtPriv as _};
use crate::transaction::{self, OutPoint, Transaction, TxIn, TxOut};
use crate::witness::Witness;
use crate::{Amount, BlockHash, BlockTime, Sequence, TestnetVersion};
diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs
index 7a9f37f1..55da4b7d 100644
--- a/bitcoin/src/blockdata/script/builder.rs
+++ b/bitcoin/src/blockdata/script/builder.rs
@@ -25,51 +25,6 @@ impl<T> Builder<T> {
#[inline]
pub const fn new() -> Self { Self(ScriptBuf::new()) }
- /// Constructs a new empty script builder with at least the specified capacity.
- #[inline]
- pub fn with_capacity(capacity: usize) -> Self { Self::from(Vec::with_capacity(capacity)) }
-
- /// Returns the length in bytes of the script.
- pub fn len(&self) -> usize { self.as_script().len() }
-
- /// Checks whether the script is the empty script.
- pub fn is_empty(&self) -> bool { self.as_script().is_empty() }
-
- /// Adds instructions to push an integer onto the stack.
- ///
- /// Integers are encoded as little-endian signed-magnitude numbers, but there are dedicated
- /// opcodes to push some small integers.
- ///
- /// # Errors
- ///
- /// Only errors if `data == i32::MIN` (CScriptNum cannot have value -2^31).
- pub fn push_int(self, n: i32) -> Result<Self, Error> {
- let mut script = self.into_script();
- script.push_int(n)?;
- Ok(Self::from(script.into_bytes()))
- }
-
- /// Adds instructions to push an unchecked integer onto the stack.
- ///
- /// Integers are encoded as little-endian signed-magnitude numbers, but there are dedicated
- /// opcodes to push some small integers.
- ///
- /// This function implements `CScript::push_int64` from Core `script.h`.
- ///
- /// > Numeric opcodes (OP_1ADD, etc) are restricted to operating on 4-byte integers.
- /// > The semantics are subtle, though: operands must be in the range [-2^31 +1...2^31 -1],
- /// > but results may overflow (and are valid as long as they are not used in a subsequent
- /// > numeric operation). CScriptNum enforces those semantics by storing results as
- /// > an int64 and allowing out-of-range values to be returned as a vector of bytes but
- /// > throwing an exception if arithmetic is done or the result is interpreted as an integer.
- ///
- /// Does not check whether `n` is in the range of [-2^31 +1...2^31 -1].
- pub fn push_int_unchecked(self, n: i64) -> Self {
- let mut script = self.into_script();
- script.push_int_unchecked(n);
- Self::from(script.into_bytes())
- }
-
/// Adds instructions to push some arbitrary data onto the stack.
///
/// If the data can be exactly produced by a numeric opcode, that opcode
@@ -102,88 +57,138 @@ impl<T> Builder<T> {
self
}
- /// Adds an `OP_VERIFY` to the script or replaces the last opcode with VERIFY form.
- ///
- /// Some opcodes such as `OP_CHECKSIG` have a verify variant that works as if `VERIFY` was
- /// in the script right after. To save space this function appends `VERIFY` only if
- /// the most-recently-added opcode *does not* have an alternate `VERIFY` form. If it does
- /// the last opcode is replaced. E.g., `OP_CHECKSIG` will become `OP_CHECKSIGVERIFY`.
- ///
- /// Note that existing `OP_*VERIFY` opcodes do not lead to the instruction being ignored
- /// because `OP_VERIFY` consumes an item from the stack so ignoring them would change the
- /// semantics.
- pub fn push_verify(self) -> Self {
- // "duplicated code" because we need to update `1` field
- match opcode_to_verify(self.as_script().last_opcode()) {
- Some(opcode) => {
- let mut script = self.into_script();
- script.as_byte_vec().pop();
- let result = Self::from(script.into_bytes());
- result.push_opcode(opcode)
- }
- None => self.push_opcode(OP_VERIFY),
- }
- }
+ /// Converts the `Builder` into `ScriptBuf`.
+ pub fn into_script(self) -> ScriptBuf<T> { self.0 }
+
+ /// Returns the internal script
+ pub fn as_script(&self) -> &Script<T> { &self.0 }
+}
- /// Adds instructions to push a public key onto the stack.
- pub fn push_key(self, key: LegacyPublicKey) -> Self {
- if key.compressed() {
- self.push_slice(key.serialize_compressed())
- } else {
- self.push_slice(key.serialize_uncompressed())
+mod sealed {
+ pub trait Sealed {}
+ impl<T> Sealed for super::Builder<T> {}
+}
+
+crate::internal_macros::define_extension_trait! {
+ /// Extension functionality for the [`Builder`] type.
+ pub trait BuilderExt<T> impl<T> for Builder<T> {
+ /// Constructs a new empty script builder with at least the specified capacity.
+ #[inline]
+ fn with_capacity(capacity: usize) -> Self { Self::from(Vec::with_capacity(capacity)) }
+
+ /// Returns the length in bytes of the script.
+ fn len(&self) -> usize { self.as_script().len() }
+
+ /// Checks whether the script is the empty script.
+ fn is_empty(&self) -> bool { self.as_script().is_empty() }
+
+ /// Adds instructions to push an integer onto the stack.
+ ///
+ /// Integers are encoded as little-endian signed-magnitude numbers, but there are dedicated
+ /// opcodes to push some small integers.
+ ///
+ /// # Errors
+ ///
+ /// Only errors if `data == i32::MIN` (CScriptNum cannot have value -2^31).
+ fn push_int(self, n: i32) -> Result<Builder<T>, Error> {
+ let mut script = self.into_script();
+ script.push_int(n)?;
+ Ok(Self::from(script.into_bytes()))
}
- }
- /// Adds instructions to push an XOnly public key onto the stack.
- pub fn push_x_only_key(self, x_only_key: XOnlyPublicKey) -> Self {
- self.push_slice(x_only_key.serialize().0)
- }
+ /// Adds instructions to push an unchecked integer onto the stack.
+ ///
+ /// Integers are encoded as little-endian signed-magnitude numbers, but there are dedicated
+ /// opcodes to push some small integers.
+ ///
+ /// This function implements `CScript::push_int64` from Core `script.h`.
+ ///
+ /// > Numeric opcodes (OP_1ADD, etc) are restricted to operating on 4-byte integers.
+ /// > The semantics are subtle, though: operands must be in the range [-2^31 +1...2^31 -1],
+ /// > but results may overflow (and are valid as long as they are not used in a subsequent
+ /// > numeric operation). CScriptNum enforces those semantics by storing results as
+ /// > an int64 and allowing out-of-range values to be returned as a vector of bytes but
+ /// > throwing an exception if arithmetic is done or the result is interpreted as an integer.
+ ///
+ /// Does not check whether `n` is in the range of [-2^31 +1...2^31 -1].
+ fn push_int_unchecked(self, n: i64) -> Self {
+ let mut script = self.into_script();
+ script.push_int_unchecked(n);
+ Self::from(script.into_bytes())
+ }
- /// Adds instructions to push an absolute lock time onto the stack.
- pub fn push_lock_time(self, lock_time: absolute::LockTime) -> Self {
- self.push_int_unchecked(lock_time.to_consensus_u32().into())
- }
+ /// Adds an `OP_VERIFY` to the script or replaces the last opcode with VERIFY form.
+ ///
+ /// Some opcodes such as `OP_CHECKSIG` have a verify variant that works as if `VERIFY` was
+ /// in the script right after. To save space this function appends `VERIFY` only if
+ /// the most-recently-added opcode *does not* have an alternate `VERIFY` form. If it does
+ /// the last opcode is replaced. E.g., `OP_CHECKSIG` will become `OP_CHECKSIGVERIFY`.
+ ///
+ /// Note that existing `OP_*VERIFY` opcodes do not lead to the instruction being ignored
+ /// because `OP_VERIFY` consumes an item from the stack so ignoring them would change the
+ /// semantics.
+ fn push_verify(self) -> Self {
+ // "duplicated code" because we need to update `1` field
+ match opcode_to_verify(self.as_script().last_opcode()) {
+ Some(opcode) => {
+ let mut script = self.into_script();
+ script.as_byte_vec().pop();
+ let result= Self::from(script.into_bytes());
+ result.push_opcode(opcode)
+ }
+ None => self.push_opcode(OP_VERIFY),
+ }
+ }
- /// Adds instructions to push a relative lock time onto the stack.
- ///
- /// This is used when creating scripts that use CHECKSEQUENCEVERIFY (CSV) to enforce
- /// relative time locks.
- pub fn push_relative_lock_time(self, lock_time: relative::LockTime) -> Self {
- self.push_int_unchecked(lock_time.to_consensus_u32().into())
- }
+ /// Adds instructions to push a public key onto the stack.
+ fn push_key(self, key: LegacyPublicKey) -> Self {
+ if key.compressed() {
+ self.push_slice(key.serialize_compressed())
+ } else {
+ self.push_slice(key.serialize_uncompressed())
+ }
+ }
- /// Adds instructions to push a sequence number onto the stack.
- ///
- /// # Deprecated
- /// This method is deprecated in favor of `push_relative_lock_time`.
- ///
- /// In Bitcoin script semantics, when using CHECKSEQUENCEVERIFY, you typically
- /// want to push a relative locktime value to be compared against the input's
- /// sequence number, not the sequence number itself.
- #[deprecated(
- since = "TBD",
- note = "Use push_relative_lock_time instead for working with timelocks in scripts"
- )]
- pub fn push_sequence(self, sequence: Sequence) -> Self {
- self.push_int_unchecked(sequence.to_consensus_u32().into())
- }
+ /// Adds instructions to push an XOnly public key onto the stack.
+ fn push_x_only_key(self, x_only_key: XOnlyPublicKey) -> Self {
+ self.push_slice(x_only_key.serialize().0)
+ }
- /// Converts the `Builder` into `ScriptBuf`.
- pub fn into_script(self) -> ScriptBuf<T> { self.0 }
+ /// Adds instructions to push an absolute lock time onto the stack.
+ fn push_lock_time(self, lock_time: absolute::LockTime) -> Self {
+ self.push_int_unchecked(lock_time.to_consensus_u32().into())
+ }
- /// Converts the `Builder` into script bytes
- pub fn into_bytes(self) -> Vec<u8> { self.into_script().into() }
+ /// Adds instructions to push a relative lock time onto the stack.
+ ///
+ /// This is used when creating scripts that use CHECKSEQUENCEVERIFY (CSV) to enforce
+ /// relative time locks.
+ fn push_relative_lock_time(self, lock_time: relative::LockTime) -> Self {
+ self.push_int_unchecked(lock_time.to_consensus_u32().into())
+ }
- /// Returns the internal script
- pub fn as_script(&self) -> &Script<T> { &self.0 }
+ /// Adds instructions to push a sequence number onto the stack.
+ ///
+ /// # Deprecated
+ /// This method is deprecated in favor of `push_relative_lock_time`.
+ ///
+ /// In Bitcoin script semantics, when using CHECKSEQUENCEVERIFY, you typically
+ /// want to push a relative locktime value to be compared against the input's
+ /// sequence number, not the sequence number itself.
+ #[deprecated(
+ since = "TBD",
+ note = "Use push_relative_lock_time instead for working with timelocks in scripts"
+ )]
+ fn push_sequence(self, sequence: Sequence) -> Self {
+ self.push_int_unchecked(sequence.to_consensus_u32().into())
+ }
- /// Returns script bytes
- pub fn as_bytes(&self) -> &[u8] { self.as_script().as_bytes() }
-}
+ /// Converts the `Builder` into script bytes
+ fn into_bytes(self) -> Vec<u8> { self.into_script().into() }
-mod sealed {
- pub trait Sealed {}
- impl<T> Sealed for super::Builder<T> {}
+ /// Returns script bytes
+ fn as_bytes(&self) -> &[u8] { self.as_script().as_bytes() }
+ }
}
crate::internal_macros::define_extension_trait! {
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index b04488d4..be9ff3dd 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -68,7 +68,7 @@ use crate::prelude::Vec;
#[doc(inline)]
pub use self::{
borrowed::{ScriptExt, TapScriptExt, ScriptPubKeyExt, WitnessScriptExt, ScriptSigExt},
- builder::Builder,
+ builder::{Builder, BuilderExt},
instruction::{Instruction, Instructions, InstructionIndices},
owned::{ScriptBufExt, ScriptPubKeyBufExt, ScriptSigBufExt},
push_bytes::{PushBytes, PushBytesBuf, PushBytesExt, PushBytesErrorReport},
diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs
index f9d18520..ce9b4545 100644
--- a/bitcoin/src/blockdata/script/owned.rs
+++ b/bitcoin/src/blockdata/script/owned.rs
@@ -16,7 +16,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, ScriptHash, WScriptHash};
+use crate::script::{self, BuilderExt as _, ScriptHash, WScriptHash};
use crate::taproot::TapNodeHash;
use crate::{internal_macros, ToU64 as _};
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index 7a93f2c0..e531e90a 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -105,7 +105,7 @@ pub mod ext {
network::NetworkExt as _,
opcodes::OpcodeExt as _,
pow::{CompactTargetExt as _, TargetExt as _, WorkExt as _},
- script::{PushBytesExt as _, ScriptExt as _, ScriptBufExt as _, TapScriptExt as _, ScriptPubKeyExt as _, ScriptPubKeyBufExt as _, WitnessScriptExt as _, ScriptSigExt as _},
+ script::{BuilderExt as _, PushBytesExt as _, ScriptExt as _, ScriptBufExt as _, TapScriptExt as _, ScriptPubKeyExt as _, ScriptPubKeyBufExt as _, WitnessScriptExt as _, ScriptSigExt as _},
taproot::{TapLeafHashExt as _, TapNodeHashExt as _},
transaction::{TxidExt as _, WtxidExt as _, OutPointExt as _, TxInExt as _, TxOutExt as _, TransactionExt as _},
witness::WitnessExt as _,
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.