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