1use ark_ff::{UniformRand, Zero};
2use clap::Parser;
3use kimchi::circuits::domains::EvaluationDomains;
4use log::debug;
5use mina_curves::pasta::{Fp, Vesta, VestaParameters};
6use mina_poseidon::{
7 constants::PlonkSpongeConstantsKimchi,
8 sponge::{DefaultFqSponge, DefaultFrSponge},
9};
10use o1vm::{
11 cannon::{self, Start, State},
12 cli, elf_loader,
13 interpreters::mips::{
14 column::N_MIPS_REL_COLS,
15 constraints as mips_constraints,
16 witness::{self as mips_witness},
17 Instruction,
18 },
19 pickles::{proof::ProofInputs, prover, verifier},
20 preimage_oracle::{NullPreImageOracle, PreImageOracle, PreImageOracleT},
21 test_preimage_read, E,
22};
23use poly_commitment::{ipa::SRS, precomputed_srs::TestSRS, SRS as _};
24use rand::rngs::ThreadRng;
25use std::{fs::File, io::BufReader, path::Path, process::ExitCode, time::Instant};
26
27pub fn cannon_main(args: cli::cannon::RunArgs) {
28 let mut rng = rand::thread_rng();
29
30 let configuration: cannon::VmConfiguration = args.vm_cfg.into();
31
32 let file =
33 File::open(&configuration.input_state_file).expect("Error opening input state file ");
34
35 let reader = BufReader::new(file);
36 let state: State = serde_json::from_reader(reader).expect("Error reading input state file");
38
39 let meta = &configuration.metadata_file.as_ref().map(|f| {
40 let meta_file =
41 File::open(f).unwrap_or_else(|_| panic!("Could not open metadata file {}", f));
42 serde_json::from_reader(BufReader::new(meta_file))
43 .unwrap_or_else(|_| panic!("Error deserializing metadata file {}", f))
44 });
45
46 let start = Start::create(state.step as usize);
48
49 let (srs, domain_fp) = match &args.srs_cache {
50 Some(cache) => {
51 debug!("Loading SRS from cache {}", cache);
52 let file_path = Path::new(cache);
53 let file = File::open(file_path).expect("Error opening SRS cache file");
54 let srs: SRS<Vesta> = {
55 if file_path
58 .file_name()
59 .unwrap()
60 .to_str()
61 .unwrap()
62 .starts_with("test_")
63 {
64 let test_srs: TestSRS<Vesta> = rmp_serde::from_read(&file).unwrap();
65 From::from(test_srs)
66 } else {
67 rmp_serde::from_read(&file).unwrap()
68 }
69 };
70 debug!("SRS loaded successfully from cache");
71 let domain_fp = EvaluationDomains::<Fp>::create(srs.size()).unwrap();
72 (srs, domain_fp)
73 }
74 None => {
75 debug!("No SRS cache provided. Creating SRS from scratch with domain size 2^16");
76 let domain_size = 1 << 16;
77 let srs = SRS::create(domain_size);
78 let domain_fp = EvaluationDomains::<Fp>::create(srs.size()).unwrap();
79 srs.get_lagrange_basis(domain_fp.d1);
80 debug!("SRS created successfully");
81 (srs, domain_fp)
82 }
83 };
84
85 let mut mips_wit_env = match configuration.host.clone() {
87 Some(host) => {
88 let mut po = PreImageOracle::create(host);
89 let _child = po.start();
90 mips_witness::Env::<Fp, Box<dyn PreImageOracleT>>::create(
91 cannon::PAGE_SIZE as usize,
92 state,
93 Box::new(po),
94 )
95 }
96 None => {
97 debug!("No preimage oracle provided 🤞");
98 mips_witness::Env::<Fp, Box<dyn PreImageOracleT>>::create(
100 cannon::PAGE_SIZE as usize,
101 state,
102 Box::new(NullPreImageOracle),
103 )
104 }
105 };
106
107 let constraints = mips_constraints::get_all_constraints::<Fp>();
108 let domain_size = domain_fp.d1.size as usize;
109
110 let mut curr_proof_inputs: ProofInputs<Vesta> = ProofInputs::new(domain_size);
111 while !mips_wit_env.halt {
112 let _instr: Instruction = mips_wit_env.step(&configuration, meta, &start);
113 for (scratch, scratch_chunk) in mips_wit_env
114 .scratch_state
115 .iter()
116 .zip(curr_proof_inputs.evaluations.scratch.iter_mut())
117 {
118 scratch_chunk.push(*scratch);
119 }
120 for (scratch, scratch_chunk) in mips_wit_env
121 .scratch_state_inverse
122 .iter()
123 .zip(curr_proof_inputs.evaluations.scratch_inverse.iter_mut())
124 {
125 scratch_chunk.push(*scratch);
126 }
127 {
129 let proof_inputs_length = curr_proof_inputs.evaluations.lookup_state.len();
130 let environment_length = mips_wit_env.lookup_state.len();
131 let lookup_state_size = std::cmp::max(proof_inputs_length, environment_length);
132 for idx in 0..lookup_state_size {
133 if idx >= environment_length {
134 curr_proof_inputs.evaluations.lookup_state[idx].push(Fp::zero());
136 } else if idx >= proof_inputs_length {
137 let mut new_vec =
139 vec![Fp::zero(); curr_proof_inputs.evaluations.instruction_counter.len()];
140 new_vec.push(Fp::from(mips_wit_env.lookup_state[idx]));
141 curr_proof_inputs.evaluations.lookup_state.push(new_vec);
142 } else {
143 curr_proof_inputs.evaluations.lookup_state[idx]
145 .push(Fp::from(mips_wit_env.lookup_state[idx]));
146 }
147 }
148 }
149 curr_proof_inputs
150 .evaluations
151 .instruction_counter
152 .push(Fp::from(mips_wit_env.instruction_counter));
153 curr_proof_inputs.evaluations.error.push(Fp::rand(&mut rng));
155
156 curr_proof_inputs
157 .evaluations
158 .selector
159 .push(Fp::from((mips_wit_env.selector - N_MIPS_REL_COLS) as u64));
160
161 if curr_proof_inputs.evaluations.instruction_counter.len() == domain_size {
162 prove_and_verify(domain_fp, &srs, &constraints, curr_proof_inputs, &mut rng);
163
164 curr_proof_inputs = ProofInputs::new(domain_size);
165 }
166 }
167
168 if curr_proof_inputs.evaluations.instruction_counter.len() < domain_size {
169 debug!("Padding witness for proof generation");
170 pad(&mips_wit_env, &mut curr_proof_inputs, &mut rng);
171 prove_and_verify(domain_fp, &srs, &constraints, curr_proof_inputs, &mut rng);
172 }
173}
174
175fn prove_and_verify(
176 domain_fp: EvaluationDomains<Fp>,
177 srs: &SRS<Vesta>,
178 constraints: &[E<Fp>],
179 curr_proof_inputs: ProofInputs<Vesta>,
180 rng: &mut ThreadRng,
181) {
182 let start_iteration = Instant::now();
183 let proof = prover::prove::<
184 Vesta,
185 DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>,
186 DefaultFrSponge<Fp, PlonkSpongeConstantsKimchi>,
187 _,
188 >(domain_fp, srs, curr_proof_inputs, constraints, rng)
189 .unwrap();
190 debug!(
191 "Proof generated in {elapsed} μs",
192 elapsed = start_iteration.elapsed().as_micros()
193 );
194 let start_iteration = Instant::now();
195 let verif = verifier::verify::<
196 Vesta,
197 DefaultFqSponge<VestaParameters, PlonkSpongeConstantsKimchi>,
198 DefaultFrSponge<Fp, PlonkSpongeConstantsKimchi>,
199 >(domain_fp, srs, constraints, &proof);
200 debug!(
201 "Verification done in {elapsed} μs",
202 elapsed = start_iteration.elapsed().as_micros()
203 );
204 assert!(verif);
205}
206
207fn pad(
208 witness_env: &mips_witness::Env<Fp, Box<dyn PreImageOracleT>>,
209 curr_proof_inputs: &mut ProofInputs<Vesta>,
210 rng: &mut ThreadRng,
211) {
212 let zero = Fp::zero();
213 let noop_selector: Fp = {
216 let noop: usize = Instruction::NoOp.into();
217 Fp::from((noop - N_MIPS_REL_COLS) as u64)
218 };
219 curr_proof_inputs
220 .evaluations
221 .scratch
222 .iter_mut()
223 .for_each(|x| x.resize(x.capacity(), zero));
224 curr_proof_inputs
225 .evaluations
226 .scratch_inverse
227 .iter_mut()
228 .for_each(|x| x.resize(x.capacity(), zero));
229 curr_proof_inputs.evaluations.instruction_counter.resize(
230 curr_proof_inputs.evaluations.instruction_counter.capacity(),
231 Fp::from(witness_env.instruction_counter),
232 );
233 curr_proof_inputs
234 .evaluations
235 .error
236 .resize_with(curr_proof_inputs.evaluations.error.capacity(), || {
237 Fp::rand(rng)
238 });
239 curr_proof_inputs.evaluations.selector.resize(
240 curr_proof_inputs.evaluations.selector.capacity(),
241 noop_selector,
242 );
243}
244
245fn gen_state_json(arg: cli::cannon::GenStateJsonArgs) -> Result<(), String> {
246 let path = Path::new(&arg.input);
247 let state = elf_loader::parse_elf(elf_loader::Architecture::Mips, path)?;
248 let file = File::create(&arg.output).expect("Error creating output state file");
249 serde_json::to_writer_pretty(file, &state).expect("Error writing output state file");
250 Ok(())
251}
252
253pub fn main() -> ExitCode {
254 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
255 let args = cli::Commands::parse();
256 match args {
257 cli::Commands::Cannon(args) => match args {
258 cli::cannon::Cannon::Run(args) => {
259 cannon_main(args);
260 }
261 cli::cannon::Cannon::TestPreimageRead(args) => {
262 test_preimage_read::main(args);
263 }
264 cli::cannon::Cannon::GenStateJson(args) => {
265 gen_state_json(args).expect("Error generating state.json");
266 }
267 },
268 }
269 ExitCode::SUCCESS
270}