1mod fri;
18mod merkle;
19mod read_iop;
20
21use alloc::{vec, vec::Vec};
22use core::{
23 cell::{RefCell, RefMut},
24 fmt,
25 iter::zip,
26 ops::DerefMut,
27};
28
29pub(crate) use merkle::MerkleTreeVerifier;
30pub use read_iop::ReadIOP;
31use risc0_core::field::{Elem, ExtElem, Field, RootsOfUnity};
32
33use crate::{
34 adapter::{
35 CircuitCoreDef, ProtocolInfo, PROOF_SYSTEM_INFO, REGISTER_GROUP_ACCUM, REGISTER_GROUP_CODE,
36 REGISTER_GROUP_DATA,
37 },
38 core::{digest::Digest, hash::HashSuite, log2_ceil},
39 taps::TapSet,
40 INV_RATE, MAX_CYCLES_PO2, QUERIES,
41};
42
43const VERIFY_TRACE_ENABLED: bool = false;
45
46macro_rules! trace_if_enabled {
47 ($($args:tt)*) => {
48 if VERIFY_TRACE_ENABLED {
49 #[cfg(not(target_os = "zkvm"))]
50 tracing::debug!($($args)*);
51 }
52 }
53}
54
55#[derive(PartialEq)]
56#[non_exhaustive]
57pub enum VerificationError {
58 ReceiptFormatError,
59 ControlVerificationError {
60 control_id: Digest,
61 },
62 ImageVerificationError,
63 MerkleQueryOutOfRange {
64 idx: usize,
65 rows: usize,
66 },
67 InvalidProof,
68 JournalDigestMismatch,
69 ClaimDigestMismatch {
70 expected: Digest,
71 received: Digest,
72 },
73 UnexpectedExitCode,
74 InvalidHashSuite,
75 VerifierParametersMissing,
76 VerifierParametersMismatch {
77 expected: Digest,
78 received: Digest,
79 },
80 ProofSystemInfoMismatch {
81 expected: ProtocolInfo,
82 received: ProtocolInfo,
83 },
84 CircuitInfoMismatch {
85 expected: ProtocolInfo,
86 received: ProtocolInfo,
87 },
88 UnresolvedAssumption {
89 digest: Digest,
90 },
91}
92
93impl fmt::Debug for VerificationError {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 fmt::Display::fmt(&self, f)
96 }
97}
98
99impl fmt::Display for VerificationError {
100 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101 match self {
102 VerificationError::ReceiptFormatError => write!(f, "invalid receipt format"),
103 VerificationError::ControlVerificationError { control_id } => {
104 write!(f, "control_id mismatch: {control_id}")
105 }
106 VerificationError::ImageVerificationError => write!(f, "image_id mismatch"),
107 VerificationError::MerkleQueryOutOfRange { idx, rows } => write!(
108 f,
109 "requested Merkle validation on row {idx}, but only {rows} rows exist",
110 ),
111 VerificationError::InvalidProof => write!(f, "verification indicates proof is invalid"),
112 VerificationError::JournalDigestMismatch => {
113 write!(f, "journal digest mismatch detected")
114 }
115 VerificationError::ClaimDigestMismatch { expected, received } => {
116 write!(f, "claim digest does not match the expected digest {received}; expected {expected}")
117 }
118 VerificationError::UnexpectedExitCode => write!(f, "unexpected exit_code"),
119 VerificationError::InvalidHashSuite => write!(f, "invalid hash suite"),
120 VerificationError::VerifierParametersMissing => {
121 write!(f, "verifier parameters were not found in verifier context for the given receipt type")
122 }
123 VerificationError::VerifierParametersMismatch { expected, received } => {
124 write!(f, "receipt was produced for a version of the verifier with parameters digest {received}; expected {expected}")
125 }
126 VerificationError::ProofSystemInfoMismatch { expected, received } => {
127 write!(f, "receipt was produced for a version of the verifier with proof system info {received}; expected {expected}")
128 }
129 VerificationError::CircuitInfoMismatch { expected, received } => {
130 write!(f, "receipt was produced for a version of the verifier with circuit info {received}; expected {expected}")
131 }
132 VerificationError::UnresolvedAssumption { digest } => {
133 write!(f, "receipt contains an unresolved assumption: {digest}")
134 }
135 }
136 }
137}
138
139#[cfg(feature = "std")]
140impl std::error::Error for VerificationError {}
141
142trait VerifyParams<F: Field> {
143 const CHECK_SIZE: usize = INV_RATE * F::ExtElem::EXT_SIZE;
144}
145
146#[stability::unstable]
147pub struct Verifier<'a, F>
148where
149 F: Field,
150{
151 taps: &'a TapSet<'a>,
152 suite: &'a HashSuite<F>,
153 iop: RefCell<ReadIOP<'a, F>>,
154 po2: usize,
155 tot_cycles: usize,
156 merkle_verifiers: Vec<Option<MerkleTreeVerifier<'a>>>,
158}
159
160impl<F: Field> VerifyParams<F> for Verifier<'_, F> {}
161
162impl<'a, F: Field> Verifier<'a, F> {
163 #[stability::unstable]
165 pub fn new(taps: &'a TapSet<'a>, suite: &'a HashSuite<F>, seal: &'a [u32]) -> Self {
166 trace_if_enabled!("Starting verify");
167 Self {
168 taps,
169 suite,
170 po2: 0,
171 tot_cycles: 0,
172 iop: RefCell::new(ReadIOP::new(seal, suite.rng.as_ref())),
173 merkle_verifiers: core::iter::repeat_with(|| None)
175 .take(taps.num_groups())
176 .collect(),
177 }
178 }
179
180 #[stability::unstable]
181 pub fn iop(&self) -> RefMut<'_, ReadIOP<'a, F>> {
182 self.iop.borrow_mut()
183 }
184
185 #[stability::unstable]
186 pub fn commit_circuit_info(&mut self, circuit_info: &ProtocolInfo) {
187 trace_if_enabled!("Verifying protocol={PROOF_SYSTEM_INFO} circuit {circuit_info}");
188 let hashfn = self.suite.hashfn.as_ref();
191 self.iop()
192 .commit(&hashfn.hash_elem_slice(&PROOF_SYSTEM_INFO.encode()));
193 self.iop()
194 .commit(&hashfn.hash_elem_slice(&circuit_info.encode()));
195 }
196
197 #[stability::unstable]
200 pub fn verify_group(&mut self, reg_group_id: usize) -> Result<&Digest, VerificationError> {
201 assert!(
202 self.merkle_verifiers[reg_group_id].is_none(),
203 "Reg group id {reg_group_id} ({}) may only occur once",
204 self.taps.group_name(reg_group_id)
205 );
206 let group_size = self.taps.group_size(reg_group_id);
207 let domain = INV_RATE * self.tot_cycles;
208 let hashfn = self.suite.hashfn.as_ref();
209
210 let merkle =
211 MerkleTreeVerifier::new(self.iop().deref_mut(), hashfn, domain, group_size, QUERIES);
212 self.merkle_verifiers[reg_group_id] = Some(merkle);
213 let root = self.merkle_verifiers[reg_group_id].as_ref().unwrap().root();
214 trace_if_enabled!(
215 "{} (group id={reg_group_id}) root: {root:?}",
216 self.taps.group_name(reg_group_id)
217 );
218 Ok(root)
219 }
220
221 #[stability::unstable]
223 pub fn read_rng(&mut self, elems: usize) -> Vec<F::Elem> {
224 let mix = (0..elems).map(|_| self.iop().random_elem()).collect();
225 trace_if_enabled!("Got mix values from IOP: {mix:?}");
226 mix
227 }
228
229 #[allow(clippy::too_many_arguments)]
230 fn fri_eval_taps(
232 &self,
233 combo_u: &[F::ExtElem],
234 check_row: &[F::Elem],
235 back_one: F::Elem,
236 x: F::Elem,
237 z: F::ExtElem,
238 rows: &[&[F::Elem]],
239 tap_mix_pows: &[F::ExtElem],
240 check_mix_pows: &[F::ExtElem],
241 ) -> F::ExtElem {
242 let mut tot = vec![F::ExtElem::ZERO; self.taps.combos_size() + 1];
243 let combo_count = self.taps.combos_size();
244 let x = F::ExtElem::from_subfield(&x);
245
246 for (reg, cur) in zip(self.taps.regs(), tap_mix_pows.iter()) {
247 tot[reg.combo_id()] += *cur * rows[reg.group()][reg.offset()];
248 }
249 for (i, cur) in zip(0..Self::CHECK_SIZE, check_mix_pows.iter()) {
250 tot[combo_count] += *cur * check_row[i];
251 }
252 let mut ret = F::ExtElem::ZERO;
253 for i in 0..combo_count {
254 let num = tot[i]
255 - self.poly_eval(
256 &combo_u
257 [self.taps.combo_begin[i] as usize..self.taps.combo_begin[i + 1] as usize],
258 x,
259 );
260 let mut divisor = F::ExtElem::ONE;
261 for back in self.taps.get_combo(i).slice() {
262 divisor *= x - z * back_one.pow(*back as usize);
263 }
264 ret += num * divisor.inv();
265 }
266 let check_num = tot[combo_count] - combo_u[self.taps.tot_combo_backs];
267 let check_div = x - z.pow(INV_RATE);
268 ret += check_num * check_div.inv();
269 ret
270 }
271
272 #[stability::unstable]
277 pub fn verify_validity(
278 &mut self,
279 validity_fn: impl Fn(&F::ExtElem, &[F::ExtElem]) -> F::ExtElem,
280 ) -> Result<(), VerificationError> {
281 for (reg_group_id, verifier) in self.merkle_verifiers.iter().enumerate() {
283 if !verifier.is_some() {
284 panic!(
285 "Missing merkle verifier for reg group {reg_group_id} ({})",
286 self.taps.group_name(reg_group_id)
287 );
288 }
289 }
290
291 let poly_mix = self.iop().random_ext_elem();
294 trace_if_enabled!("Poly mix: {poly_mix:?}");
295
296 let hashfn = self.suite.hashfn.as_ref();
297 let domain = INV_RATE * self.tot_cycles;
298 let check_merkle = MerkleTreeVerifier::new(
299 self.iop().deref_mut(),
300 hashfn,
301 domain,
302 Self::CHECK_SIZE,
303 QUERIES,
304 );
305 trace_if_enabled!("Check merkle root: {}", check_merkle.root());
306
307 cfg_if::cfg_if! {
310 if #[cfg(feature = "circuit_debug")] {
311 let z_slice = self.iop().read_field_elem_slice(F::ExtElem::EXT_SIZE);
312 let z = F::ExtElem::from_subelems(z_slice.iter().cloned());
313 } else {
314 let z = self.iop().random_ext_elem();
315 }
316 }
317
318 trace_if_enabled!("Z = {z:?}");
319 let back_one = F::Elem::ROU_REV[self.po2];
320
321 let num_taps = self.taps.tap_size();
323 let coeff_u = self
324 .iop()
325 .read_field_elem_slice(num_taps + Self::CHECK_SIZE);
326 let hash_u = hashfn.hash_ext_elem_slice(coeff_u);
327 self.iop().commit(&hash_u);
328
329 let mut cur_pos = 0;
331 let mut eval_u = Vec::with_capacity(num_taps);
332 for reg in self.taps.regs() {
333 for i in 0..reg.size() {
334 let x = z * back_one.pow(reg.back(i));
335 let fx = self.poly_eval(&coeff_u[cur_pos..(cur_pos + reg.size())], x);
336 eval_u.push(fx);
337 }
338 cur_pos += reg.size();
339 }
340 assert_eq!(eval_u.len(), num_taps, "Miscalculated capacity for eval_us");
341
342 #[cfg(not(target_os = "zkvm"))]
345 tracing::debug!("> compute_polynomial");
346
347 let result = validity_fn(&poly_mix, &eval_u);
348 trace_if_enabled!("Computed polynomial: {result:?}");
349
350 let mut check = F::ExtElem::default();
357 let remap = [0, 2, 1, 3];
358 let fp0 = F::Elem::ZERO;
359 let fp1 = F::Elem::ONE;
360 for (i, rmi) in remap.iter().enumerate() {
361 check += coeff_u[num_taps + rmi]
362 * z.pow(i)
363 * F::ExtElem::from_subelems([fp1, fp0, fp0, fp0]);
364 check += coeff_u[num_taps + rmi + 4]
365 * z.pow(i)
366 * F::ExtElem::from_subelems([fp0, fp1, fp0, fp0]);
367 check += coeff_u[num_taps + rmi + 8]
368 * z.pow(i)
369 * F::ExtElem::from_subelems([fp0, fp0, fp1, fp0]);
370 check += coeff_u[num_taps + rmi + 12]
371 * z.pow(i)
372 * F::ExtElem::from_subelems([fp0, fp0, fp0, fp1]);
373 }
374 let three = F::Elem::from_u64(3);
375 check *= (F::ExtElem::from_subfield(&three) * z).pow(self.tot_cycles) - F::ExtElem::ONE;
376 trace_if_enabled!("Check = {check:?}");
377 if check != result {
378 tracing::debug!("check != result");
379 return Err(VerificationError::InvalidProof);
380 }
381
382 let mix = self.iop().random_ext_elem();
384 trace_if_enabled!("FRI mix = {mix:?}");
385
386 let mut combo_u: Vec<F::ExtElem> = vec![F::ExtElem::ZERO; self.taps.tot_combo_backs + 1];
394 let mut cur_mix = F::ExtElem::ONE;
395 cur_pos = 0;
396 let mut tap_mix_pows = Vec::with_capacity(self.taps.reg_count());
397 for reg in self.taps.regs() {
398 for i in 0..reg.size() {
399 combo_u[self.taps.combo_begin[reg.combo_id()] as usize + i] +=
400 cur_mix * coeff_u[cur_pos + i];
401 }
402 tap_mix_pows.push(cur_mix);
403 cur_mix *= mix;
404 cur_pos += reg.size();
405 }
406 assert_eq!(
407 tap_mix_pows.len(),
408 self.taps.reg_count(),
409 "Miscalculated capacity for tap_mix_pows"
410 );
411 trace_if_enabled!("cur_mix: {cur_mix:?}, cur_pos: {cur_pos}");
412 let mut check_mix_pows = Vec::with_capacity(Self::CHECK_SIZE);
414 for _ in 0..Self::CHECK_SIZE {
415 combo_u[self.taps.tot_combo_backs] += cur_mix * coeff_u[cur_pos];
416 cur_pos += 1;
417 check_mix_pows.push(cur_mix);
418 cur_mix *= mix;
419 }
420 assert_eq!(
421 check_mix_pows.len(),
422 Self::CHECK_SIZE,
423 "Miscalculated capacity for check_mix_pows"
424 );
425 let gen = <F::Elem as RootsOfUnity>::ROU_FWD[log2_ceil(domain)];
426 let hashfn = self.suite.hashfn.as_ref();
427 self.fri_verify(|idx| {
428 let x = gen.pow(idx);
429 let rows= self
430 .merkle_verifiers
431 .iter()
432 .map(|merkle: &Option<MerkleTreeVerifier>| -> Result<&'a [F::Elem], VerificationError> {
433 merkle.as_ref()
434 .unwrap()
435 .verify(self.iop().deref_mut(), hashfn, idx)
436 })
437 .collect::<Result<Vec<_>,_>>()?;
438 let check_row = check_merkle.verify(self.iop().deref_mut(),hashfn, idx)?;
439 let ret = self.fri_eval_taps(&combo_u, check_row, back_one, x, z, &rows, &tap_mix_pows, &check_mix_pows);
440 Ok(ret)
441 })?;
442 Ok(())
443 }
444
445 #[stability::unstable]
450 pub fn read_slice_with_po2(&mut self, size: usize) -> (&'a [F::Elem], usize) {
451 let slice = self.iop().read_field_elem_slice(size + 1);
452 self.iop().commit(&self.suite.hashfn.hash_elem_slice(slice));
453
454 let (out, &[po2_elem]) = slice.split_at(size) else {
456 unreachable!()
457 };
458 let (&[po2], &[]) = po2_elem.to_u32_words().split_at(1) else {
459 panic!("po2 elem is larger than u32");
460 };
461 self.po2 = po2 as usize;
462 assert!(self.po2 <= MAX_CYCLES_PO2);
463 self.tot_cycles = 1usize.checked_shl(po2).unwrap();
464 (out, self.po2)
465 }
466
467 fn poly_eval(&self, coeffs: &[F::ExtElem], x: F::ExtElem) -> F::ExtElem {
470 let mut mul_x = F::ExtElem::ONE;
471 let mut tot = F::ExtElem::ZERO;
472 for coeff in coeffs {
473 tot += *coeff * mul_x;
474 mul_x *= x;
475 }
476 tot
477 }
478}
479
480pub fn verify<F, C, CheckCode>(
486 circuit: &C,
487 suite: &HashSuite<F>,
488 seal: &[u32],
489 check_code: CheckCode,
490) -> Result<(), VerificationError>
491where
492 F: Field,
493 C: CircuitCoreDef<F>,
494 CheckCode: Fn(u32, &Digest) -> Result<(), VerificationError>,
495{
496 if seal.is_empty() {
497 return Err(VerificationError::ReceiptFormatError);
498 }
499
500 let mut verifier = Verifier::<F>::new(circuit.get_taps(), suite, seal);
501 verifier.commit_circuit_info(&C::CIRCUIT_INFO);
502
503 let (out, po2) = verifier.read_slice_with_po2(C::OUTPUT_SIZE);
509
510 let code_root = verifier.verify_group(REGISTER_GROUP_CODE)?;
513
514 check_code(po2 as u32, code_root)?;
517
518 verifier.verify_group(REGISTER_GROUP_DATA)?;
523
524 let mix = verifier.read_rng(C::MIX_SIZE);
526
527 verifier.verify_group(REGISTER_GROUP_ACCUM)?;
537
538 verifier
541 .verify_validity(|poly_mix, eval_u| circuit.poly_ext(poly_mix, eval_u, &[out, &mix]).tot)?;
542
543 verifier.iop().verify_complete();
545 Ok(())
546}