diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21fa75f6b..6d6f3d7be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,3 +149,24 @@ jobs: with: command: clippy args: -- -D warnings + + fuzz: + name: Fuzz + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - nightly + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + - run: cargo install cargo-fuzz + - uses: actions-rs/cargo@v1 + with: + command: fuzz + args: run compare -- -max_total_time=100 diff --git a/Cargo.lock b/Cargo.lock index 4f05631ba..73c9bfdea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "autocfg" version = "0.1.8" @@ -276,6 +282,17 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libloading" version = "0.7.4" @@ -877,6 +894,7 @@ dependencies = [ "enum_primitive", "hex", "lazy_static", + "libfuzzer-sys", "log", "proptest", "ripemd", diff --git a/Cargo.toml b/Cargo.toml index 1a01276ab..86e004bdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,11 +59,12 @@ path = "src/lib.rs" [features] external-secp = [] -rust-interpreter = [] +test-dependencies = [] [dependencies] bitflags = "2.5" enum_primitive = "0.1" +libfuzzer-sys = "0.4" log = "0.4" proptest = "0.9" ripemd = "0.1" diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..c16ac4703 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "zcash_script-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +zcash_script = { path = "..", features = ["test-dependencies"] } + +[[bin]] +name = "compare" +path = "fuzz_targets/compare.rs" +test = false +doc = false +bench = false diff --git a/fuzz/fuzz_targets/compare.rs b/fuzz/fuzz_targets/compare.rs new file mode 100644 index 000000000..ec80c550a --- /dev/null +++ b/fuzz/fuzz_targets/compare.rs @@ -0,0 +1,24 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +extern crate zcash_script; + +use zcash_script::*; + +fn missing_sighash(_script_code: &[u8], _hash_type: HashType) -> Option<[u8; 32]> { + None +} + +fuzz_target!(|tup: (i64, bool, &[u8], &[u8], u32)| { + // `fuzz_target!` doesn’t support pattern matching in the parameter list. + let (lock_time, is_final, pub_key, sig, flags) = tup; + let ret = check_verify_callback::( + &missing_sighash, + lock_time, + is_final, + pub_key, + sig, + testing::repair_flags(VerificationFlags::from_bits_truncate(flags)), + ); + assert_eq!(ret.0, ret.1); +}); diff --git a/src/lib.rs b/src/lib.rs index 42b6bb1ac..7d59ed47e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ fn check_legacy_sigop_count_script( /// Runs both the C++ and Rust implementations of `ZcashScript::verify_callback` and returns both /// results. This is more useful for testing than the impl that logs a warning if the results differ /// and always returns the C++ result. -fn check_verify_callback( +pub fn check_verify_callback( sighash: SighashCalculator, lock_time: i64, is_final: bool,