Add `arbitrary_witness` and `parse_int` fuzz targets
What changed, and why it matters
This commit only adds two new fuzz-testing targets (automated test programs that feed random data to library functions) for the rust-bitcoin project. It does not change any production code, fix a bug, or alter behavior visible to users. There is no security issue in the commit itself.
No action required; this is a routine addition of fuzz coverage. Continue normal review of any future crashes the new targets may surface.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff registers two new fuzz targets in the CI workflow and Cargo manifest: bitcoin_arbitrary_witness (round-trip consensus serialize/deserialize of arbitrary Witness values plus WitnessExt accessors) and units_parse_int (exercise parse_int helpers for signed/unsigned integer types and hex prefix variants). Both targets are pure test harnesses under fuzz/fuzz_targets; no library source files are modified.
Changed components
fuzz/fuzz_targets/bitcoin/arbitrary_witness.rsfuzz/fuzz_targets/units/parse_int.rs.github/workflows/cron-daily-fuzz.ymlfuzz/Cargo.tomlInspect captured patch +175 / −0
diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml
index e71150aa..63f67eca 100644
--- a/.github/workflows/cron-daily-fuzz.yml
+++ b/.github/workflows/cron-daily-fuzz.yml
@@ -21,6 +21,7 @@ jobs:
bitcoin_arbitrary_block,
bitcoin_arbitrary_script,
bitcoin_arbitrary_transaction,
+ bitcoin_arbitrary_witness,
bitcoin_deserialize_block,
bitcoin_deserialize_prefilled_transaction,
bitcoin_deserialize_psbt,
@@ -41,6 +42,7 @@ jobs:
p2p_deserialize_raw_net_msg,
units_arbitrary_weight,
units_parse_amount,
+ units_parse_int,
]
steps:
- name: Install test dependencies
diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml
index 7c764a67..a1a6aae5 100644
--- a/fuzz/Cargo.toml
+++ b/fuzz/Cargo.toml
@@ -33,6 +33,10 @@ path = "fuzz_targets/bitcoin/arbitrary_script.rs"
name = "bitcoin_arbitrary_transaction"
path = "fuzz_targets/bitcoin/arbitrary_transaction.rs"
+[[bin]]
+name = "bitcoin_arbitrary_witness"
+path = "fuzz_targets/bitcoin/arbitrary_witness.rs"
+
[[bin]]
name = "bitcoin_deserialize_block"
path = "fuzz_targets/bitcoin/deserialize_block.rs"
@@ -112,3 +116,7 @@ path = "fuzz_targets/units/arbitrary_weight.rs"
[[bin]]
name = "units_parse_amount"
path = "fuzz_targets/units/parse_amount.rs"
+
+[[bin]]
+name = "units_parse_int"
+path = "fuzz_targets/units/parse_int.rs"
diff --git a/fuzz/fuzz_targets/bitcoin/arbitrary_witness.rs b/fuzz/fuzz_targets/bitcoin/arbitrary_witness.rs
new file mode 100644
index 00000000..c61ba466
--- /dev/null
+++ b/fuzz/fuzz_targets/bitcoin/arbitrary_witness.rs
@@ -0,0 +1,59 @@
+use arbitrary::{Arbitrary, Unstructured};
+use honggfuzz::fuzz;
+use bitcoin::consensus::{deserialize, serialize};
+use bitcoin::Witness;
+use bitcoin::blockdata::witness::WitnessExt;
+
+fn do_test(data: &[u8]) {
+ let mut u = Unstructured::new(data);
+
+ if let Ok(mut witness) = Witness::arbitrary(&mut u) {
+ let serialized = serialize(&witness);
+
+ let _ = witness.witness_script();
+ let _ = witness.taproot_leaf_script();
+
+ let deserialized: Result<Witness, _> = deserialize(serialized.as_slice());
+ assert!(deserialized.is_ok(), "Deserialization error: {:?}", deserialized.err().unwrap());
+ assert_eq!(deserialized.unwrap(), witness);
+
+ if let Ok(element_bytes) = Vec::<u8>::arbitrary(&mut u) {
+ witness.push(element_bytes.as_slice());
+ }
+ }
+}
+
+fn main() {
+ loop {
+ fuzz!(|data| {
+ do_test(data);
+ });
+ }
+}
+
+#[cfg(all(test, fuzzing))]
+mod tests {
+ fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
+ let mut b = 0;
+ for (idx, c) in hex.as_bytes().iter().enumerate() {
+ b <<= 4;
+ match *c {
+ b'A'..=b'F' => b |= c - b'A' + 10,
+ b'a'..=b'f' => b |= c - b'a' + 10,
+ b'0'..=b'9' => b |= c - b'0',
+ _ => panic!("Bad hex"),
+ }
+ if (idx & 1) == 1 {
+ out.push(b);
+ b = 0;
+ }
+ }
+ }
+
+ #[test]
+ fn duplicate_crash() {
+ let mut a = Vec::new();
+ extend_vec_from_hex("00", &mut a);
+ super::do_test(&a);
+ }
+}
diff --git a/fuzz/fuzz_targets/units/parse_int.rs b/fuzz/fuzz_targets/units/parse_int.rs
new file mode 100644
index 00000000..e37c4da0
--- /dev/null
+++ b/fuzz/fuzz_targets/units/parse_int.rs
@@ -0,0 +1,106 @@
+use arbitrary::Unstructured;
+use honggfuzz::fuzz;
+use bitcoin::parse_int;
+
+fn do_test(data: &[u8]) {
+ let mut u = Unstructured::new(data);
+
+ if let Ok(s) = u.arbitrary::<&str>() {
+ let _ = parse_int::int_from_str::<i8>(s);
+ let _ = parse_int::int_from_str::<i16>(s);
+ let _ = parse_int::int_from_str::<i32>(s);
+ let _ = parse_int::int_from_str::<i64>(s);
+ let _ = parse_int::int_from_str::<i128>(s);
+
+ let _ = parse_int::int_from_str::<u8>(s);
+ let _ = parse_int::int_from_str::<u16>(s);
+ let _ = parse_int::int_from_str::<u32>(s);
+ let _ = parse_int::int_from_str::<u64>(s);
+ let _ = parse_int::int_from_str::<u128>(s);
+
+ let _ = parse_int::hex_remove_prefix(s);
+
+ if parse_int::hex_u32_prefixed(s).is_ok() {
+ assert!(parse_int::hex_u32(s).is_ok());
+ assert!(parse_int::hex_u32_unprefixed(s).is_err());
+ }
+
+ if parse_int::hex_u32_unprefixed(s).is_ok() {
+ assert!(parse_int::hex_u32(s).is_ok());
+ assert!(parse_int::hex_u32_prefixed(s).is_err());
+ }
+
+ if parse_int::hex_u128_prefixed(s).is_ok() {
+ assert!(parse_int::hex_u128(s).is_ok());
+ assert!(parse_int::hex_u128_unprefixed(s).is_err());
+ }
+
+ if parse_int::hex_u128_unprefixed(s).is_ok() {
+ assert!(parse_int::hex_u128(s).is_ok());
+ assert!(parse_int::hex_u128_prefixed(s).is_err());
+ }
+ }
+
+ if let Ok(s) = u.arbitrary::<String>() {
+ let _ = parse_int::int_from_string::<i8>(s.clone());
+ let _ = parse_int::int_from_string::<i16>(s.clone());
+ let _ = parse_int::int_from_string::<i32>(s.clone());
+ let _ = parse_int::int_from_string::<i64>(s.clone());
+ let _ = parse_int::int_from_string::<i128>(s.clone());
+
+ let _ = parse_int::int_from_string::<u8>(s.clone());
+ let _ = parse_int::int_from_string::<u16>(s.clone());
+ let _ = parse_int::int_from_string::<u32>(s.clone());
+ let _ = parse_int::int_from_string::<u64>(s.clone());
+ let _ = parse_int::int_from_string::<u128>(s);
+ }
+
+ if let Ok(s) = u.arbitrary::<Box<str>>() {
+ let _ = parse_int::int_from_box::<i8>(s.clone());
+ let _ = parse_int::int_from_box::<i16>(s.clone());
+ let _ = parse_int::int_from_box::<i32>(s.clone());
+ let _ = parse_int::int_from_box::<i64>(s.clone());
+ let _ = parse_int::int_from_box::<i128>(s.clone());
+
+ let _ = parse_int::int_from_box::<u8>(s.clone());
+ let _ = parse_int::int_from_box::<u16>(s.clone());
+ let _ = parse_int::int_from_box::<u32>(s.clone());
+ let _ = parse_int::int_from_box::<u64>(s.clone());
+ let _ = parse_int::int_from_box::<u128>(s);
+ }
+}
+
+fn main() {
+ loop {
+ fuzz!(|data| {
+ do_test(data);
+ });
+ }
+}
+
+#[cfg(all(test, fuzzing))]
+mod tests {
+ fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
+ let mut b = 0;
+ for (idx, c) in hex.as_bytes().iter().enumerate() {
+ b <<= 4;
+ match *c {
+ b'A'..=b'F' => b |= c - b'A' + 10,
+ b'a'..=b'f' => b |= c - b'a' + 10,
+ b'0'..=b'9' => b |= c - b'0',
+ _ => panic!("Bad hex"),
+ }
+ if (idx & 1) == 1 {
+ out.push(b);
+ b = 0;
+ }
+ }
+ }
+
+ #[test]
+ fn duplicate_crash() {
+ let mut a = Vec::new();
+ extend_vec_from_hex("00", &mut a);
+ super::do_test(&a);
+ }
+}
Why this scored 15/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.