risc0_zkp/
adapter.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Interface between the circuit and prover/verifier
16
17use alloc::{str::from_utf8, vec::Vec};
18use core::fmt;
19
20use anyhow::Result;
21use risc0_core::field::{Elem, ExtElem, Field};
22use serde::{Deserialize, Serialize};
23
24use crate::{hal::cpu::SyncSlice, taps::TapSet};
25
26// TODO: Remove references to these constants so we don't depend on a
27// fixed set of register groups.
28pub const REGISTER_GROUP_ACCUM: usize = 0;
29pub const REGISTER_GROUP_CODE: usize = 1;
30pub const REGISTER_GROUP_DATA: usize = 2;
31
32// If true, enable tracing of adapter internals.
33const ADAPTER_TRACE_ENABLED: bool = true;
34
35macro_rules! trace_if_enabled {
36    ($($args:tt)*) => {
37        if ADAPTER_TRACE_ENABLED {
38            tracing::trace!($($args)*)
39        }
40    }
41}
42
43#[derive(Clone, Copy, Debug)]
44pub struct MixState<EE: ExtElem> {
45    pub tot: EE,
46    pub mul: EE,
47}
48
49pub trait CircuitStepHandler<E: Elem> {
50    fn call(
51        &mut self,
52        cycle: usize,
53        name: &str,
54        extra: &str,
55        args: &[E],
56        outs: &mut [E],
57    ) -> Result<()>;
58
59    fn sort(&mut self, name: &str);
60}
61
62pub struct CircuitStepContext {
63    pub size: usize,
64    pub cycle: usize,
65}
66
67pub trait CircuitStep<E: Elem> {
68    fn step_exec<S: CircuitStepHandler<E>>(
69        &self,
70        ctx: &CircuitStepContext,
71        custom: &mut S,
72        args: &[SyncSlice<E>],
73    ) -> Result<E>;
74
75    fn step_verify_bytes<S: CircuitStepHandler<E>>(
76        &self,
77        ctx: &CircuitStepContext,
78        custom: &mut S,
79        args: &[SyncSlice<E>],
80    ) -> Result<E>;
81
82    fn step_verify_mem<S: CircuitStepHandler<E>>(
83        &self,
84        ctx: &CircuitStepContext,
85        custom: &mut S,
86        args: &[SyncSlice<E>],
87    ) -> Result<E>;
88
89    fn step_compute_accum<S: CircuitStepHandler<E>>(
90        &self,
91        ctx: &CircuitStepContext,
92        custom: &mut S,
93        args: &[SyncSlice<E>],
94    ) -> Result<E>;
95
96    fn step_verify_accum<S: CircuitStepHandler<E>>(
97        &self,
98        ctx: &CircuitStepContext,
99        custom: &mut S,
100        args: &[SyncSlice<E>],
101    ) -> Result<E>;
102}
103
104pub trait PolyFp<F: Field> {
105    fn poly_fp(
106        &self,
107        cycle: usize,
108        steps: usize,
109        mix: &[F::ExtElem],
110        args: &[&[F::Elem]],
111    ) -> F::ExtElem;
112}
113
114pub trait PolyExt<F: Field> {
115    fn poly_ext(
116        &self,
117        mix: &F::ExtElem,
118        u: &[F::ExtElem],
119        args: &[&[F::Elem]],
120    ) -> MixState<F::ExtElem>;
121}
122
123pub trait TapsProvider {
124    fn get_taps(&self) -> &'static TapSet<'static>;
125
126    fn accum_size(&self) -> usize {
127        self.get_taps().group_size(REGISTER_GROUP_ACCUM)
128    }
129
130    fn code_size(&self) -> usize {
131        self.get_taps().group_size(REGISTER_GROUP_CODE)
132    }
133
134    fn ctrl_size(&self) -> usize {
135        self.get_taps().group_size(REGISTER_GROUP_CODE)
136    }
137
138    fn data_size(&self) -> usize {
139        self.get_taps().group_size(REGISTER_GROUP_DATA)
140    }
141}
142
143/// A protocol info string for the proof system and circuits.
144/// Used to seed the Fiat-Shamir transcript and provide domain separation between different
145/// protocol and circuit versions.
146#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
147pub struct ProtocolInfo(pub [u8; 16]);
148
149impl ProtocolInfo {
150    /// Encode a fixed context byte-string to elements, with one element per byte.
151    // NOTE: This function is intended to be compatible with const, but is not const currently because
152    // E::from_u64 is not const, as const functions on traits is not stable.
153    pub fn encode<E: Elem>(&self) -> [E; 16] {
154        let mut elems = [E::ZERO; 16];
155        for (i, elem) in elems.iter_mut().enumerate().take(self.0.len()) {
156            *elem = E::from_u64(self.0[i] as u64);
157        }
158        elems
159    }
160}
161
162impl fmt::Display for ProtocolInfo {
163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164        match from_utf8(&self.0) {
165            Ok(s) => write!(f, "{}", s),
166            Err(_) => write!(f, "0x{}", hex::encode(self.0)),
167        }
168    }
169}
170
171/// Versioned info string for the proof system.
172///
173/// NOTE: This string should be bumped with every change to the proof system, as defined by a
174/// change to checks applied by the verifier.
175pub const PROOF_SYSTEM_INFO: ProtocolInfo = ProtocolInfo(*b"RISC0_STARK:v1__");
176
177pub trait CircuitInfo {
178    const CIRCUIT_INFO: ProtocolInfo;
179    const OUTPUT_SIZE: usize;
180    const MIX_SIZE: usize;
181}
182
183/// traits implemented by generated rust code used in both prover and verifier
184pub trait CircuitCoreDef<F: Field>: CircuitInfo + PolyExt<F> + TapsProvider {}
185
186/// traits implemented by generated rust code used in only the prover
187pub trait CircuitProveDef<F: Field>:
188    CircuitStep<F::Elem> + PolyFp<F> + CircuitCoreDef<F> + Sync
189{
190}
191
192pub type Arg = usize;
193pub type Var = usize;
194
195pub struct PolyExtStepDef {
196    pub block: &'static [PolyExtStep],
197    pub ret: Var,
198}
199
200#[derive(Debug)]
201pub enum PolyExtStep {
202    Const(u32),
203    ConstExt(u32, u32, u32, u32),
204    Get(usize),
205    GetGlobal(Arg, usize),
206    Add(Var, Var),
207    Sub(Var, Var),
208    Mul(Var, Var),
209    True,
210    AndEqz(Var, Var),
211    AndCond(Var, Var, Var),
212}
213
214impl PolyExtStepDef {
215    pub fn step<F: Field>(
216        &self,
217        mix: &F::ExtElem,
218        u: &[F::ExtElem],
219        args: &[&[F::Elem]],
220    ) -> MixState<F::ExtElem> {
221        PolyExtExecutor::<F>::new(self).run(mix, u, args)
222    }
223}
224
225struct PolyExtExecutor<'a, F: Field> {
226    def: &'a PolyExtStepDef,
227    fp_expected: usize,
228    mix_expected: usize,
229    fp_vars: Vec<F::ExtElem>,
230    #[cfg(feature = "circuit_debug")]
231    fp_index: Vec<usize>,
232    mix_vars: Vec<MixState<F::ExtElem>>,
233    #[cfg(feature = "circuit_debug")]
234    mix_index: Vec<usize>,
235}
236
237impl<'a, F: Field> PolyExtExecutor<'a, F> {
238    pub fn new(def: &'a PolyExtStepDef) -> Self {
239        let fp_expected = def.block.len() - (def.ret + 1);
240        let mix_expected = def.ret + 1;
241        Self {
242            def,
243            fp_expected,
244            mix_expected,
245            fp_vars: Vec::with_capacity(fp_expected),
246            #[cfg(feature = "circuit_debug")]
247            fp_index: Vec::with_capacity(fp_expected),
248            mix_vars: Vec::with_capacity(mix_expected),
249            #[cfg(feature = "circuit_debug")]
250            mix_index: Vec::with_capacity(mix_expected),
251        }
252    }
253
254    pub fn run(
255        &mut self,
256        mix: &F::ExtElem,
257        u: &[F::ExtElem],
258        args: &[&[F::Elem]],
259    ) -> MixState<F::ExtElem> {
260        for (idx, op) in self.def.block.iter().enumerate() {
261            self.step(idx, op, mix, u, args);
262        }
263        assert_eq!(
264            self.fp_vars.len(),
265            self.fp_expected,
266            "Miscalculated capacity for fp_vars"
267        );
268        assert_eq!(
269            self.mix_vars.len(),
270            self.mix_expected,
271            "Miscalculated capacity for mix_vars"
272        );
273
274        #[cfg(feature = "circuit_debug")]
275        self.debug(self.def.ret);
276
277        self.mix_vars[self.def.ret]
278    }
279
280    #[cfg(feature = "circuit_debug")]
281    fn debug(&mut self, next: Var) {
282        let op_index = self.mix_index[next];
283        let op = &self.def.block[op_index];
284        tracing::debug!("chain: [m:{next}] {op:?}");
285        match op {
286            PolyExtStep::True => {
287                tracing::debug!("PolyExtStep::True");
288            }
289            PolyExtStep::AndEqz(chain, inner) => {
290                let inner_val = self.fp_vars[*inner];
291                // inner should be zero
292                if inner_val != F::ExtElem::ZERO {
293                    // this is the first expression that is broken
294                    tracing::debug!("expr: {}", self.debug_expr(*inner));
295                    let inner_idx = self.fp_index[*inner];
296                    let op = &self.def.block[inner_idx];
297                    panic!("eqz failure: [f:{}] {op:?}", *inner);
298                }
299                self.debug(*chain);
300            }
301            PolyExtStep::AndCond(chain, cond, inner) => {
302                let cond = self.fp_vars[*cond];
303                if cond != F::ExtElem::ZERO {
304                    tracing::debug!("true conditional");
305                    // conditional is true
306                    let inner_val = self.fp_vars[*inner];
307                    // inner should be zero
308                    if inner_val != F::ExtElem::ZERO {
309                        tracing::debug!("inner != 0");
310                        // follow inner to find out where it went bad
311                        self.debug(*inner);
312                    } else {
313                        // follow chain
314                        self.debug(*chain)
315                    }
316                } else {
317                    // conditional is false, follow chain
318                    self.debug(*chain)
319                }
320            }
321            _ => unreachable!(),
322        }
323    }
324
325    #[cfg(feature = "circuit_debug")]
326    fn debug_expr(&self, next: Var) -> String {
327        let op_index = self.fp_index[next];
328        let op = &self.def.block[op_index];
329        match op {
330            PolyExtStep::Const(x) => format!("{x:?}"),
331            PolyExtStep::ConstExt(x0, x1, x2, x3) => format!("({x0:?}, {x1:?}, {x2:?}, {x3:?})"),
332            PolyExtStep::Get(x) => format!("Get({x})"),
333            PolyExtStep::GetGlobal(arg, x) => format!("GetGlobal({arg}, {x})"),
334            PolyExtStep::Add(x, y) => {
335                format!("({} + {})", self.debug_expr(*x), self.debug_expr(*y))
336            }
337            PolyExtStep::Sub(x, y) => {
338                format!("({} - {})", self.debug_expr(*x), self.debug_expr(*y))
339            }
340            PolyExtStep::Mul(x, y) => {
341                format!("({} * {})", self.debug_expr(*x), self.debug_expr(*y))
342            }
343            _ => String::new(),
344        }
345    }
346
347    fn fp_index(&self) -> usize {
348        self.fp_vars.len()
349    }
350
351    fn mix_index(&self) -> usize {
352        self.mix_vars.len()
353    }
354
355    fn push_fp(&mut self, _idx: usize, val: F::ExtElem) {
356        #[cfg(feature = "circuit_debug")]
357        self.fp_index.push(_idx);
358        self.fp_vars.push(val);
359    }
360
361    fn push_mix(&mut self, _idx: usize, mix: MixState<F::ExtElem>) {
362        #[cfg(feature = "circuit_debug")]
363        self.mix_index.push(_idx);
364        self.mix_vars.push(mix);
365    }
366
367    fn step(
368        &mut self,
369        idx: usize,
370        op: &PolyExtStep,
371        mix: &F::ExtElem,
372        u: &[F::ExtElem],
373        args: &[&[F::Elem]],
374    ) {
375        match op {
376            PolyExtStep::Const(value) => {
377                let val = F::Elem::from_u64(*value as u64);
378                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
379                self.push_fp(idx, val.into());
380            }
381            PolyExtStep::ConstExt(x0, x1, x2, x3) => {
382                let val = F::ExtElem::from_subelems([
383                    F::Elem::from_u64(*x0 as u64),
384                    F::Elem::from_u64(*x1 as u64),
385                    F::Elem::from_u64(*x2 as u64),
386                    F::Elem::from_u64(*x3 as u64),
387                ]);
388                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
389                self.push_fp(idx, val);
390            }
391            PolyExtStep::Get(tap) => {
392                let val = u[*tap];
393                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
394                self.push_fp(idx, val);
395            }
396            PolyExtStep::GetGlobal(base, offset) => {
397                let val = F::ExtElem::from_subfield(&args[*base][*offset]);
398                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
399                self.push_fp(idx, val);
400            }
401            PolyExtStep::Add(x1, x2) => {
402                let val = self.fp_vars[*x1] + self.fp_vars[*x2];
403                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
404                self.push_fp(idx, val);
405            }
406            PolyExtStep::Sub(x1, x2) => {
407                let val = self.fp_vars[*x1] - self.fp_vars[*x2];
408                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
409                self.push_fp(idx, val);
410            }
411            PolyExtStep::Mul(x1, x2) => {
412                let val = self.fp_vars[*x1] * self.fp_vars[*x2];
413                trace_if_enabled!("[f:{}] {op:?} -> {val:?}", self.fp_index());
414                self.push_fp(idx, val);
415            }
416            PolyExtStep::True => {
417                let mix_val = MixState {
418                    tot: F::ExtElem::ZERO,
419                    mul: F::ExtElem::ONE,
420                };
421                trace_if_enabled!("[m:{}] {op:?}", self.mix_index());
422                self.push_mix(idx, mix_val);
423            }
424            PolyExtStep::AndEqz(chain, inner) => {
425                let chain = self.mix_vars[*chain];
426                let inner = self.fp_vars[*inner];
427                let mix_val = MixState {
428                    tot: chain.tot + chain.mul * inner,
429                    mul: chain.mul * *mix,
430                };
431                trace_if_enabled!("[m:{}] {op:?}, inner: {inner:?}", self.mix_index());
432                self.push_mix(idx, mix_val);
433            }
434            PolyExtStep::AndCond(chain, cond, inner) => {
435                let chain = self.mix_vars[*chain];
436                let cond = self.fp_vars[*cond];
437                let inner = self.mix_vars[*inner];
438                let mix_val = MixState {
439                    tot: chain.tot + cond * inner.tot * chain.mul,
440                    mul: chain.mul * inner.mul,
441                };
442                trace_if_enabled!(
443                    "[m:{}] {op:?}, cond: {}, inner: {}",
444                    self.mix_index(),
445                    cond != F::ExtElem::ZERO,
446                    inner.tot != F::ExtElem::ZERO,
447                );
448                self.push_mix(idx, mix_val);
449            }
450        }
451    }
452}