diff --git a/Cargo.lock b/Cargo.lock index af1b69b118a..423ea2c0697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,6 +1204,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -1633,6 +1643,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endian-type" version = "0.1.2" @@ -1871,6 +1890,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2322,12 +2356,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ + "base64", "bytes", "futures-channel", "futures-core", @@ -2335,12 +2386,16 @@ dependencies = [ "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2 0.6.0", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2646,6 +2701,22 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -3120,6 +3191,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minicov" version = "0.3.7" @@ -3320,6 +3397,23 @@ dependencies = [ "url", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -4009,12 +4103,50 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "owo-colors" version = "4.2.2" @@ -4868,6 +5000,48 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +[[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rexpect" version = "0.5.0" @@ -5063,7 +5237,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -5081,7 +5255,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" dependencies = [ - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "jni", "log", @@ -5090,7 +5264,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework", + "security-framework 3.2.0", "security-framework-sys", "webpki-root-certs 0.26.11", "windows-sys 0.59.0", @@ -5280,6 +5454,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework" version = "3.2.0" @@ -5287,7 +5474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ "bitflags 2.9.4", - "core-foundation", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -5395,6 +5582,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.14.0" @@ -5671,6 +5870,7 @@ dependencies = [ "p256", "rand 0.8.5", "redis", + "reqwest", "serde", "serde_json", "sha1", @@ -5796,6 +5996,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -5808,6 +6011,27 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -6047,6 +6271,16 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.2" @@ -6138,6 +6372,25 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", "tower-layer", "tower-service", ] @@ -6399,6 +6652,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec-collections" version = "0.4.3" @@ -6626,7 +6885,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -6682,6 +6941,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/tooling/ssa_fuzzer/fuzzer/Cargo.toml b/tooling/ssa_fuzzer/fuzzer/Cargo.toml index 488baf8c574..7f2f262e6cb 100644 --- a/tooling/ssa_fuzzer/fuzzer/Cargo.toml +++ b/tooling/ssa_fuzzer/fuzzer/Cargo.toml @@ -33,6 +33,7 @@ strum = "0.24" sha2.workspace = true p256 = "0.13.2" k256 = "0.13.4" +reqwest = { version = "0.12.23", features = ["json", "blocking"] } [dependencies.noir_ssa_fuzzer] path = "../" diff --git a/tooling/ssa_fuzzer/fuzzer/src/abstract_vm_integration.rs b/tooling/ssa_fuzzer/fuzzer/src/abstract_vm_integration.rs new file mode 100644 index 00000000000..0cd8ecb7c38 --- /dev/null +++ b/tooling/ssa_fuzzer/fuzzer/src/abstract_vm_integration.rs @@ -0,0 +1,89 @@ +//! Module for comparing Brillig output with Brillig-compatible Abstract VM output +use crate::fuzz_lib::fuzzer::FuzzerOutput; +use acvm::acir::circuit::Program; +use acvm::{AcirField, FieldElement}; +use base64::Engine; +use std::time::Instant; + +/// Function for transpiling Brillig bytecode to Abstract VM bytecode +/// The first argument is the Brillig bytecode +pub(crate) type TranspileBrilligBytecodeToAbstractVMBytecode = + Box Result>; +/// Function for executing Abstract VM bytecode +/// The first argument is the Abstract VM bytecode +/// The second argument is the inputs as strings +pub(crate) type ExecuteAbstractVMBytecode = + Box) -> Result, String>>; + +#[derive(Debug)] +pub(crate) enum AbstractVMComparisonResult { + Match, + Mismatch { brillig_outputs: Vec, abstract_vm_outputs: Vec }, + TranspilerError(String), + SimulatorError(String), + BrilligCompilationError(String), +} + +pub(crate) fn compare_with_abstract_vm( + fuzzer_output: &FuzzerOutput, + transpiler: &TranspileBrilligBytecodeToAbstractVMBytecode, + simulator: &ExecuteAbstractVMBytecode, +) -> AbstractVMComparisonResult { + let step_start = Instant::now(); + let brillig_outputs = fuzzer_output.get_return_witnesses(); + let bytecode = if let Some(program) = &fuzzer_output.program { + let serialized = Program::serialize_program(&program.program); + base64::engine::general_purpose::STANDARD.encode(serialized) + } else { + return AbstractVMComparisonResult::BrilligCompilationError( + "No bytecode found in program".to_string(), + ); + }; + log::debug!("Bytecode serialization: {:?}", step_start.elapsed()); + + let step_start = Instant::now(); + let abstract_vm_bytecode = match transpiler(bytecode) { + Ok(bc) => bc, + Err(e) => return AbstractVMComparisonResult::TranspilerError(e), + }; + log::debug!("Transpiler call: {:?}", step_start.elapsed()); + + // TODO(sn): now simulator service perceives first input as a selector, which must fit in 32 bits + if fuzzer_output.get_input_witnesses()[0].num_bits() >= 32 { + return AbstractVMComparisonResult::Match; + } + + let step_start = Instant::now(); + let inputs = fuzzer_output + .get_input_witnesses() + .iter() + .map(FieldElement::to_string) + .collect::>(); + log::debug!("Input extraction: {:?}", step_start.elapsed()); + + let abstract_vm_outputs: Vec = match simulator(abstract_vm_bytecode, inputs) { + Ok(outputs) => { + outputs.iter().map(|output| FieldElement::try_from_str(output).unwrap()).collect() + } + Err(e) => { + // brillig execution failed, so we assume the match + if brillig_outputs.is_empty() { + return AbstractVMComparisonResult::Match; + } + log::info!("Brillig outputs: {brillig_outputs:?}"); + return AbstractVMComparisonResult::SimulatorError(e); + } + }; + + if brillig_outputs.len() != abstract_vm_outputs.len() { + return AbstractVMComparisonResult::Mismatch { brillig_outputs, abstract_vm_outputs }; + } + + for (brillig_out, abstract_vm_out) in brillig_outputs.iter().zip(abstract_vm_outputs.iter()) { + if *brillig_out != *abstract_vm_out { + return AbstractVMComparisonResult::Mismatch { brillig_outputs, abstract_vm_outputs }; + } + } + + AbstractVMComparisonResult::Match +} diff --git a/tooling/ssa_fuzzer/fuzzer/src/acir_vs_brillig.rs b/tooling/ssa_fuzzer/fuzzer/src/acir_vs_brillig.rs index 6fb679cc29d..bf60a4e424f 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/acir_vs_brillig.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/acir_vs_brillig.rs @@ -34,12 +34,12 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { "FULL" => compile_options.show_ssa = true, "FINAL" => { compile_options.show_ssa_pass = - vec!["After Dead Instruction Elimination - ACIR".to_string()]; + vec!["Dead Instruction Elimination (3)".to_string()]; } "FIRST_AND_FINAL" => { compile_options.show_ssa_pass = vec![ "After Removing Unreachable Functions (1)".to_string(), - "After Dead Instruction Elimination - ACIR".to_string(), + "Dead Instruction Elimination (3)".to_string(), ]; } _ => (), diff --git a/tooling/ssa_fuzzer/fuzzer/src/brillig.rs b/tooling/ssa_fuzzer/fuzzer/src/brillig.rs index 3881f8e1d49..4082fa3934a 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/brillig.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/brillig.rs @@ -1,9 +1,14 @@ #![no_main] +mod abstract_vm_integration; pub(crate) mod fuzz_lib; mod mutations; mod utils; +use abstract_vm_integration::{ + AbstractVMComparisonResult, ExecuteAbstractVMBytecode, + TranspileBrilligBytecodeToAbstractVMBytecode, compare_with_abstract_vm, +}; use bincode::serde::{borrow_decode_from_slice, encode_to_vec}; use fuzz_lib::{ fuzz_target_lib::fuzz_target, @@ -16,15 +21,243 @@ use noirc_driver::CompileOptions; use noirc_evaluator::ssa::ir::function::RuntimeType; use noirc_frontend::monomorphization::ast::InlineType as FrontendInlineType; use rand::{SeedableRng, rngs::StdRng}; -use sha1::{Digest, Sha1}; -use utils::{push_fuzzer_output_to_redis_queue, redis}; +use serde_json::{Value, json}; +use std::fs; +use std::io::{BufRead, BufReader, Write}; +use std::process::{Child, Command, Stdio}; +use std::sync::{Mutex, OnceLock}; + +lazy_static::lazy_static! { + static ref SIMULATOR_BIN_PATH: String = std::env::var("SIMULATOR_BIN_PATH").expect("SIMULATOR_BIN_PATH must be set"); + static ref TRANSPILER_BIN_PATH: String = std::env::var("TRANSPILER_BIN_PATH").expect("TRANSPILER_BIN_PATH must be set"); +} + +/// Placeholder for creating a base contract artifact to feed to the transpiler +fn create_base_contract_artifact() -> Value { + json!({ + "noir_version": "1.0.0-beta.11+a92d049c8771332a383aec07474691764c4d90f0-aztec", + "name": "AvmTest", + "functions": [{ + "name": "main2", + "hash": "9106907505563584043", + "is_unconstrained": true, + "custom_attributes": ["public"], + "abi": { + "parameters": [{ + "name": "a", + "type": { + "kind": "integer", + "sign": "unsigned", + "width": 64 + }, + "visibility": "private" + }], + "return_type": { + "abi_type": { + "kind": "integer", + "sign": "unsigned", + "width": 64 + }, + "visibility": "public" + }, + "error_types": { + "17843811134343075018": { + "error_kind": "string", + "string": "Stack too deep" + } + } + }, + "bytecode": "", + "debug_symbols": "dVDNCoQgEH6XOXtIoVp6lYgwm0IQFdOFJXz3HaN228Ne5pvx+5GZHWac0jpqu7gNun6HKWhj9Doap2TUztLrDlUpvIGOM+AtQc4MLsUYA2IR3CwU5GVAG6GzyRgGT2nSIdq8tAdGGYitGKCdCSlw0QZLl9nXXf23cl43j9NOfSs+EaLOeaBJKh1+FsklLWg5GTzHJVl1Y+PLX8x1CB+cwjkFLEm3a1DtRcVEPeTy2xs=", + "expression_width": {"Bounded": {"width": 4}} + }], + "outputs": {}, + "file_map": {} + }) +} + +fn transpile(bytecode_base64: String) -> Result { + let start_time = std::time::Instant::now(); + let mut contract = create_base_contract_artifact(); + + // Set the bytecode in the contract + if let Some(functions) = contract.get_mut("functions").and_then(|f| f.as_array_mut()) { + if let Some(function) = functions.get_mut(0) { + if let Some(obj) = function.as_object_mut() { + obj.insert("bytecode".to_string(), Value::String(bytecode_base64)); + } + } + } + + let contract_json = serde_json::to_string(&contract) + .map_err(|e| format!("Failed to serialize contract: {e}"))?; + + fs::write("contract_artifact.json", contract_json) + .map_err(|e| format!("Failed to write contract artifact: {e}"))?; + let output = Command::new(TRANSPILER_BIN_PATH.as_str()) + .arg("contract_artifact.json") + .arg("output.json") + .output() + .map_err(|e| format!("Failed to execute transpiler: {e}"))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(format!("Transpiler failed: {stderr}")); + } + + log::debug!("Transpiler output: {output:?}"); + + let output_content = fs::read_to_string("output.json") + .map_err(|e| format!("Failed to read output.json: {e}"))?; + + let output_json: Value = serde_json::from_str(&output_content) + .map_err(|e| format!("Failed to parse output.json: {e}"))?; + + let bytecode = output_json + .get("functions") + .and_then(|f| f.as_array()) + .and_then(|arr| arr.first()) + .and_then(|func| func.get("bytecode")) + .and_then(|bc| bc.as_str()) + .ok_or("Failed to extract bytecode from output")?; + + log::debug!("Transpilation took: {:?}", start_time.elapsed()); + Ok(bytecode.to_string()) +} + +/// Global simulator process that stays alive across calls +static SIMULATOR_PROCESS: OnceLock>> = OnceLock::new(); + +struct SimulatorProcess { + child: Child, + stdin: std::process::ChildStdin, + stdout: BufReader, +} + +impl Drop for SimulatorProcess { + fn drop(&mut self) { + let _ = self.child.kill(); + let _ = self.child.wait(); + } +} + +impl SimulatorProcess { + fn new() -> Result { + let mut child = Command::new("node") + .arg(SIMULATOR_BIN_PATH.as_str()) + .env("LOG_LEVEL", "silent") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|e| format!("Failed to start simulator process: {e}"))?; + + let stdin = child.stdin.take().ok_or("Failed to get stdin")?; + let stdout = child.stdout.take().ok_or("Failed to get stdout")?; + let stdout = BufReader::new(stdout); + + Ok(SimulatorProcess { child, stdin, stdout }) + } + + fn execute(&mut self, bytecode: &str, inputs: &[String]) -> Result, String> { + let request = json!({ + "bytecode": bytecode, + "inputs": inputs + }); + + // Send request + let request_line = format!( + "{}\n", + serde_json::to_string(&request) + .map_err(|e| format!("Failed to serialize request: {e}"))? + ); + log::debug!("Simulator request: {request_line}"); -const MAX_EXECUTION_TIME_TO_KEEP_IN_CORPUS: u64 = 3; + self.stdin + .write_all(request_line.as_bytes()) + .map_err(|e| format!("Failed to write to simulator: {e}"))?; + self.stdin.flush().map_err(|e| format!("Failed to flush simulator input: {e}"))?; + + // Read response + log::debug!("Reading response from simulator"); + let mut response_line = String::new(); + self.stdout + .read_line(&mut response_line) + .map_err(|e| format!("Failed to read from simulator: {e}"))?; + + log::debug!("Simulator response: {response_line}"); + + let response: Value = serde_json::from_str(response_line.trim()) + .map_err(|e| format!("Failed to parse simulator response: {e}"))?; + + if let Some(reverted) = response.get("reverted").and_then(|v| v.as_bool()) { + if reverted { + return Err("Execution reverted".to_string()); + } + } + + let outputs = response + .get("output") + .and_then(|v| v.as_array()) + .ok_or("Missing output in simulator response")?; + + let result: Result, String> = outputs + .iter() + .map(|v| v.as_str().ok_or("Invalid output format".to_string()).map(|s| s.to_string())) + .collect(); + + result.map_err(|e| format!("Failed to parse output array: {e}")) + } +} + +fn recreate_simulator() -> Result<(), String> { + let mutex = SIMULATOR_PROCESS.get_or_init(|| Mutex::new(None)); + let mut guard = mutex.lock().map_err(|e| format!("Failed to lock simulator mutex: {e}"))?; + *guard = Some(SimulatorProcess::new()?); + Ok(()) +} + +fn get_or_create_simulator() +-> Result>, String> { + let mutex = SIMULATOR_PROCESS.get_or_init(|| Mutex::new(None)); + let mut guard = mutex.lock().map_err(|e| format!("Failed to lock simulator mutex: {e}"))?; + + if guard.is_none() { + *guard = Some(SimulatorProcess::new()?); + } + + Ok(guard) +} + +/// Initialize the simulator process +fn initialize() { + let _mutex = get_or_create_simulator().expect("Failed to create simulator"); +} + +/// Simulate Abstract VM bytecode execution +fn simulate_abstract_vm(bytecode: String, inputs: Vec) -> Result, String> { + log::debug!( + "Simulating Abstract VM with bytecode length: {}, inputs: {:?}", + bytecode.len(), + inputs + ); + + let mut simulator_guard = get_or_create_simulator()?; + let simulator = simulator_guard.as_mut().ok_or("Simulator not initialized")?; + + simulator.execute(&bytecode, &inputs) +} + +const MAX_EXECUTION_TIME_TO_KEEP_IN_CORPUS: u64 = 10; const INLINE_TYPE: FrontendInlineType = FrontendInlineType::Inline; const BRILLIG_RUNTIME: RuntimeType = RuntimeType::Brillig(INLINE_TYPE); const TARGET_RUNTIMES: [RuntimeType; 1] = [BRILLIG_RUNTIME]; -libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { +libfuzzer_sys::fuzz_target!( + init: { + println!("Initializing simulator process"); + initialize(); + }, |data: &[u8]| -> Corpus { let _ = env_logger::try_init(); let mut compile_options = CompileOptions::default(); @@ -33,12 +266,12 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { "FULL" => compile_options.show_ssa = true, "FINAL" => { compile_options.show_ssa_pass = - vec!["After Dead Instruction Elimination - ACIR".to_string()]; + vec!["Dead Instruction Elimination (3)".to_string()]; } "FIRST_AND_FINAL" => { compile_options.show_ssa_pass = vec![ "After Removing Unreachable Functions (1)".to_string(), - "After Dead Instruction Elimination - ACIR".to_string(), + "Dead Instruction Elimination (3)".to_string(), ]; } _ => (), @@ -52,6 +285,14 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { array_set_enabled: false, ecdsa_secp256k1_enabled: false, ecdsa_secp256r1_enabled: false, + blake2s_hash_enabled: false, + blake3_hash_enabled: false, + aes128_encrypt_enabled: false, + field_to_bytes_to_field_enabled: false, + point_add_enabled: false, + multi_scalar_mul_enabled: false, + shl_enabled: false, + shr_enabled: false, ..InstructionOptions::default() }; let fuzzer_command_options = @@ -69,16 +310,33 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| -> Corpus { let start = std::time::Instant::now(); let fuzzer_output = fuzz_target(fuzzer_data, TARGET_RUNTIMES.to_vec(), options); - // If REDIS_URL is set and generated program is executed - if redis::ensure_redis_connection() { - // cargo-fuzz saves tests with name equal to sha1 of content - let mut hasher = Sha1::new(); - hasher.update(data); - let sha1_hash = hasher.finalize(); - let test_id = format!("{sha1_hash:x}"); - match push_fuzzer_output_to_redis_queue("fuzzer_output", test_id, fuzzer_output) { - Ok(json_str) => log::debug!("{json_str}"), - Err(e) => log::error!("Failed to push to Redis queue: {e}"), + let transpiler: TranspileBrilligBytecodeToAbstractVMBytecode = Box::new(transpile); + let simulator: ExecuteAbstractVMBytecode = Box::new(simulate_abstract_vm); + + match compare_with_abstract_vm(&fuzzer_output, &transpiler, &simulator) { + AbstractVMComparisonResult::Match => { + log::debug!("Abstract VM and Brillig outputs match"); + } + AbstractVMComparisonResult::Mismatch { brillig_outputs, abstract_vm_outputs } => { + log::error!("Abstract VM and Brillig outputs mismatch!"); + log::error!("Brillig outputs: {brillig_outputs:?}"); + log::error!("Abstract VM outputs: {abstract_vm_outputs:?}"); + panic!("Abstract VM vs Brillig mismatch detected"); + } + AbstractVMComparisonResult::TranspilerError(err) => { + panic!("Transpiler error: {err}"); + } + AbstractVMComparisonResult::SimulatorError(err) => { + log::error!("Simulator error: {err}"); + if err.contains("EOF while parsing a value") { + log::warn!("Recreating simulator"); + recreate_simulator().expect("Failed to recreate simulator"); + } else { + panic!("Simulator error: {err}"); + } + } + AbstractVMComparisonResult::BrilligCompilationError(err) => { + log::debug!("Brillig compilation error: {err}"); } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs index 39bda4efaaf..906d1915a07 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs @@ -458,6 +458,9 @@ impl BlockContext { } } Instruction::FieldToBytesToField { field_idx } => { + if !self.options.instruction_options.field_to_bytes_to_field_enabled { + return; + } let field = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let field = match field { Some(field) => field, @@ -468,6 +471,9 @@ impl BlockContext { self.store_variable(&field); } Instruction::Blake2sHash { field_idx, limbs_count } => { + if !self.options.instruction_options.blake2s_hash_enabled { + return; + } let input = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let input = match input { Some(input) => input, @@ -482,6 +488,9 @@ impl BlockContext { self.store_variable(&hash_as_field); } Instruction::Blake3Hash { field_idx, limbs_count } => { + if !self.options.instruction_options.blake3_hash_enabled { + return; + } let input = self.get_stored_variable(&Type::Numeric(NumericType::Field), field_idx); let input = match input { Some(input) => input, @@ -496,6 +505,9 @@ impl BlockContext { self.store_variable(&hash_as_field); } Instruction::Keccakf1600Hash { u64_indices, load_elements_of_array } => { + if !self.options.instruction_options.keccakf1600_hash_enabled { + return; + } let input = match self.insert_array( builder, u64_indices.to_vec(), @@ -520,6 +532,9 @@ impl BlockContext { } } Instruction::Aes128Encrypt { input_idx, input_limbs_count, key_idx, iv_idx } => { + if !self.options.instruction_options.aes128_encrypt_enabled { + return; + } if input_limbs_count == 0 { return; } @@ -554,6 +569,9 @@ impl BlockContext { state_indices, load_elements_of_array, } => { + if !self.options.instruction_options.sha256_compression_enabled { + return; + } let input = match self.insert_array( builder, input_indices.to_vec(), diff --git a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs index c0809ed4173..330c715d35a 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs @@ -28,6 +28,12 @@ pub(crate) struct InstructionOptions { pub(crate) multi_scalar_mul_enabled: bool, pub(crate) ecdsa_secp256r1_enabled: bool, pub(crate) ecdsa_secp256k1_enabled: bool, + pub(crate) blake2s_hash_enabled: bool, + pub(crate) blake3_hash_enabled: bool, + pub(crate) aes128_encrypt_enabled: bool, + pub(crate) field_to_bytes_to_field_enabled: bool, + pub(crate) sha256_compression_enabled: bool, + pub(crate) keccakf1600_hash_enabled: bool, } impl Default for InstructionOptions { @@ -58,6 +64,12 @@ impl Default for InstructionOptions { multi_scalar_mul_enabled: true, ecdsa_secp256r1_enabled: true, ecdsa_secp256k1_enabled: true, + blake2s_hash_enabled: true, + blake3_hash_enabled: true, + aes128_encrypt_enabled: true, + field_to_bytes_to_field_enabled: true, + sha256_compression_enabled: true, + keccakf1600_hash_enabled: true, } } } diff --git a/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs index 968952bb662..96c5edcd279 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/mutations/configuration.rs @@ -464,7 +464,7 @@ pub(crate) enum GenerateInitialWitness { pub(crate) type GenerateInitialWitnessConfig = WeightedSelectionConfig; pub(crate) const BASIC_GENERATE_INITIAL_WITNESS_CONFIGURATION: GenerateInitialWitnessConfig = GenerateInitialWitnessConfig::new([ - (GenerateInitialWitness::Numeric, 10), + (GenerateInitialWitness::Numeric, 30), (GenerateInitialWitness::Array, 1), ]); @@ -478,7 +478,7 @@ pub(crate) enum GenerateType { pub(crate) type GenerateTypeConfig = WeightedSelectionConfig; pub(crate) const BASIC_GENERATE_TYPE_CONFIGURATION: GenerateTypeConfig = GenerateTypeConfig::new([ - (GenerateType::Numeric, 10), + (GenerateType::Numeric, 40), (GenerateType::Reference, 6), (GenerateType::Array, 5), (GenerateType::Slice, 6), diff --git a/tooling/ssa_fuzzer/fuzzer/src/utils/mod.rs b/tooling/ssa_fuzzer/fuzzer/src/utils/mod.rs index 8c6e3bbaaf2..7d3fa6fe1b0 100644 --- a/tooling/ssa_fuzzer/fuzzer/src/utils/mod.rs +++ b/tooling/ssa_fuzzer/fuzzer/src/utils/mod.rs @@ -1,3 +1,5 @@ +// TODO(sn): separate to features +#![allow(dead_code)] pub(crate) mod redis; use crate::fuzz_lib::fuzzer::FuzzerOutput; use base64::{Engine as _, engine::general_purpose};