Fix script::Builder::push_slice() handling of 0x00
What changed, and why it matters
This commit fixes a bug in the rust-bitcoin library where calling a function to push the single byte 0x00 onto a Bitcoin script would incorrectly push an empty byte array instead. In Bitcoin scripts, pushing the number 0 (empty) and pushing the byte 0x00 are semantically different operations. The fix ensures 0x00 is pushed as a one-byte data push, while the number 0 remains represented as an empty push (OP_0).
Review downstream code that uses `Builder::push_slice()` with a single 0x00 byte to determine whether the previous empty-push behavior created incorrect scripts, signatures, or transaction hashes. Update the ignored test and add regression tests covering 0x00, OP_0, and other boundary values. Consider whether this bug could have produced invalid or non-standard Bitcoin transactions in production.
Security signals we found
Script encoding bug: 0x00 byte pushed as empty array instead of one-byte push
Potential for non-standard or invalid transaction scripts depending on use case
Could affect protocols relying on exact script bytes (e.g., covenants, contracts, signature hashes)
No explicit security disclosure or CVE referenced in commit
Evidence from the diff
The Builder::push_slice() method in bitcoin/src/blockdata/script/builder.rs previously treated a single-byte slice containing 0x00 as equivalent to pushing the number 0, emitting OP_PUSHBYTES_0 (which encodes an empty push). The corrected code removes the special-case handling for 0x00, so a one-byte 0x00 slice is now pushed non-minimally as an actual 0x00 byte. Single-byte values 0x81 (negative 1) and 1..=16 still use their minimal opcodes. A related test was marked #[ignore] because it encoded the old, incorrect behavior.
Changed components
bitcoin/src/blockdata/script/builder.rsbitcoin/src/blockdata/script/tests.rsBuilder::push_slice() public APIInspect captured patch +4 / −4
diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs
index 90f21836..2935dd46 100644
--- a/bitcoin/src/blockdata/script/builder.rs
+++ b/bitcoin/src/blockdata/script/builder.rs
@@ -76,15 +76,14 @@ impl<T> Builder<T> {
/// Adds instructions to push some arbitrary data onto the stack.
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) {
+ if bytes.len() == 1 {
match bytes[0] {
0x81 => self.push_opcode(OP_1NEGATE),
- 0 => self.push_opcode(OP_PUSHBYTES_0),
1..=16 => self.push_opcode(Opcode::from(bytes[0] + (OP_1.to_u8() - 1))),
- _ => self, // unreachable arm
+ _ => self.push_slice_non_minimal(data),
}
} else {
- self.push_slice_non_minimal(data.as_ref())
+ self.push_slice_non_minimal(data)
}
}
diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs
index 276c8630..f5049092 100644
--- a/bitcoin/src/blockdata/script/tests.rs
+++ b/bitcoin/src/blockdata/script/tests.rs
@@ -16,6 +16,7 @@ type Script = crate::ScriptSig;
type ScriptBuf = crate::ScriptSigBuf;
#[test]
+#[ignore] // bad test; will be fixed in next commit
#[rustfmt::skip]
fn script() {
let mut comp = vec![];
Why this scored 61/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.