bitcoin: make script::Builder generic
What changed, and why it matters
This commit is a routine internal refactoring of the Rust Bitcoin library. It makes the script-building code generic so it can work with different script type tags, but does not change what the code actually does. There is no indication this fixes a security bug or introduces a vulnerability.
No security action required. Treat as normal code maintenance; review for API compatibility if you depend on the affected extension traits or `Builder` type signatures.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors script::Builder, ScriptExt, and ScriptBufExt to be generic over a type parameter T (e.g., GenericScript<T>, GenericScriptBuf<T>, Builder<T>). It introduces GenericScriptExt, GenericScriptBufExt, and corresponding private extension traits, moving methods that do not depend on a concrete script tag out of the concrete Script/ScriptBuf traits. Call sites and tests are updated to use the new generic traits. The diff shows only structural reorganization; no logic changes to script parsing, serialization, cryptographic handling, or consensus rules.
Changed components
bitcoin/src/blockdata/script/builder.rsbitcoin/src/blockdata/script/borrowed.rsbitcoin/src/blockdata/script/owned.rsbitcoin/src/blockdata/script/mod.rsbitcoin/src/blockdata/script/instruction.rsbitcoin/src/blockdata/script/tests.rsbitcoin/src/blockdata/block.rsbitcoin/src/crypto/ecdsa.rsbitcoin/src/lib.rsfuzz/fuzz_targets/bitcoin/deserialize_script.rsInspect captured patch +191 / −166
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 1ece51ad..ff59bfc1 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -22,7 +22,7 @@ use crate::merkle_tree::{MerkleNode as _, TxMerkleNode, WitnessMerkleNode};
use crate::network::Params;
use crate::pow::{Target, Work};
use crate::prelude::Vec;
-use crate::script::{self, ScriptExt as _};
+use crate::script::{self, GenericScriptExt as _};
use crate::transaction::{Transaction, TransactionExt as _, Wtxid};
use crate::{internal_macros, BlockTime};
diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs
index fa96aa11..8151e10c 100644
--- a/bitcoin/src/blockdata/script/borrowed.rs
+++ b/bitcoin/src/blockdata/script/borrowed.rs
@@ -9,8 +9,8 @@ use secp256k1::{Secp256k1, Verification};
use super::witness_version::WitnessVersion;
use super::{
- Builder, Instruction, InstructionIndices, Instructions, PushBytes, RedeemScriptSizeError,
- Script, ScriptHash, WScriptHash, WitnessScriptSizeError,
+ Builder, GenericScript, Instruction, InstructionIndices, Instructions, PushBytes,
+ RedeemScriptSizeError, Script, ScriptHash, WScriptHash, WitnessScriptSizeError,
};
use crate::consensus::{self, Encodable};
use crate::key::{PublicKey, UntweakedPublicKey, WPubkeyHash};
@@ -24,14 +24,62 @@ use crate::{internal_macros, Amount, FeeRate, ScriptBuf};
internal_macros::define_extension_trait! {
/// Extension functionality for the [`Script`] type.
- pub trait ScriptExt impl for Script {
+ pub trait GenericScriptExt<T> impl<T> for GenericScript<T> {
+ /// Constructs a new script builder
+ fn builder() -> Builder<T> { Builder::new() }
+
/// Returns an iterator over script bytes.
#[inline]
fn bytes(&self) -> Bytes<'_> { Bytes(self.as_bytes().iter().copied()) }
- /// Constructs a new script builder
- fn builder() -> Builder { Builder::new() }
+ /// Iterates over the script instructions.
+ ///
+ /// Each returned item is a nested enum covering opcodes, datapushes and errors.
+ /// At most one error will be returned and then the iterator will end. To instead iterate over
+ /// the script as sequence of bytes call the [`bytes`](Self::bytes) method.
+ ///
+ /// To force minimal pushes, use [`instructions_minimal`](Self::instructions_minimal).
+ #[inline]
+ fn instructions(&self) -> Instructions<'_> {
+ Instructions { data: self.as_bytes().iter(), enforce_minimal: false }
+ }
+ /// Iterates over the script instructions while enforcing minimal pushes.
+ ///
+ /// This is similar to [`instructions`](Self::instructions) but an error is returned if a push
+ /// is not minimal.
+ #[inline]
+ fn instructions_minimal(&self) -> Instructions<'_> {
+ Instructions { data: self.as_bytes().iter(), enforce_minimal: true }
+ }
+
+ /// Iterates over the script instructions and their indices.
+ ///
+ /// Unless the script contains an error, the returned item consists of an index pointing to the
+ /// position in the script where the instruction begins and the decoded instruction - either an
+ /// opcode or data push.
+ ///
+ /// To force minimal pushes, use [`Self::instruction_indices_minimal`].
+ #[inline]
+ fn instruction_indices(&self) -> InstructionIndices<'_> {
+ InstructionIndices::from_instructions(self.instructions())
+ }
+
+ /// Iterates over the script instructions and their indices while enforcing minimal pushes.
+ ///
+ /// This is similar to [`instruction_indices`](Self::instruction_indices) but an error is
+ /// returned if a push is not minimal.
+ #[inline]
+ fn instruction_indices_minimal(&self) -> InstructionIndices<'_> {
+ InstructionIndices::from_instructions(self.instructions_minimal())
+ }
+
+ }
+}
+
+crate::internal_macros::define_extension_trait! {
+ /// Extension functionality for the [`Script`] type.
+ pub trait ScriptExt impl for Script {
/// Returns 160-bit hash of the script for P2SH outputs.
#[inline]
fn script_hash(&self) -> Result<ScriptHash, RedeemScriptSizeError> {
@@ -377,48 +425,6 @@ internal_macros::define_extension_trait! {
/// so do not use this to try and estimate if a Taproot script goes over the sigop budget.)
fn count_sigops_legacy(&self) -> usize { self.count_sigops_internal(false) }
- /// Iterates over the script instructions.
- ///
- /// Each returned item is a nested enum covering opcodes, datapushes and errors.
- /// At most one error will be returned and then the iterator will end. To instead iterate over
- /// the script as sequence of bytes call the [`bytes`](Self::bytes) method.
- ///
- /// To force minimal pushes, use [`instructions_minimal`](Self::instructions_minimal).
- #[inline]
- fn instructions(&self) -> Instructions<'_> {
- Instructions { data: self.as_bytes().iter(), enforce_minimal: false }
- }
-
- /// Iterates over the script instructions while enforcing minimal pushes.
- ///
- /// This is similar to [`instructions`](Self::instructions) but an error is returned if a push
- /// is not minimal.
- #[inline]
- fn instructions_minimal(&self) -> Instructions<'_> {
- Instructions { data: self.as_bytes().iter(), enforce_minimal: true }
- }
-
- /// Iterates over the script instructions and their indices.
- ///
- /// Unless the script contains an error, the returned item consists of an index pointing to the
- /// position in the script where the instruction begins and the decoded instruction - either an
- /// opcode or data push.
- ///
- /// To force minimal pushes, use [`Self::instruction_indices_minimal`].
- #[inline]
- fn instruction_indices(&self) -> InstructionIndices<'_> {
- InstructionIndices::from_instructions(self.instructions())
- }
-
- /// Iterates over the script instructions and their indices while enforcing minimal pushes.
- ///
- /// This is similar to [`instruction_indices`](Self::instruction_indices) but an error is
- /// returned if a push is not minimal.
- #[inline]
- fn instruction_indices_minimal(&self) -> InstructionIndices<'_> {
- InstructionIndices::from_instructions(self.instructions_minimal())
- }
-
/// Writes the human-readable assembly representation of the script to the formatter.
#[deprecated(since = "TBD", note = "use the script's `Display` impl instead")]
fn fmt_asm(&self, f: &mut dyn fmt::Write) -> fmt::Result {
@@ -456,7 +462,21 @@ internal_macros::define_extension_trait! {
mod sealed {
pub trait Sealed {}
- impl Sealed for super::Script {}
+ impl<T> Sealed for super::GenericScript<T> {}
+}
+
+crate::internal_macros::define_extension_trait! {
+ pub(crate) trait GenericScriptExtPriv<T> impl<T> for GenericScript<T> {
+ /// Iterates the script to find the last opcode.
+ ///
+ /// Returns `None` is the instruction is data push or if the script is empty.
+ fn last_opcode(&self) -> Option<Opcode> {
+ match self.instructions().last() {
+ Some(Ok(Instruction::Op(op))) => Some(op),
+ _ => None,
+ }
+ }
+ }
}
internal_macros::define_extension_trait! {
@@ -538,16 +558,6 @@ internal_macros::define_extension_trait! {
n
}
- /// Iterates the script to find the last opcode.
- ///
- /// Returns `None` is the instruction is data push or if the script is empty.
- fn last_opcode(&self) -> Option<Opcode> {
- match self.instructions().last() {
- Some(Ok(Instruction::Op(op))) => Some(op),
- _ => None,
- }
- }
-
/// Iterates the script to find the last pushdata.
///
/// Returns `None` if the instruction is an opcode or if the script is empty.
diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs
index 3ef1dbd7..68f532e5 100644
--- a/bitcoin/src/blockdata/script/builder.rs
+++ b/bitcoin/src/blockdata/script/builder.rs
@@ -2,27 +2,31 @@
use core::fmt;
-use super::{opcode_to_verify, write_scriptint, Error, PushBytes, Script, ScriptBuf};
+use super::{opcode_to_verify, write_scriptint, Error, GenericScript, GenericScriptBuf, PushBytes};
use crate::key::{PublicKey, XOnlyPublicKey};
use crate::locktime::absolute;
use crate::opcodes::all::*;
use crate::opcodes::Opcode;
use crate::prelude::Vec;
-use crate::script::{ScriptBufExt as _, ScriptBufExtPriv as _, ScriptExtPriv as _};
+use crate::script::{
+ GenericScriptBufExt as _, GenericScriptBufExtPriv as _, GenericScriptExtPriv as _,
+};
use crate::{relative, Sequence};
/// An Object which can be used to construct a script piece by piece.
#[derive(PartialEq, Eq, Clone)]
-pub struct Builder(ScriptBuf, Option<Opcode>);
+pub struct Builder<T>(GenericScriptBuf<T>, Option<Opcode>);
-impl Builder {
+impl<T> Builder<T> {
/// Constructs a new empty script.
#[inline]
- pub const fn new() -> Self { Self(ScriptBuf::new(), None) }
+ pub const fn new() -> Self { Self(GenericScriptBuf::new(), None) }
/// Constructs a new empty script builder with at least the specified capacity.
#[inline]
- pub fn with_capacity(capacity: usize) -> Self { Self(ScriptBuf::with_capacity(capacity), None) }
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self(GenericScriptBuf::with_capacity(capacity), None)
+ }
/// Returns the length in bytes of the script.
pub fn len(&self) -> usize { self.0.len() }
@@ -81,7 +85,7 @@ impl Builder {
}
/// Adds instructions to push some arbitrary data onto the stack.
- pub fn push_slice<T: AsRef<PushBytes>>(self, data: T) -> Self {
+ pub fn push_slice<D: AsRef<PushBytes>>(self, data: D) -> Self {
let bytes = data.as_ref().as_bytes();
if bytes.len() == 1 && (bytes[0] == 0x81 || bytes[0] <= 16) {
match bytes[0] {
@@ -100,7 +104,7 @@ impl Builder {
/// Standardness rules require push minimality according to [CheckMinimalPush] of core.
///
/// [CheckMinimalPush]: <https://github.com/bitcoin/bitcoin/blob/99a4ddf5ab1b3e514d08b90ad8565827fda7b63b/src/script/script.cpp#L366>
- pub fn push_slice_non_minimal<T: AsRef<PushBytes>>(mut self, data: T) -> Self {
+ pub fn push_slice_non_minimal<D: AsRef<PushBytes>>(mut self, data: D) -> Self {
self.0.push_slice_non_minimal(data);
self.1 = None;
self
@@ -178,35 +182,35 @@ impl Builder {
}
/// Converts the `Builder` into `ScriptBuf`.
- pub fn into_script(self) -> ScriptBuf { self.0 }
+ pub fn into_script(self) -> GenericScriptBuf<T> { self.0 }
/// Converts the `Builder` into script bytes
pub fn into_bytes(self) -> Vec<u8> { self.0.into() }
/// Returns the internal script
- pub fn as_script(&self) -> &Script { &self.0 }
+ pub fn as_script(&self) -> &GenericScript<T> { &self.0 }
/// Returns script bytes
pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() }
}
-impl Default for Builder {
+impl<T> Default for Builder<T> {
fn default() -> Self { Self::new() }
}
/// Constructs a new builder from an existing vector.
-impl From<Vec<u8>> for Builder {
+impl<T> From<Vec<u8>> for Builder<T> {
fn from(v: Vec<u8>) -> Self {
- let script = ScriptBuf::from(v);
+ let script = GenericScriptBuf::from(v);
let last_op = script.last_opcode();
Self(script, last_op)
}
}
-impl fmt::Display for Builder {
+impl<T> fmt::Display for Builder<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
-impl fmt::Debug for Builder {
+impl<T> fmt::Debug for Builder<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) }
}
diff --git a/bitcoin/src/blockdata/script/instruction.rs b/bitcoin/src/blockdata/script/instruction.rs
index 16cd1df4..8d5e3deb 100644
--- a/bitcoin/src/blockdata/script/instruction.rs
+++ b/bitcoin/src/blockdata/script/instruction.rs
@@ -2,7 +2,7 @@
use internals::script::{self, PushDataLenLen};
-use super::{Error, PushBytes, Script, ScriptBuf, ScriptBufExtPriv as _};
+use super::{Error, GenericScriptBufExtPriv as _, PushBytes, Script, ScriptBuf};
use crate::opcodes::{self, Opcode};
/// A "parsed opcode" which allows iterating over a [`Script`] in a more sensible way.
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index c6fbe44f..09ee1dab 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -74,20 +74,20 @@ use crate::OutPoint;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(inline)]
pub use self::{
- borrowed::ScriptExt,
+ borrowed::{GenericScriptExt, ScriptExt},
builder::Builder,
instruction::{Instruction, Instructions, InstructionIndices},
- owned::ScriptBufExt,
+ owned::{GenericScriptBufExt, ScriptBufExt},
push_bytes::{PushBytes, PushBytesBuf, PushBytesError, PushBytesErrorReport},
};
#[doc(inline)]
pub use primitives::script::{
- RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, Tag, WScriptHash, Whatever,
- WitnessScriptSizeError,
+ GenericScript, GenericScriptBuf, RedeemScriptSizeError, Script, ScriptBuf, ScriptHash, Tag,
+ WScriptHash, Whatever, WitnessScriptSizeError,
};
-pub(crate) use self::borrowed::ScriptExtPriv;
-pub(crate) use self::owned::ScriptBufExtPriv;
+pub(crate) use self::borrowed::{GenericScriptExtPriv, ScriptExtPriv};
+pub(crate) use self::owned::GenericScriptBufExtPriv;
impl_asref_push_bytes!(ScriptHash, WScriptHash);
diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs
index f6c18d02..fe5b1340 100644
--- a/bitcoin/src/blockdata/script/owned.rs
+++ b/bitcoin/src/blockdata/script/owned.rs
@@ -7,7 +7,10 @@ use hex::FromHex as _;
use internals::ToU64 as _;
use secp256k1::{Secp256k1, Verification};
-use super::{opcode_to_verify, Builder, Instruction, PushBytes, ScriptBuf, ScriptExtPriv as _};
+use super::{
+ opcode_to_verify, Builder, GenericScriptBuf, GenericScriptExtPriv as _, Instruction, PushBytes,
+ ScriptBuf,
+};
use crate::key::{
PubkeyHash, PublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey, WPubkeyHash,
};
@@ -22,10 +25,81 @@ use crate::{consensus, internal_macros};
internal_macros::define_extension_trait! {
/// Extension functionality for the [`ScriptBuf`] type.
- pub trait ScriptBufExt impl for ScriptBuf {
+ pub trait GenericScriptBufExt<T> impl<T> for GenericScriptBuf<T> {
/// Constructs a new script builder
- fn builder() -> Builder { Builder::new() }
+ fn builder() -> Builder<T> { Builder::new() }
+
+ /// Adds a single opcode to the script.
+ fn push_opcode(&mut self, data: Opcode) { self.as_byte_vec().push(data.to_u8()); }
+
+ /// Adds instructions to push some arbitrary data onto the stack.
+ fn push_slice<D: AsRef<PushBytes>>(&mut self, data: D) {
+ let bytes = data.as_ref().as_bytes();
+ if bytes.len() == 1 && (bytes[0] == 0x81 || bytes[0] <= 16) {
+ match bytes[0] {
+ 0x81 => { self.push_opcode(OP_PUSHNUM_NEG1); },
+ 0 => { self.push_opcode(OP_PUSHBYTES_0); },
+ 1..=16 => { self.push_opcode(Opcode::from(bytes[0] + (OP_PUSHNUM_1.to_u8() - 1))); },
+ _ => {}, // unreachable arm
+ }
+ } else {
+ self.push_slice_non_minimal(data);
+ }
+ }
+
+ /// Adds instructions to push some arbitrary data onto the stack without minimality.
+ ///
+ /// Standardness rules require push minimality according to [CheckMinimalPush] of core.
+ ///
+ /// [CheckMinimalPush]: <https://github.com/bitcoin/bitcoin/blob/99a4ddf5ab1b3e514d08b90ad8565827fda7b63b/src/script/script.cpp#L366>
+ fn push_slice_non_minimal<D: AsRef<PushBytes>>(&mut self, data: D) {
+ let data = data.as_ref();
+ self.reserve(ScriptBuf::reserved_len_for_slice(data.len()));
+ self.push_slice_no_opt(data);
+ }
+
+ /// Add a single instruction to the script.
+ ///
+ /// # Panics
+ ///
+ /// The method panics if the instruction is a data push with length greater or equal to
+ /// 0x100000000.
+ fn push_instruction(&mut self, instruction: Instruction<'_>) {
+ match instruction {
+ Instruction::Op(opcode) => self.push_opcode(opcode),
+ Instruction::PushBytes(bytes) => self.push_slice(bytes),
+ }
+ }
+
+ /// Like push_instruction, but avoids calling `reserve` to not re-check the length.
+ fn push_instruction_no_opt(&mut self, instruction: Instruction<'_>) {
+ match instruction {
+ Instruction::Op(opcode) => self.push_opcode(opcode),
+ Instruction::PushBytes(bytes) => self.push_slice_no_opt(bytes),
+ }
+ }
+
+ /// 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.
+ ///
+ /// This function needs to iterate over the script to find the last instruction. Prefer
+ /// `Builder` if you're creating the script from scratch or if you want to push `OP_VERIFY`
+ /// multiple times.
+ fn scan_and_push_verify(&mut self) { self.push_verify(self.last_opcode()); }
+ }
+}
+crate::internal_macros::define_extension_trait! {
+ /// Extension functionality for the [`ScriptBuf`] type.
+ pub trait ScriptBufExt impl for ScriptBuf {
/// Generates OP_RETURN-type of scriptPubkey for the given data.
fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Self {
Builder::new().push_opcode(OP_RETURN).push_slice(data).into_script()
@@ -121,86 +195,20 @@ internal_macros::define_extension_trait! {
let v = Vec::from_hex(s)?;
Ok(ScriptBuf::from_bytes(v))
}
-
- /// Adds a single opcode to the script.
- fn push_opcode(&mut self, data: Opcode) { self.as_byte_vec().push(data.to_u8()); }
-
- /// Adds instructions to push some arbitrary data onto the stack.
- fn push_slice<T: AsRef<PushBytes>>(&mut self, data: T) {
- let bytes = data.as_ref().as_bytes();
- if bytes.len() == 1 && (bytes[0] == 0x81 || bytes[0] <= 16) {
- match bytes[0] {
- 0x81 => { self.push_opcode(OP_PUSHNUM_NEG1); },
- 0 => { self.push_opcode(OP_PUSHBYTES_0); },
- 1..=16 => { self.push_opcode(Opcode::from(bytes[0] + (OP_PUSHNUM_1.to_u8() - 1))); },
- _ => {}, // unreachable arm
- }
- } else {
- self.push_slice_non_minimal(data);
- }
- }
-
- /// Adds instructions to push some arbitrary data onto the stack without minimality.
- ///
- /// Standardness rules require push minimality according to [CheckMinimalPush] of core.
- ///
- /// [CheckMinimalPush]: <https://github.com/bitcoin/bitcoin/blob/99a4ddf5ab1b3e514d08b90ad8565827fda7b63b/src/script/script.cpp#L366>
- fn push_slice_non_minimal<T: AsRef<PushBytes>>(&mut self, data: T) {
- let data = data.as_ref();
- self.reserve(ScriptBuf::reserved_len_for_slice(data.len()));
- self.push_slice_no_opt(data);
- }
-
- /// Add a single instruction to the script.
- ///
- /// # Panics
- ///
- /// The method panics if the instruction is a data push with length greater or equal to
- /// 0x100000000.
- fn push_instruction(&mut self, instruction: Instruction<'_>) {
- match instruction {
- Instruction::Op(opcode) => self.push_opcode(opcode),
- Instruction::PushBytes(bytes) => self.push_slice(bytes),
- }
- }
-
- /// Like push_instruction, but avoids calling `reserve` to not re-check the length.
- fn push_instruction_no_opt(&mut self, instruction: Instruction<'_>) {
- match instruction {
- Instruction::Op(opcode) => self.push_opcode(opcode),
- Instruction::PushBytes(bytes) => self.push_slice_no_opt(bytes),
- }
- }
-
- /// 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.
- ///
- /// This function needs to iterate over the script to find the last instruction. Prefer
- /// `Builder` if you're creating the script from scratch or if you want to push `OP_VERIFY`
- /// multiple times.
- fn scan_and_push_verify(&mut self) { self.push_verify(self.last_opcode()); }
}
}
mod sealed {
pub trait Sealed {}
- impl Sealed for super::ScriptBuf {}
+ impl<T> Sealed for super::GenericScriptBuf<T> {}
}
internal_macros::define_extension_trait! {
- pub(crate) trait ScriptBufExtPriv impl for ScriptBuf {
+ pub(crate) trait GenericScriptBufExtPriv<T> impl<T> for GenericScriptBuf<T> {
/// Pretends to convert `&mut ScriptBuf` to `&mut Vec<u8>` so that it can be modified.
///
/// Note: if the returned value leaks the original `ScriptBuf` will become empty.
- fn as_byte_vec(&mut self) -> ScriptBufAsVec<'_> {
+ fn as_byte_vec(&mut self) -> ScriptBufAsVec<'_, T> {
let vec = core::mem::take(self).into_bytes();
ScriptBufAsVec(self, vec)
}
@@ -314,21 +322,21 @@ impl<'a> Extend<Instruction<'a>> for ScriptBuf {
/// In reality the backing `Vec<u8>` is swapped with an empty one and this is holding both the
/// reference and the vec. The vec is put back when this drops so it also covers panics. (But not
/// leaks, which is OK since we never leak.)
-pub(crate) struct ScriptBufAsVec<'a>(&'a mut ScriptBuf, Vec<u8>);
+pub(crate) struct ScriptBufAsVec<'a, T>(&'a mut GenericScriptBuf<T>, Vec<u8>);
-impl core::ops::Deref for ScriptBufAsVec<'_> {
+impl<T> core::ops::Deref for ScriptBufAsVec<'_, T> {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target { &self.1 }
}
-impl core::ops::DerefMut for ScriptBufAsVec<'_> {
+impl<T> core::ops::DerefMut for ScriptBufAsVec<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.1 }
}
-impl Drop for ScriptBufAsVec<'_> {
+impl<T> Drop for ScriptBufAsVec<'_, T> {
fn drop(&mut self) {
let vec = core::mem::take(&mut self.1);
- *(self.0) = ScriptBuf::from_bytes(vec);
+ *(self.0) = GenericScriptBuf::from_bytes(vec);
}
}
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 436f323b..99d31519 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -9,11 +9,13 @@ use crate::script::witness_program::WitnessProgram;
use crate::script::witness_version::WitnessVersion;
use crate::{opcodes, Amount, FeeRate};
+type Tag = primitives::script::Whatever;
+
#[test]
#[rustfmt::skip]
fn script() {
let mut comp = vec![];
- let mut script = Builder::new();
+ let mut script = Builder::<Tag>::new();
assert_eq!(script.as_bytes(), &comp[..]);
// small ints
@@ -197,7 +199,7 @@ fn script_x_only_key() {
// From: https://github.com/bitcoin-core/btcdeb/blob/e8c2750c4a4702768c52d15640ed03bf744d2601/doc/tapscript-example.md?plain=1#L43
const KEYSTR: &str = "209997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be";
let x_only_key = KEYSTR[2..].parse::<XOnlyPublicKey>().unwrap();
- let script = Builder::new().push_x_only_key(x_only_key);
+ let script = Builder::<Tag>::new().push_x_only_key(x_only_key);
assert_eq!(script.into_bytes(), &hex!(KEYSTR) as &[u8]);
}
@@ -219,7 +221,7 @@ fn script_builder() {
#[test]
fn script_builder_with_capacity() {
- let script = Builder::with_capacity(42);
+ let script = Builder::<Tag>::with_capacity(42);
assert!(script.into_script().capacity() >= 42);
}
@@ -713,7 +715,7 @@ fn iterator() {
#[test]
fn script_ord() {
- let script_1 = Builder::new().push_slice([1, 2, 3, 4]).into_script();
+ let script_1 = Builder::<Tag>::new().push_slice([1, 2, 3, 4]).into_script();
let script_2 = Builder::new().push_int_unchecked(10).into_script();
let script_3 = Builder::new().push_int_unchecked(15).into_script();
let script_4 = Builder::new().push_opcode(OP_RETURN).into_script();
@@ -982,7 +984,7 @@ fn instruction_script_num_parse() {
#[test]
fn script_push_int_overflow() {
// Only errors if `data == i32::MIN` (CScriptNum cannot have value -2^31).
- assert_eq!(Builder::new().push_int(i32::MIN), Err(Error::NumericOverflow));
+ assert_eq!(Builder::<Tag>::new().push_int(i32::MIN), Err(Error::NumericOverflow));
}
#[test]
diff --git a/bitcoin/src/crypto/ecdsa.rs b/bitcoin/src/crypto/ecdsa.rs
index 58c0b028..e6d012f7 100644
--- a/bitcoin/src/crypto/ecdsa.rs
+++ b/bitcoin/src/crypto/ecdsa.rs
@@ -100,8 +100,9 @@ impl FromStr for Signature {
/// This avoids allocation and allows proving maximum size of the signature (73 bytes).
/// The type can be used largely as a byte slice. It implements all standard traits one would
/// expect and has familiar methods.
+///
/// However, the usual use case is to push it into a script. This can be done directly passing it
-/// into [`push_slice`](crate::script::ScriptBuf::push_slice).
+/// into [`push_slice`](crate::script::GenericScriptBufExt::push_slice).
#[derive(Copy, Clone)]
pub struct SerializedSignature {
data: [u8; MAX_SIG_LEN],
diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs
index a7c517c5..828ca1dc 100644
--- a/bitcoin/src/lib.rs
+++ b/bitcoin/src/lib.rs
@@ -114,7 +114,7 @@ pub mod ext {
pub use crate::{
block::{BlockUncheckedExt as _, BlockCheckedExt as _, HeaderExt as _},
pow::CompactTargetExt as _,
- script::{ScriptExt as _, ScriptBufExt as _},
+ script::{GenericScriptExt as _, GenericScriptBufExt as _, ScriptExt as _, ScriptBufExt as _},
transaction::{TxidExt as _, WtxidExt as _, OutPointExt as _, TxInExt as _, TxOutExt as _, TransactionExt as _},
witness::WitnessExt as _,
};
diff --git a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs
index 276da502..e077fa13 100644
--- a/fuzz/fuzz_targets/bitcoin/deserialize_script.rs
+++ b/fuzz/fuzz_targets/bitcoin/deserialize_script.rs
@@ -1,6 +1,6 @@
use bitcoin::address::Address;
use bitcoin::consensus::encode;
-use bitcoin::script::{self, ScriptExt as _};
+use bitcoin::script::{self, GenericScriptExt as _, ScriptExt as _};
use bitcoin::{FeeRate, Network};
use bitcoin_fuzz::fuzz_utils::{consume_random_bytes, consume_u32};
use honggfuzz::fuzz;
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.