risc0_circuit_rv32im/
lib.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#![cfg_attr(not(feature = "std"), no_std)]
16
17#[cfg(feature = "execute")]
18pub mod execute;
19#[cfg(feature = "prove")]
20pub mod prove;
21pub mod trace;
22mod zirgen;
23
24use core::num::TryFromIntError;
25
26use anyhow::{anyhow, ensure, Result};
27use derive_more::Debug;
28use risc0_zkp::{
29    adapter::CircuitInfo as _,
30    core::{digest::Digest, hash::poseidon2::Poseidon2HashSuite},
31    layout::Tree,
32    verify::VerificationError,
33};
34use serde::{Deserialize, Serialize};
35
36use self::zirgen::circuit::{Val, LAYOUT_GLOBAL};
37
38pub use self::zirgen::CircuitImpl;
39
40pub const RV32IM_SEAL_VERSION: u32 = 1;
41
42pub const MAX_INSN_CYCLES: usize = 2000; // TODO(flaub): calculate actual value
43
44pub fn verify(seal: &[u32]) -> Result<(), VerificationError> {
45    tracing::debug!("verify");
46
47    // We don't have a `code' buffer to verify.
48    let check_code_fn = |_: u32, _: &Digest| Ok(());
49
50    if seal[0] != RV32IM_SEAL_VERSION {
51        return Err(VerificationError::ReceiptFormatError);
52    }
53
54    let seal = &seal[1..];
55
56    let hash_suite = Poseidon2HashSuite::new_suite();
57    risc0_zkp::verify::verify(&CircuitImpl, &hash_suite, seal, check_code_fn)
58}
59
60#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
61pub struct HighLowU16(pub u16, pub u16);
62
63impl From<HighLowU16> for u32 {
64    fn from(x: HighLowU16) -> Self {
65        ((x.0 as u32) << 16) | (x.1 as u32)
66    }
67}
68
69impl From<u32> for HighLowU16 {
70    fn from(x: u32) -> Self {
71        Self((x >> 16) as u16, (x & 0xffff) as u16)
72    }
73}
74
75#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq)]
76pub struct TerminateState {
77    pub a0: HighLowU16,
78    pub a1: HighLowU16,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
82pub struct Rv32imV2Claim {
83    pub pre_state: Digest,
84    pub post_state: Digest,
85    pub input: Digest,
86    pub output: Option<Digest>,
87    pub terminate_state: Option<TerminateState>,
88    pub shutdown_cycle: Option<u32>,
89}
90
91impl Rv32imV2Claim {
92    pub fn decode(seal: &[u32]) -> Result<Rv32imV2Claim> {
93        ensure!(seal[0] == RV32IM_SEAL_VERSION, "seal version mismatch");
94        let seal = &seal[1..];
95
96        let io: &[Val] = bytemuck::checked::cast_slice(&seal[..CircuitImpl::OUTPUT_SIZE]);
97        let global = Tree::new(io, LAYOUT_GLOBAL);
98
99        let pre_state = global.map(|c| c.state_in).get_digest_from_shorts()?;
100        let post_state = global.map(|c| c.state_out).get_digest_from_shorts()?;
101        let input = global.map(|c| c.input).get_digest_from_shorts()?;
102        let output = global.map(|c| c.output).get_digest_from_shorts()?;
103        let is_terminate = global.map(|c| c.is_terminate).get_u32_from_elem()?;
104        let term_a0_high = global.map(|c| c.term_a0high).get_u32_from_elem()?;
105        let term_a0_low = global.map(|c| c.term_a0low).get_u32_from_elem()?;
106        let term_a1_high = global.map(|c| c.term_a1high).get_u32_from_elem()?;
107        let term_a1_low = global.map(|c| c.term_a1low).get_u32_from_elem()?;
108        let shutdown_cycle = global.map(|c| c.shutdown_cycle).get_u32_from_elem()?;
109
110        fn try_as_u16(x: u32) -> Result<u16> {
111            x.try_into()
112                .map_err(|err: TryFromIntError| anyhow!("{err}"))
113        }
114
115        let terminate_state = if is_terminate == 1 {
116            Some(TerminateState {
117                a0: HighLowU16(try_as_u16(term_a0_high)?, try_as_u16(term_a0_low)?),
118                a1: HighLowU16(try_as_u16(term_a1_high)?, try_as_u16(term_a1_low)?),
119            })
120        } else {
121            None
122        };
123
124        let output = if is_terminate == 1 {
125            Some(output)
126        } else {
127            None
128        };
129
130        Ok(Rv32imV2Claim {
131            pre_state,
132            post_state,
133            input,
134            output,
135            terminate_state,
136            shutdown_cycle: Some(shutdown_cycle),
137        })
138    }
139}