Add new error type for PushBytes::read_scriptint
What changed, and why it matters
This commit is a straightforward internal code cleanup in the rust-bitcoin library. It creates a new, more specific error type called ScriptIntError for one particular function (reading script integers from push bytes), replacing a broader, less precise error type. There is no indication this fixes a security vulnerability; it is a refactoring to make error reporting clearer for developers.
No security action required. Treat as a normal API-quality refactor; downstream users may need to update error handling if they matched on script::Error for read_scriptint results.
Security signals we found
No security-relevant behavior change: parsing logic, overflow checks, and non-minimal detection remain identical.
Error-type narrowing reduces API ambiguity but does not alter exploit surface.
No mention of vulnerability, CVE, bug bounty, or security fix in commit title or message.
Evidence from the diff
The change introduces a dedicated ScriptIntError enum (NumericOverflow, NonMinimal) and updates PushBytes::read_scriptint, read_cltv_scriptint, read_scriptint_internal, and read_scriptint_non_minimal to return it instead of the general script::Error. It adds From
Changed components
bitcoin/src/blockdata/script/push_bytes.rsbitcoin/src/blockdata/script/mod.rsbitcoin/src/blockdata/block.rsbitcoin/src/blockdata/script/tests.rsInspect captured patch +49 / −16
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 8507c9a4..282ad4ea 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -18,7 +18,7 @@ use crate::consensus::encode::{self, Decodable, Encodable, WriteExt as _};
use crate::merkle_tree::{MerkleNode as _, TxMerkleNode, WitnessMerkleNode};
use crate::network::Params;
use crate::prelude::Vec;
-use crate::script::{self, ScriptExt as _};
+use crate::script::{self, ScriptIntError, ScriptExt as _};
use crate::transaction::{Coinbase, Transaction, TransactionExt as _, Wtxid};
use crate::{internal_macros, BlockTime, Target, Weight, Work};
@@ -364,7 +364,7 @@ impl BlockCheckedExt for Block<Checked> {
match (push.script_num(), push.push_bytes().map(|b| b.read_scriptint())) {
(Some(num), Some(Ok(_)) | None) =>
Ok(num.try_into().map_err(|_| Bip34Error::NegativeHeight)?),
- (_, Some(Err(err))) => Err(to_bip34_error(err)),
+ (_, Some(Err(err))) => Err(err.into()),
(None, _) => Err(Bip34Error::NotPresent),
}
}
@@ -509,6 +509,16 @@ impl std::error::Error for Bip34Error {
}
}
+impl From<ScriptIntError> for Bip34Error {
+ #[inline]
+ fn from(err: ScriptIntError) -> Self {
+ match err {
+ ScriptIntError::NonMinimal => Self::NonMinimalPush,
+ _ => Self::NotPresent,
+ }
+ }
+}
+
#[inline]
fn to_bip34_error(err: script::Error) -> Bip34Error {
match err {
diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs
index d7c343e6..a9da14af 100644
--- a/bitcoin/src/blockdata/script/mod.rs
+++ b/bitcoin/src/blockdata/script/mod.rs
@@ -76,7 +76,7 @@ pub use self::{
builder::Builder,
instruction::{Instruction, Instructions, InstructionIndices},
owned::{ScriptBufExt, ScriptPubKeyBufExt},
- push_bytes::{PushBytes, PushBytesBuf, PushBytesError, PushBytesErrorReport},
+ push_bytes::{PushBytes, PushBytesBuf, PushBytesError, PushBytesErrorReport, ScriptIntError},
};
#[doc(inline)]
pub use primitives::script::{
@@ -155,12 +155,12 @@ pub fn write_scriptint(out: &mut [u8; 8], n: i64) -> usize {
///
/// See [`push_bytes::PushBytes::read_scriptint`] for a description of some subtleties of
/// this function.
-pub fn read_scriptint_non_minimal(v: &[u8]) -> Result<i32, Error> {
+pub fn read_scriptint_non_minimal(v: &[u8]) -> Result<i32, ScriptIntError> {
if v.is_empty() {
return Ok(0);
}
if v.len() > 4 {
- return Err(Error::NumericOverflow);
+ return Err(ScriptIntError::NumericOverflow);
}
let ret = scriptint_parse(v);
diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs
index 427b310f..386d81e0 100644
--- a/bitcoin/src/blockdata/script/push_bytes.rs
+++ b/bitcoin/src/blockdata/script/push_bytes.rs
@@ -3,6 +3,7 @@
//! Contains `PushBytes` & co
use core::ops::{Deref, DerefMut};
+use core::fmt;
use crate::prelude::{Borrow, BorrowMut};
use crate::script;
@@ -298,9 +299,9 @@ impl PushBytes {
///
/// # Errors
///
- /// * [`script::Error::NumericOverflow`] if result is not in range [-2^31 +1...2^31 -1].
- /// * [`script::Error::NonMinimalPush`] if encoding is non-minimal.
- pub fn read_scriptint(&self) -> Result<i32, script::Error> {
+ /// * [`ScriptIntError::NumericOverflow`] if result is not in range [-2^39 +1...2^39 -1].
+ /// * [`ScriptIntError::NonMinimal`] if encoding is non-minimal.
+ pub fn read_scriptint(&self) -> Result<i32, ScriptIntError> {
// Cast is safe, since the function already checks for byte length > 4
let ret = self.read_scriptint_internal(4)?;
Ok(i32::try_from(ret).expect("4 bytes or less fits in an i32"))
@@ -325,9 +326,9 @@ impl PushBytes {
///
/// # Errors
///
- /// * [`script::Error::NumericOverflow`] if result is not in range [-2^39 +1...2^39 -1].
- /// * [`script::Error::NonMinimalPush`] if encoding is non-minimal.
- pub fn read_cltv_scriptint(&self) -> Result<i64, script::Error> {
+ /// * [`ScriptIntError::NumericOverflow`] if result is not in range [-2^39 +1...2^39 -1].
+ /// * [`ScriptIntError::NonMinimal`] if encoding is non-minimal.
+ pub fn read_cltv_scriptint(&self) -> Result<i64, ScriptIntError> {
self.read_scriptint_internal(5)
}
@@ -336,13 +337,13 @@ impl PushBytes {
/// As with `read_cltv_scriptint`, this returns an i64, since that is the maximum size we might
/// need to return data. In practice, if the max_size parameter is 4 or less, this function
/// will always return a value that can fit into an i32, and can thus be safely cast.
- fn read_scriptint_internal(&self, max_size: usize) -> Result<i64, script::Error> {
+ fn read_scriptint_internal(&self, max_size: usize) -> Result<i64, ScriptIntError> {
let last = match self.as_bytes().last() {
Some(last) => last,
None => return Ok(0),
};
if self.len() > max_size {
- return Err(script::Error::NumericOverflow);
+ return Err(ScriptIntError::NumericOverflow);
}
// Comment and code copied from Bitcoin Core:
// https://github.com/bitcoin/bitcoin/blob/447f50e4aed9a8b1d80e1891cda85801aeb80b4e/src/script/script.h#L247-L262
@@ -356,7 +357,7 @@ impl PushBytes {
// is +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if self.len() <= 1 || (self[self.len() - 2] & 0x80) == 0 {
- return Err(script::Error::NonMinimalPush);
+ return Err(ScriptIntError::NonMinimal);
}
}
@@ -417,6 +418,28 @@ impl BorrowMut<PushBytes> for PushBytesBuf {
fn borrow_mut(&mut self) -> &mut PushBytes { self.as_mut_push_bytes() }
}
+/// Possible errors that can arise from [`PushBytes::read_scriptint`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum ScriptIntError {
+ /// The result is not in range [-2^31 +1...2^31 -1].
+ NumericOverflow,
+ /// The resulting encoding is non-minimal.
+ NonMinimal,
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ScriptIntError {}
+
+impl fmt::Display for ScriptIntError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Self::NumericOverflow => f.write_str("script integer outside of valid range"),
+ Self::NonMinimal => f.write_str("non-minimal encoded script integer"),
+ }
+ }
+}
+
/// Reports information about failed conversion into `PushBytes`.
///
/// This should not be needed by general public, except as an additional bound on `TryFrom` when
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 66fce858..276c8630 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -430,11 +430,11 @@ fn non_minimal_scriptints() {
);
assert_eq!(
PushBytes::read_scriptint(<[_; 3] as AsRef<PushBytes>>::as_ref(&[0x8f, 0x00, 0x00])),
- Err(Error::NonMinimalPush)
+ Err(ScriptIntError::NonMinimal)
);
assert_eq!(
PushBytes::read_scriptint(<[_; 2] as AsRef<PushBytes>>::as_ref(&[0x7f, 0x00])),
- Err(Error::NonMinimalPush)
+ Err(ScriptIntError::NonMinimal)
);
assert_eq!(read_scriptint_non_minimal(&[0x80, 0x00]), Ok(0x80));
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.