From 539751fbd733fdfaa3ffa431084e1ff2ef82a032 Mon Sep 17 00:00:00 2001 From: martyall Date: Thu, 2 Jan 2025 17:25:41 -0800 Subject: [PATCH] added test_mips_elf --- Cargo.lock | 1 + o1vm/Cargo.toml | 1 + o1vm/src/cannon.rs | 19 ++++ o1vm/src/pickles/main.rs | 213 +++++++++++------------------------- o1vm/src/pickles/mod.rs | 113 +++++++++++++++++++ o1vm/src/pickles/proof.rs | 29 ++++- o1vm/tests/test_mips_elf.rs | 84 ++++++++++++++ 7 files changed, 310 insertions(+), 150 deletions(-) create mode 100644 o1vm/tests/test_mips_elf.rs diff --git a/Cargo.lock b/Cargo.lock index 456d4ce2d5..84ebc1f291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1997,6 +1997,7 @@ dependencies = [ "mina-curves", "mina-poseidon", "o1-utils", + "once_cell", "os_pipe", "poly-commitment", "rand", diff --git a/o1vm/Cargo.toml b/o1vm/Cargo.toml index a91b75d54c..fa75be09b1 100644 --- a/o1vm/Cargo.toml +++ b/o1vm/Cargo.toml @@ -45,6 +45,7 @@ log.workspace = true mina-curves.workspace = true mina-poseidon.workspace = true o1-utils.workspace = true +once_cell.workspace = true os_pipe.workspace = true poly-commitment.workspace = true rand.workspace = true diff --git a/o1vm/src/cannon.rs b/o1vm/src/cannon.rs index b9be929523..a888bf898d 100644 --- a/o1vm/src/cannon.rs +++ b/o1vm/src/cannon.rs @@ -221,6 +221,25 @@ pub struct VmConfiguration { pub host: Option, } +impl Default for VmConfiguration { + fn default() -> Self { + VmConfiguration { + input_state_file: "state.json".to_string(), + output_state_file: "out.json".to_string(), + metadata_file: None, + proof_at: StepFrequency::Never, + stop_at: StepFrequency::Never, + snapshot_state_at: StepFrequency::Never, + info_at: StepFrequency::Never, + proof_fmt: "proof-%d.json".to_string(), + snapshot_fmt: "state-%d.json".to_string(), + pprof_cpu: false, + halt_address: None, + host: None, + } + } +} + #[derive(Debug, Clone)] pub struct Start { pub time: std::time::Instant, diff --git a/o1vm/src/pickles/main.rs b/o1vm/src/pickles/main.rs index 4d3b185c8a..f7ca7e5546 100644 --- a/o1vm/src/pickles/main.rs +++ b/o1vm/src/pickles/main.rs @@ -1,159 +1,16 @@ -use ark_ff::UniformRand; use clap::Parser; -use kimchi::circuits::domains::EvaluationDomains; use log::debug; -use mina_curves::pasta::{Fp, Vesta, VestaParameters}; -use mina_poseidon::{ - constants::PlonkSpongeConstantsKimchi, - sponge::{DefaultFqSponge, DefaultFrSponge}, -}; +use mina_curves::pasta::{Fp, Vesta}; use o1vm::{ cannon::{self, Start, State}, cli, elf_loader, - interpreters::mips::{ - column::N_MIPS_REL_COLS, - constraints as mips_constraints, - witness::{self as mips_witness}, - Instruction, - }, - pickles::{proof::ProofInputs, prover, verifier}, + interpreters::mips::witness::{self as mips_witness}, + pickles::{cannon_main, DOMAIN_FP, DOMAIN_SIZE}, preimage_oracle::{NullPreImageOracle, PreImageOracle, PreImageOracleT}, test_preimage_read, }; use poly_commitment::{ipa::SRS, SRS as _}; -use std::{fs::File, io::BufReader, path::Path, process::ExitCode, time::Instant}; - -pub const DOMAIN_SIZE: usize = 1 << 15; - -pub fn cannon_main(args: cli::cannon::RunArgs) { - let mut rng = rand::thread_rng(); - - let configuration: cannon::VmConfiguration = args.vm_cfg.into(); - - let file = - File::open(&configuration.input_state_file).expect("Error opening input state file "); - - let reader = BufReader::new(file); - // Read the JSON contents of the file as an instance of `State`. - let state: State = serde_json::from_reader(reader).expect("Error reading input state file"); - - let meta = &configuration.metadata_file.as_ref().map(|f| { - let meta_file = - File::open(f).unwrap_or_else(|_| panic!("Could not open metadata file {}", f)); - serde_json::from_reader(BufReader::new(meta_file)) - .unwrap_or_else(|_| panic!("Error deserializing metadata file {}", f)) - }); - - // Initialize some data used for statistical computations - let start = Start::create(state.step as usize); - - let domain_fp = EvaluationDomains::::create(DOMAIN_SIZE).unwrap(); - - let srs: SRS = match &args.srs_cache { - Some(cache) => { - debug!("Loading SRS from cache {}", cache); - let file = File::open(cache).expect("Error opening srs_cache file "); - let reader = BufReader::new(file); - let srs: SRS = rmp_serde::from_read(reader).unwrap(); - debug!("SRS loaded successfully from cache"); - srs - } - None => { - debug!("No SRS cache provided. Creating SRS from scratch"); - let srs = SRS::create(DOMAIN_SIZE); - srs.get_lagrange_basis(domain_fp.d1); - debug!("SRS created successfully"); - srs - } - }; - - // Initialize the environments - let mut mips_wit_env = match configuration.host.clone() { - Some(host) => { - let mut po = PreImageOracle::create(host); - let _child = po.start(); - mips_witness::Env::>::create( - cannon::PAGE_SIZE as usize, - state, - Box::new(po), - ) - } - None => { - debug!("No preimage oracle provided 🤞"); - // warning: the null preimage oracle has no data and will crash the program if used - mips_witness::Env::>::create( - cannon::PAGE_SIZE as usize, - state, - Box::new(NullPreImageOracle), - ) - } - }; - - let constraints = mips_constraints::get_all_constraints::(); - - let mut curr_proof_inputs: ProofInputs = ProofInputs::new(DOMAIN_SIZE); - while !mips_wit_env.halt { - let _instr: Instruction = mips_wit_env.step(&configuration, meta, &start); - for (scratch, scratch_chunk) in mips_wit_env - .scratch_state - .iter() - .zip(curr_proof_inputs.evaluations.scratch.iter_mut()) - { - scratch_chunk.push(*scratch); - } - for (scratch, scratch_chunk) in mips_wit_env - .scratch_state_inverse - .iter() - .zip(curr_proof_inputs.evaluations.scratch_inverse.iter_mut()) - { - scratch_chunk.push(*scratch); - } - curr_proof_inputs - .evaluations - .instruction_counter - .push(Fp::from(mips_wit_env.instruction_counter)); - // FIXME: Might be another value - curr_proof_inputs.evaluations.error.push(Fp::rand(&mut rng)); - - curr_proof_inputs - .evaluations - .selector - .push(Fp::from((mips_wit_env.selector - N_MIPS_REL_COLS) as u64)); - - if curr_proof_inputs.evaluations.instruction_counter.len() == DOMAIN_SIZE { - let start_iteration = Instant::now(); - debug!("Limit of {DOMAIN_SIZE} reached. We make a proof, verify it (for testing) and start with a new chunk"); - let proof = prover::prove::< - Vesta, - DefaultFqSponge, - DefaultFrSponge, - _, - >(domain_fp, &srs, curr_proof_inputs, &constraints, &mut rng) - .unwrap(); - // Check that the proof is correct. This is for testing purposes. - // Leaving like this for now. - debug!( - "Proof generated in {elapsed} μs", - elapsed = start_iteration.elapsed().as_micros() - ); - { - let start_iteration = Instant::now(); - let verif = verifier::verify::< - Vesta, - DefaultFqSponge, - DefaultFrSponge, - >(domain_fp, &srs, &constraints, &proof); - debug!( - "Verification done in {elapsed} μs", - elapsed = start_iteration.elapsed().as_micros() - ); - assert!(verif); - } - - curr_proof_inputs = ProofInputs::new(DOMAIN_SIZE); - } - } -} +use std::{fs::File, io::BufReader, path::Path, process::ExitCode}; fn gen_state_json(arg: cli::cannon::GenStateJsonArgs) -> Result<(), String> { let path = Path::new(&arg.input); @@ -169,7 +26,67 @@ pub fn main() -> ExitCode { match args { cli::Commands::Cannon(args) => match args { cli::cannon::Cannon::Run(args) => { - cannon_main(args); + let configuration: cannon::VmConfiguration = args.vm_cfg.into(); + + // Read the JSON contents of the file as an instance of `State`. + let state: State = { + let file = File::open(&configuration.input_state_file) + .expect("Error opening input state file "); + let reader = BufReader::new(file); + serde_json::from_reader(reader).expect("Error reading input state file") + }; + + // Initialize some data used for statistical computations + let start = Start::create(state.step as usize); + + let meta = &configuration.metadata_file.as_ref().map(|f| { + let meta_file = File::open(f) + .unwrap_or_else(|_| panic!("Could not open metadata file {}", f)); + serde_json::from_reader(BufReader::new(meta_file)) + .unwrap_or_else(|_| panic!("Error deserializing metadata file {}", f)) + }); + + // Initialize the environments + let mips_wit_env = match configuration.host.clone() { + Some(host) => { + let mut po = PreImageOracle::create(host); + let _child = po.start(); + mips_witness::Env::>::create( + cannon::PAGE_SIZE as usize, + state, + Box::new(po), + ) + } + None => { + debug!("No preimage oracle provided 🤞"); + // warning: the null preimage oracle has no data and will crash the program if used + mips_witness::Env::>::create( + cannon::PAGE_SIZE as usize, + state, + Box::new(NullPreImageOracle), + ) + } + }; + + let srs: SRS = match &args.srs_cache { + Some(cache) => { + debug!("Loading SRS from cache {}", cache); + let file = File::open(cache).expect("Error opening srs_cache file "); + let reader = BufReader::new(file); + let srs: SRS = rmp_serde::from_read(reader).unwrap(); + debug!("SRS loaded successfully from cache"); + srs + } + None => { + debug!("No SRS cache provided. Creating SRS from scratch"); + let srs = SRS::create(DOMAIN_SIZE); + srs.get_lagrange_basis(DOMAIN_FP.d1); + debug!("SRS created successfully"); + srs + } + }; + + cannon_main(configuration, mips_wit_env, &srs, start, meta); } cli::cannon::Cannon::TestPreimageRead(args) => { test_preimage_read::main(args); diff --git a/o1vm/src/pickles/mod.rs b/o1vm/src/pickles/mod.rs index 193162e90b..6382a7ee71 100644 --- a/o1vm/src/pickles/mod.rs +++ b/o1vm/src/pickles/mod.rs @@ -12,6 +12,30 @@ //! O1VM_FLAVOR=pickles bash run-code.sh //! ``` +use std::time::Instant; + +use self::proof::ProofInputs; +use crate::{ + cannon::{self, Start}, + interpreters::mips::{ + column::N_MIPS_REL_COLS, constraints as mips_constraints, witness as mips_witness, + Instruction, + }, + preimage_oracle::PreImageOracleT, +}; +use ark_ff::UniformRand; +use kimchi::circuits::domains::EvaluationDomains; +use kimchi_msm::expr::E; +use log::debug; +use mina_curves::pasta::{Fp, Vesta, VestaParameters}; +use mina_poseidon::{ + constants::PlonkSpongeConstantsKimchi, + sponge::{DefaultFqSponge, DefaultFrSponge}, +}; +use once_cell::sync::Lazy; +use poly_commitment::ipa::SRS; +use rand::rngs::ThreadRng; + pub mod column_env; pub mod proof; pub mod prover; @@ -33,5 +57,94 @@ pub const DEGREE_QUOTIENT_POLYNOMIAL: u64 = 7; /// added for the selectors. pub const TOTAL_NUMBER_OF_CONSTRAINTS: usize = 464; +pub const DOMAIN_SIZE: usize = 1 << 15; + +pub static DOMAIN_FP: Lazy> = + Lazy::new(|| EvaluationDomains::::create(DOMAIN_SIZE).unwrap()); + +fn prove_and_verify( + srs: &SRS, + curr_proof_inputs: ProofInputs, + constraints: &[E], + rng: &mut ThreadRng, +) { + let start_iteration = Instant::now(); + let proof = prover::prove::< + Vesta, + DefaultFqSponge, + DefaultFrSponge, + _, + >(*DOMAIN_FP, srs, curr_proof_inputs, constraints, rng) + .unwrap(); + + debug!( + "Proof generated in {elapsed} μs", + elapsed = start_iteration.elapsed().as_micros() + ); + let verif = verifier::verify::< + Vesta, + DefaultFqSponge, + DefaultFrSponge, + >(*DOMAIN_FP, srs, constraints, &proof); + debug!( + "Verification done in {elapsed} μs", + elapsed = start_iteration.elapsed().as_micros() + ); + assert!(verif); +} + +pub fn cannon_main( + configuration: cannon::VmConfiguration, + mut mips_wit_env: mips_witness::Env>, + srs: &SRS, + start: Start, + meta: &Option, +) { + let mut rng = rand::thread_rng(); + + let constraints = mips_constraints::get_all_constraints::(); + + let mut curr_proof_inputs: ProofInputs = ProofInputs::new(DOMAIN_SIZE); + while !mips_wit_env.halt { + let _instr: Instruction = mips_wit_env.step(&configuration, meta, &start); + for (scratch, scratch_chunk) in mips_wit_env + .scratch_state + .iter() + .zip(curr_proof_inputs.evaluations.scratch.iter_mut()) + { + scratch_chunk.push(*scratch); + } + for (scratch, scratch_chunk) in mips_wit_env + .scratch_state_inverse + .iter() + .zip(curr_proof_inputs.evaluations.scratch_inverse.iter_mut()) + { + scratch_chunk.push(*scratch); + } + curr_proof_inputs + .evaluations + .instruction_counter + .push(Fp::from(mips_wit_env.instruction_counter)); + // FIXME: Might be another value + curr_proof_inputs.evaluations.error.push(Fp::rand(&mut rng)); + + curr_proof_inputs + .evaluations + .selector + .push(Fp::from((mips_wit_env.selector - N_MIPS_REL_COLS) as u64)); + + if curr_proof_inputs.evaluations.instruction_counter.len() == DOMAIN_SIZE { + debug!("Limit of {DOMAIN_SIZE} reached. We make a proof, verify it (for testing) and start with a new chunk"); + prove_and_verify(srs, curr_proof_inputs, &constraints, &mut rng); + curr_proof_inputs = ProofInputs::new(DOMAIN_SIZE); + } + } + if curr_proof_inputs.evaluations.instruction_counter.len() < DOMAIN_SIZE { + debug!("Reached halting condition, proving remaining execution"); + curr_proof_inputs.pad(); + prove_and_verify(srs, curr_proof_inputs, &constraints, &mut rng); + } +} + #[cfg(test)] mod tests; diff --git a/o1vm/src/pickles/proof.rs b/o1vm/src/pickles/proof.rs index 40529a3409..bc5a4a70fb 100644 --- a/o1vm/src/pickles/proof.rs +++ b/o1vm/src/pickles/proof.rs @@ -1,8 +1,9 @@ +use crate::interpreters::mips::column::{N_MIPS_SEL_COLS, SCRATCH_SIZE, SCRATCH_SIZE_INVERSE}; +use ark_ff::Zero; use kimchi::{curve::KimchiCurve, proof::PointEvaluations}; +use log::debug; use poly_commitment::{ipa::OpeningProof, PolyComm}; -use crate::interpreters::mips::column::{N_MIPS_SEL_COLS, SCRATCH_SIZE, SCRATCH_SIZE_INVERSE}; - pub struct WitnessColumns { pub scratch: [G; SCRATCH_SIZE], pub scratch_inverse: [G; SCRATCH_SIZE_INVERSE], @@ -27,6 +28,30 @@ impl ProofInputs { }, } } + + pub fn pad(&mut self) { + let zero = G::ScalarField::zero(); + let n = self.evaluations.instruction_counter.capacity(); + debug!( + "Padding proof inputs with zeros, current length {}", + self.evaluations.instruction_counter.len() + ); + self.evaluations + .scratch + .iter_mut() + .for_each(|s| s.resize(n, zero)); + self.evaluations + .scratch_inverse + .iter_mut() + .for_each(|s| s.resize(n, zero)); + self.evaluations.instruction_counter.resize(n, zero); + self.evaluations.error.resize(n, zero); + self.evaluations.selector.resize(n, zero); + debug!( + "Padded proof inputs, now has length {}", + self.evaluations.instruction_counter.len() + ); + } } // FIXME: should we blind the commitment? diff --git a/o1vm/tests/test_mips_elf.rs b/o1vm/tests/test_mips_elf.rs new file mode 100644 index 0000000000..f8ad6b3231 --- /dev/null +++ b/o1vm/tests/test_mips_elf.rs @@ -0,0 +1,84 @@ +use ark_ff::Field; +use mina_curves::pasta::{Fp, Vesta}; +use o1vm::{ + cannon::{self, State, VmConfiguration}, + elf_loader::Architecture, + interpreters::mips::witness::{self}, + pickles::{cannon_main, DOMAIN_FP, DOMAIN_SIZE}, + preimage_oracle::{NullPreImageOracle, PreImageOracleT}, +}; +use once_cell::sync::Lazy; +use poly_commitment::{ipa::SRS, SRS as _}; + +static SRS: Lazy> = Lazy::new(|| { + let srs = SRS::create(DOMAIN_SIZE); + srs.get_lagrange_basis(DOMAIN_FP.d1); + srs +}); + +fn parse_state(fp: String) -> State { + let curr_dir = std::env::current_dir().unwrap(); + let path = curr_dir.join(std::path::PathBuf::from(fp)); + o1vm::elf_loader::parse_elf(Architecture::Mips, &path).unwrap() +} + +fn read_word(env: &mut witness::Env, addr: u32) -> u32 { + let bytes: [u8; 4] = [ + env.get_memory_direct(addr), + env.get_memory_direct(addr + 1), + env.get_memory_direct(addr + 2), + env.get_memory_direct(addr + 3), + ]; + u32::from_be_bytes(bytes) +} + +#[test] +fn test_nor_execution() { + let state = parse_state(String::from("resources/programs/mips/bin/nor")); + let start = cannon::Start::create(state.step as usize); + let configuration = VmConfiguration { + halt_address: Some(0), + ..Default::default() + }; + let mut witness = witness::Env::>::create( + cannon::PAGE_SIZE as usize, + state, + Box::new(NullPreImageOracle), + ); + while !witness.halt { + witness.step(&configuration, &None, &start); + } + let return_register = 0xbffffff0_u32; + + let done_register = return_register + 4; + assert_eq!( + read_word(&mut witness, return_register + 4), + 1, + "Expected done register to be set to 1, got {}", + done_register + ); + + let result_register = return_register + 8; + assert_eq!( + read_word(&mut witness, result_register), + 1, + "Expected result register to be 1, got {}", + result_register + ); +} + +#[test] +fn test_nor_proving() { + let state = parse_state(String::from("resources/programs/mips/bin/nor")); + let start = cannon::Start::create(state.step as usize); + let configuration = VmConfiguration { + halt_address: Some(0), + ..Default::default() + }; + let witness = witness::Env::>::create( + cannon::PAGE_SIZE as usize, + state, + Box::new(NullPreImageOracle), + ); + cannon_main(configuration, witness, &SRS, start, &None); +}