risc0_circuit_recursion/prove/
program.rs

1// Copyright 2024 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
15use risc0_zkp::{
16    core::{digest::Digest, hash::HashSuite},
17    field::baby_bear::{BabyBear, BabyBearElem},
18    hal::{cpu::CpuHal, Hal},
19    prove::poly_group::PolyGroup,
20    ZK_CYCLES,
21};
22
23use super::RECURSION_CODE_SIZE;
24
25/// A Program for the recursion circuit (e.g. lift_20 or join).
26///
27/// The recursion circuit is an application specific virtual machine with a limited instruction
28/// set, no control flow operations, and a write-once memory tape. Although it is not general
29/// purpose, it can load and execute a program, similar to the rv32im zkVM.
30///
31/// Programs for the recursion circuit are loaded into the control columns, which is a set of
32/// public columns in the witness. Programs are therefore identified by their control ID, which is
33/// similar but not the same as the image ID used to identify rv32im programs.
34#[derive(Clone)]
35pub struct Program {
36    /// The code of the program, encoded as Baby Bear field elements.
37    pub code: Vec<BabyBearElem>,
38
39    /// The number of code columns.
40    pub code_size: usize,
41
42    /// 1 << po2 is the number of cycles executed.
43    pub po2: usize,
44}
45
46impl Program {
47    /// Create a [Program] from a stream of data encoded by Zirgen.
48    pub fn from_encoded(encoded: &[u32], po2: usize) -> Self {
49        let prog = Self {
50            code: encoded.iter().copied().map(BabyBearElem::from).collect(),
51            code_size: RECURSION_CODE_SIZE,
52            po2,
53        };
54        assert_eq!(prog.code.len() % RECURSION_CODE_SIZE, 0);
55        assert!(prog.code.len() <= (RECURSION_CODE_SIZE * (1 << po2) - ZK_CYCLES));
56        prog
57    }
58
59    /// Total number of rows in the code group for this program.
60    pub fn code_rows(&self) -> usize {
61        self.code.len() / self.code_size
62    }
63
64    /// An iterator over the rows of the code group.
65    pub fn code_by_row(&self) -> impl Iterator<Item = &[BabyBearElem]> {
66        self.code.as_slice().chunks(self.code_size)
67    }
68
69    /// Given a [Program] for the recursion circuit, compute the control ID as the FRI Merkle root
70    /// of the code group. This uniquely identifies the program running on the recursion circuit
71    /// (e.g. lift_20 or join)
72    pub fn compute_control_id(&self, hash_suite: HashSuite<BabyBear>) -> Digest {
73        let hal = CpuHal::new(hash_suite);
74        let cycles = 1 << self.po2;
75
76        let mut code = vec![BabyBearElem::default(); cycles * self.code_size];
77
78        for (cycle, row) in self.code_by_row().enumerate() {
79            for (i, elem) in row.iter().enumerate() {
80                code[cycles * i + cycle] = *elem;
81            }
82        }
83        let coeffs = hal.copy_from_elem("coeffs", &code);
84        // Do interpolate & shift
85        hal.batch_interpolate_ntt(&coeffs, self.code_size);
86        hal.zk_shift(&coeffs, self.code_size);
87        // Make the poly-group & extract the root
88        let code_group = PolyGroup::new(&hal, coeffs, self.code_size, cycles, "code");
89        let root = *code_group.merkle.root();
90        tracing::trace!("Computed recursion code: {root:?}");
91        root
92    }
93}