From 5f1100e47ea3400ac33556c014984d27d6d65c40 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 17 Oct 2023 14:02:46 -0700 Subject: [PATCH] Add rune fuzz targets (#2526) --- fuzz/Cargo.toml | 18 ++++++++++ fuzz/fuzz_targets/runestone_decipher.rs | 36 ++++++++++++++++++++ fuzz/fuzz_targets/transaction_builder.rs | 19 +++++++---- fuzz/fuzz_targets/varint_decode.rs | 17 +++++++++ fuzz/fuzz_targets/varint_encode.rs | 11 ++++++ justfile | 12 ++++++- src/lib.rs | 11 ++++-- src/runes.rs | 8 ++--- src/subcommand/wallet/transaction_builder.rs | 4 +-- 9 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 fuzz/fuzz_targets/runestone_decipher.rs create mode 100644 fuzz/fuzz_targets/varint_decode.rs create mode 100644 fuzz/fuzz_targets/varint_encode.rs diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 4725759858..60c9958501 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -19,8 +19,26 @@ bitcoin = { version = "0.30.0", features = ["rand"] } libfuzzer-sys = "0.4" ord = { path = ".." } +[[bin]] +name = "runestone-decipher" +path = "fuzz_targets/runestone_decipher.rs" +test = false +doc = false + [[bin]] name = "transaction-builder" path = "fuzz_targets/transaction_builder.rs" test = false doc = false + +[[bin]] +name = "varint-encode" +path = "fuzz_targets/varint_encode.rs" +test = false +doc = false + +[[bin]] +name = "varint-decode" +path = "fuzz_targets/varint_decode.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/runestone_decipher.rs b/fuzz/fuzz_targets/runestone_decipher.rs new file mode 100644 index 0000000000..43cdb315aa --- /dev/null +++ b/fuzz/fuzz_targets/runestone_decipher.rs @@ -0,0 +1,36 @@ +#![no_main] + +use { + bitcoin::{ + locktime, opcodes, + script::{self, PushBytes}, + Transaction, TxOut, + }, + libfuzzer_sys::fuzz_target, + ord::runes::Runestone, +}; + +fuzz_target!(|input: Vec>| { + let mut builder = script::Builder::new() + .push_opcode(opcodes::all::OP_RETURN) + .push_slice(b"RUNE_TEST"); + + for slice in input { + let Ok(push): Result<&PushBytes, _> = slice.as_slice().try_into() else { + continue; + }; + builder = builder.push_slice(push); + } + + let tx = Transaction { + input: Vec::new(), + lock_time: locktime::absolute::LockTime::ZERO, + output: vec![TxOut { + script_pubkey: builder.into_script(), + value: 0, + }], + version: 0, + }; + + Runestone::from_transaction(&tx); +}); diff --git a/fuzz/fuzz_targets/transaction_builder.rs b/fuzz/fuzz_targets/transaction_builder.rs index ad1be3dcfe..e91f5af2d3 100644 --- a/fuzz/fuzz_targets/transaction_builder.rs +++ b/fuzz/fuzz_targets/transaction_builder.rs @@ -7,7 +7,7 @@ use { Amount, OutPoint, }, libfuzzer_sys::fuzz_target, - ord::{FeeRate, SatPoint, TransactionBuilder}, + ord::{FeeRate, SatPoint, Target, TransactionBuilder}, std::collections::BTreeMap, }; @@ -62,29 +62,34 @@ fuzz_target!(|input: Input| { .assume_checked(), ]; - let Ok(fee_rate) = FeeRate::try_from(input.fee_rate) else { return; }; + let Ok(fee_rate) = FeeRate::try_from(input.fee_rate) else { + return; + }; match input.output_value { Some(output_value) => { - let _ = TransactionBuilder::build_transaction_with_value( + let _ = TransactionBuilder::new( satpoint, inscriptions, amounts, recipient, change, fee_rate, - Amount::from_sat(output_value), - ); + Target::Value(Amount::from_sat(output_value)), + ) + .build_transaction(); } None => { - let _ = TransactionBuilder::build_transaction_with_postage( + let _ = TransactionBuilder::new( satpoint, inscriptions, amounts, recipient, change, fee_rate, - ); + Target::Postage, + ) + .build_transaction(); } } }); diff --git a/fuzz/fuzz_targets/varint_decode.rs b/fuzz/fuzz_targets/varint_decode.rs new file mode 100644 index 0000000000..b7022c7241 --- /dev/null +++ b/fuzz/fuzz_targets/varint_decode.rs @@ -0,0 +1,17 @@ +#![no_main] + +use {libfuzzer_sys::fuzz_target, ord::runes::varint}; + +fuzz_target!(|input: &[u8]| { + let mut i = 0; + + while i < input.len() { + let Ok((decoded, length)) = varint::decode(&input[i..]) else { + break; + }; + let mut encoded = Vec::new(); + varint::encode_to_vec(decoded, &mut encoded); + assert_eq!(encoded, &input[i..i + length]); + i += length; + } +}); diff --git a/fuzz/fuzz_targets/varint_encode.rs b/fuzz/fuzz_targets/varint_encode.rs new file mode 100644 index 0000000000..b89c322562 --- /dev/null +++ b/fuzz/fuzz_targets/varint_encode.rs @@ -0,0 +1,11 @@ +#![no_main] + +use {libfuzzer_sys::fuzz_target, ord::runes::varint}; + +fuzz_target!(|input: u128| { + let mut encoded = Vec::new(); + varint::encode_to_vec(input, &mut encoded); + let (decoded, length) = varint::decode(&encoded).unwrap(); + assert_eq!(length, encoded.len()); + assert_eq!(decoded, input); +}); diff --git a/justfile b/justfile index b5739fc00d..938f294481 100644 --- a/justfile +++ b/justfile @@ -68,7 +68,17 @@ profile-tests: | tee test-times.txt fuzz: - cd fuzz && cargo +nightly fuzz run transaction-builder + #!/usr/bin/env bash + set -euxo pipefail + + cd fuzz + + while true; do + cargo +nightly fuzz run transaction-builder -- -max_total_time=60 + cargo +nightly fuzz run runestone-decipher -- -max_total_time=60 + cargo +nightly fuzz run varint-decode -- -max_total_time=60 + cargo +nightly fuzz run varint-encode -- -max_total_time=60 + done open: open http://localhost diff --git a/src/lib.rs b/src/lib.rs index 5f7c024c3f..d5f4d49d68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,8 +79,13 @@ use { }; pub use crate::{ - fee_rate::FeeRate, inscription::Inscription, object::Object, rarity::Rarity, sat::Sat, - sat_point::SatPoint, subcommand::wallet::transaction_builder::TransactionBuilder, + fee_rate::FeeRate, + inscription::Inscription, + object::Object, + rarity::Rarity, + sat::Sat, + sat_point::SatPoint, + subcommand::wallet::transaction_builder::{Target, TransactionBuilder}, }; #[cfg(test)] @@ -121,7 +126,7 @@ mod outgoing; mod page_config; pub mod rarity; mod representation; -mod runes; +pub mod runes; pub mod sat; mod sat_point; pub mod subcommand; diff --git a/src/runes.rs b/src/runes.rs index 7a6cd7f461..3fdd9cdc6d 100644 --- a/src/runes.rs +++ b/src/runes.rs @@ -1,8 +1,8 @@ use {self::error::Error, super::*}; -pub(crate) use { - edict::Edict, etching::Etching, pile::Pile, rune::Rune, rune_id::RuneId, runestone::Runestone, -}; +pub use runestone::Runestone; + +pub(crate) use {edict::Edict, etching::Etching, pile::Pile, rune::Rune, rune_id::RuneId}; const MAX_DIVISIBILITY: u8 = 38; @@ -13,7 +13,7 @@ mod pile; mod rune; mod rune_id; mod runestone; -pub(crate) mod varint; +pub mod varint; type Result = std::result::Result; diff --git a/src/subcommand/wallet/transaction_builder.rs b/src/subcommand/wallet/transaction_builder.rs index 4e7c549640..bac9934bdf 100644 --- a/src/subcommand/wallet/transaction_builder.rs +++ b/src/subcommand/wallet/transaction_builder.rs @@ -121,7 +121,7 @@ impl TransactionBuilder { pub(crate) const TARGET_POSTAGE: Amount = Amount::from_sat(10_000); pub(crate) const MAX_POSTAGE: Amount = Amount::from_sat(2 * 10_000); - pub(crate) fn new( + pub fn new( outgoing: SatPoint, inscriptions: BTreeMap, amounts: BTreeMap, @@ -145,7 +145,7 @@ impl TransactionBuilder { } } - pub(crate) fn build_transaction(self) -> Result { + pub fn build_transaction(self) -> Result { if self.change_addresses.len() < 2 { return Err(Error::DuplicateAddress( self.change_addresses.first().unwrap().clone(),