risc0_zkvm/host/server/prove/
dev_mode.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
15use std::time::Duration;
16
17use anyhow::{ensure, Context, Result};
18use risc0_zkp::core::digest::Digest;
19use serde::{Deserialize, Deserializer, Serialize};
20
21use crate::{
22    claim::{
23        receipt::{exit_code_from_terminate_state, UnionClaim},
24        Unknown,
25    },
26    host::{
27        prove_info::ProveInfo,
28        server::{exec::executor::ExecutorImpl, session::null_callback},
29    },
30    receipt::{FakeReceipt, InnerReceipt, SegmentReceipt, SuccinctReceipt},
31    recursion::MerkleProof,
32    ExecutorEnv, MaybePruned, PreflightResults, ProverOpts, ProverServer, Receipt, ReceiptClaim,
33    Segment, Session, VerifierContext, WorkClaim,
34};
35
36const ERR_DEV_MODE_DISABLED: &str =
37    "zkVM: dev mode is disabled. Unset RISC0_DEV_MODE environment variable to produce valid proofs";
38
39/// Configuration for simulated DevMode delay.
40#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
41pub struct DevModeDelay {
42    /// Delay for prove_segment_core
43    #[serde(deserialize_with = "duration_secs")]
44    pub prove_segment_core: Duration,
45
46    /// Delay for segment_preflight
47    #[serde(deserialize_with = "duration_secs")]
48    pub segment_preflight: Duration,
49
50    /// Delay for prove_keccak
51    #[serde(deserialize_with = "duration_secs")]
52    pub prove_keccak: Duration,
53
54    /// Delay for lift
55    #[serde(deserialize_with = "duration_secs")]
56    pub lift: Duration,
57
58    /// Delay for join
59    #[serde(deserialize_with = "duration_secs")]
60    pub join: Duration,
61
62    /// Delay for union
63    #[serde(deserialize_with = "duration_secs")]
64    pub union: Duration,
65
66    /// Delay for resolve
67    #[serde(deserialize_with = "duration_secs")]
68    pub resolve: Duration,
69}
70
71/// An implementation of a [ProverServer] for development and testing purposes.
72///
73/// This DevModeProver does not produce an actual proof.
74/// Instead, the guest code is executed and a fake receipt is returned with
75/// accurate journal contents but no cryptographic information.
76/// Because the receipt is fake, a verifier can only "verify" this receipt
77/// if dev mode is turned on; verification will otherwise fail.
78///
79/// CONVENIENT, BUT NOT MEANT FOR PRODUCTION
80/// Dev mode supports rapid development by allowing the developer to quickly
81/// iterate on code without being forced to wait for proving to complete.
82/// However, it must not be used in production as it provides no security
83/// whatsoever.
84///
85/// How to enable and disable dev mode:
86/// Dev mode is only used when the environment variable `RISC0_DEV_MODE` is set.
87/// It can be fully disabled at compile time, regardless of environment
88/// variables, by setting the feature flag `disable-dev-mode` on the
89/// `risc0_zkvm` crate.
90#[non_exhaustive]
91pub struct DevModeProver {
92    delay: Option<DevModeDelay>,
93}
94
95/// Utility macro to compress repeated checks that dev mode is not disabled.
96macro_rules! ensure_dev_mode_allowed {
97    () => {
98        ensure!(
99            cfg!(not(feature = "disable-dev-mode")),
100            ERR_DEV_MODE_DISABLED
101        );
102    };
103}
104
105impl DevModeProver {
106    /// Create a DevModeProver without delay.
107    pub fn new() -> Self {
108        Self { delay: None }
109    }
110
111    /// Create a DevModeProver with simulated delay.
112    pub fn with_delay(delay: DevModeDelay) -> Self {
113        Self { delay: Some(delay) }
114    }
115}
116
117impl Default for DevModeProver {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123impl ProverServer for DevModeProver {
124    fn prove(&self, env: ExecutorEnv<'_>, elf: &[u8]) -> Result<ProveInfo> {
125        let ctx = VerifierContext::default().with_dev_mode(true);
126        self.prove_with_ctx(env, &ctx, elf)
127    }
128
129    fn prove_session(&self, ctx: &VerifierContext, session: &Session) -> Result<ProveInfo> {
130        eprintln!(
131            "WARNING: Proving in dev mode does not generate a valid receipt. \
132            Receipts generated from this process are invalid and should never be used in production."
133        );
134
135        ensure!(ctx.dev_mode(), ERR_DEV_MODE_DISABLED);
136        ensure_dev_mode_allowed!();
137
138        let session_claim = session
139            .claim()
140            .context("failed to compute claim for session")?;
141        let receipt = Receipt::new(
142            FakeReceipt::new(session_claim.clone()).into(),
143            session.journal.clone().unwrap_or_default().bytes,
144        );
145        let work_receipt = session.work().map(|work| {
146            FakeReceipt::new(WorkClaim {
147                claim: session_claim.into(),
148                work: work.into(),
149            })
150            .into()
151        });
152
153        Ok(ProveInfo {
154            receipt,
155            work_receipt,
156            stats: session.stats(),
157        })
158    }
159
160    /// Prove the specified ELF binary using the specified [VerifierContext].
161    fn prove_with_ctx(
162        &self,
163        env: ExecutorEnv<'_>,
164        ctx: &VerifierContext,
165        elf: &[u8],
166    ) -> Result<ProveInfo> {
167        let session = ExecutorImpl::from_elf(env, elf)
168            .unwrap()
169            .run_with_callback(null_callback)?;
170        self.prove_session(ctx, &session)
171    }
172
173    fn segment_preflight(&self, segment: &Segment) -> Result<PreflightResults> {
174        ensure_dev_mode_allowed!();
175
176        if let Some(ref delay) = self.delay {
177            std::thread::sleep(delay.segment_preflight);
178        }
179
180        Ok(PreflightResults {
181            inner: Default::default(),
182            terminate_state: segment.inner.claim.terminate_state,
183            output: segment.output.clone(),
184            segment_index: segment.index,
185        })
186    }
187
188    fn prove_segment_core(
189        &self,
190        ctx: &VerifierContext,
191        preflight_results: PreflightResults,
192    ) -> Result<SegmentReceipt> {
193        ensure!(ctx.dev_mode(), ERR_DEV_MODE_DISABLED);
194        ensure_dev_mode_allowed!();
195
196        if let Some(ref delay) = self.delay {
197            std::thread::sleep(delay.prove_segment_core);
198        }
199
200        let exit_code = exit_code_from_terminate_state(&preflight_results.terminate_state)?;
201        Ok(SegmentReceipt {
202            seal: Vec::new(),
203            index: preflight_results.segment_index,
204            hashfn: "fake".into(),
205            verifier_parameters: Digest::ZERO,
206            claim: ReceiptClaim {
207                pre: MaybePruned::Pruned(Digest::ZERO),
208                post: MaybePruned::Pruned(Digest::ZERO),
209                exit_code,
210                input: MaybePruned::Pruned(Digest::ZERO),
211                output: MaybePruned::Pruned(Digest::ZERO),
212            },
213        })
214    }
215
216    fn prove_keccak(
217        &self,
218        _request: &crate::ProveKeccakRequest,
219    ) -> Result<SuccinctReceipt<Unknown>> {
220        ensure_dev_mode_allowed!();
221
222        if let Some(ref delay) = self.delay {
223            std::thread::sleep(delay.prove_keccak);
224        }
225
226        Ok(fake_succinct_receipt())
227    }
228
229    fn lift(&self, _receipt: &SegmentReceipt) -> Result<SuccinctReceipt<ReceiptClaim>> {
230        fake_recursion(self.delay.map(|d| d.lift))
231    }
232
233    fn lift_povw(
234        &self,
235        _receipt: &SegmentReceipt,
236    ) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
237        fake_recursion(self.delay.map(|d| d.lift))
238    }
239
240    fn join(
241        &self,
242        _a: &SuccinctReceipt<ReceiptClaim>,
243        _b: &SuccinctReceipt<ReceiptClaim>,
244    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
245        fake_recursion(self.delay.map(|d| d.join))
246    }
247
248    fn join_povw(
249        &self,
250        _a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
251        _b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
252    ) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
253        fake_recursion(self.delay.map(|d| d.join))
254    }
255
256    fn join_unwrap_povw(
257        &self,
258        _a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
259        _b: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
260    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
261        fake_recursion(self.delay.map(|d| d.join))
262    }
263
264    fn resolve(
265        &self,
266        _conditional: &SuccinctReceipt<ReceiptClaim>,
267        _assumption: &SuccinctReceipt<Unknown>,
268    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
269        fake_recursion(self.delay.map(|d| d.resolve))
270    }
271
272    fn resolve_povw(
273        &self,
274        _conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
275        _assumption: &SuccinctReceipt<Unknown>,
276    ) -> Result<SuccinctReceipt<WorkClaim<ReceiptClaim>>> {
277        fake_recursion(self.delay.map(|d| d.resolve))
278    }
279
280    fn resolve_unwrap_povw(
281        &self,
282        _conditional: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
283        _assumption: &SuccinctReceipt<Unknown>,
284    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
285        fake_recursion(self.delay.map(|d| d.resolve))
286    }
287
288    fn union(
289        &self,
290        _a: &SuccinctReceipt<Unknown>,
291        _b: &SuccinctReceipt<Unknown>,
292    ) -> Result<SuccinctReceipt<UnionClaim>> {
293        fake_recursion(self.delay.map(|d| d.union))
294    }
295
296    fn unwrap_povw(
297        &self,
298        _a: &SuccinctReceipt<WorkClaim<ReceiptClaim>>,
299    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
300        // TODO: Apply a delay here. Should be a little smaller than a join.
301        fake_recursion(None)
302    }
303
304    fn identity_p254(
305        &self,
306        _a: &SuccinctReceipt<ReceiptClaim>,
307    ) -> Result<SuccinctReceipt<ReceiptClaim>> {
308        // TODO: Apply a delay here.
309        fake_recursion(None)
310    }
311
312    fn compress(&self, opts: &ProverOpts, receipt: &Receipt) -> Result<Receipt> {
313        ensure!(opts.dev_mode(), ERR_DEV_MODE_DISABLED);
314        ensure_dev_mode_allowed!();
315
316        Ok(Receipt::new(
317            InnerReceipt::Fake(FakeReceipt {
318                claim: receipt.claim()?,
319            }),
320            receipt.journal.bytes.clone(),
321        ))
322    }
323}
324
325/// Private function used to simulate the delay of a lift.
326/// Return type is generic to handle any type of output claim.
327fn fake_recursion<Claim>(delay: Option<Duration>) -> Result<SuccinctReceipt<Claim>> {
328    ensure_dev_mode_allowed!();
329
330    if let Some(delay) = delay {
331        std::thread::sleep(delay);
332    }
333
334    Ok(fake_succinct_receipt())
335}
336
337fn fake_succinct_receipt<Claim>() -> SuccinctReceipt<Claim> {
338    SuccinctReceipt {
339        seal: vec![],
340        control_id: Digest::ZERO,
341        claim: MaybePruned::Pruned(Digest::ZERO),
342        hashfn: "fake".into(),
343        verifier_parameters: Digest::ZERO,
344        control_inclusion_proof: MerkleProof {
345            index: 0,
346            digests: vec![],
347        },
348    }
349}
350
351fn duration_secs<'de, D>(deserializer: D) -> Result<Duration, D::Error>
352where
353    D: Deserializer<'de>,
354{
355    let secs = f64::deserialize(deserializer)?;
356    Ok(Duration::from_secs_f64(secs))
357}