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