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