Skip to main content

sp1_sdk/
lib.rs

1//! # SP1 SDK
2//!
3//! A library for interacting with the SP1 RISC-V zkVM.
4//!
5//! Visit the [Getting Started](https://docs.succinct.xyz/docs/sp1/getting-started/install) section
6//! in the official SP1 documentation for a quick start guide.
7
8#![warn(clippy::pedantic)]
9#![allow(clippy::similar_names)]
10#![allow(clippy::cast_possible_wrap)]
11#![allow(clippy::cast_possible_truncation)]
12#![allow(clippy::cast_sign_loss)]
13#![allow(clippy::module_name_repetitions)]
14#![allow(clippy::needless_range_loop)]
15#![allow(clippy::cast_lossless)]
16#![allow(clippy::bool_to_int_with_if)]
17#![allow(clippy::should_panic_without_expect)]
18#![allow(clippy::field_reassign_with_default)]
19#![allow(clippy::manual_assert)]
20#![allow(clippy::unreadable_literal)]
21#![allow(clippy::match_wildcard_for_single_variants)]
22#![allow(clippy::missing_panics_doc)]
23#![allow(clippy::missing_errors_doc)]
24#![allow(clippy::explicit_iter_loop)]
25#![warn(missing_docs)]
26
27pub mod artifacts;
28pub mod client;
29pub mod cpu;
30pub use cpu::CpuProver;
31pub mod mock;
32pub use mock::MockProver;
33pub mod light;
34pub use light::LightProver;
35#[cfg(feature = "cuda")]
36pub mod cuda;
37#[cfg(feature = "cuda")]
38pub use cuda::CudaProver;
39pub mod env;
40
41pub mod install;
42#[cfg(feature = "network")]
43pub mod network;
44#[cfg(feature = "network")]
45pub use network::prover::NetworkProver;
46
47#[cfg(feature = "blocking")]
48pub mod blocking;
49
50pub mod utils;
51
52// Re-export the client.
53pub use crate::client::ProverClient;
54
55// Re-export the proof and prover traits.
56pub mod proof;
57pub use proof::*;
58pub mod prover;
59
60/// The traits that define how to interact with the prover.
61pub use prover::{ProveRequest, Prover, ProvingKey, SP1ProvingKey, SP1VerificationError};
62
63// Re-export the build utilities and executor primitives.
64pub use sp1_build::include_elf;
65pub use sp1_core_executor::{ExecutionReport, HookEnv, SP1Context, SP1ContextBuilder, StatusCode};
66
67// Re-export the machine/prover primitives.
68pub use sp1_core_machine::io::SP1Stdin;
69pub use sp1_core_machine::riscv::RiscvAir;
70pub use sp1_primitives::{io::SP1PublicValues, Elf};
71pub use sp1_prover::{HashableKey, ProverMode, SP1VerifyingKey, SP1_CIRCUIT_VERSION};
72
73/// A prelude, including all the types and traits that are commonly used.
74pub mod prelude {
75    pub use super::{
76        include_elf, Elf, HashableKey, ProveRequest, Prover, ProvingKey, RiscvAir,
77        SP1ProofWithPublicValues, SP1Stdin,
78    };
79}
80
81// Re-export the utilities.
82pub use utils::setup_logger;
83
84#[cfg(all(test, feature = "slow-tests"))]
85mod tests {
86    use sp1_primitives::io::SP1PublicValues;
87
88    use crate::{utils, MockProver, Prover, ProverClient, SP1Stdin};
89
90    #[tokio::test]
91    async fn test_execute() {
92        utils::setup_logger();
93        let client = ProverClient::builder().cpu().build().await;
94        let elf = test_artifacts::FIBONACCI_ELF;
95        let mut stdin = SP1Stdin::new();
96        stdin.write(&10usize);
97        let (_pv, report) = client.execute(elf, stdin).await.unwrap();
98
99        assert_eq!(report.exit_code, 0);
100    }
101
102    #[tokio::test]
103    async fn test_execute_panic() {
104        utils::setup_logger();
105        let client = ProverClient::builder().cpu().build().await;
106        let elf = test_artifacts::PANIC_ELF;
107        let mut stdin = SP1Stdin::new();
108        stdin.write(&10usize);
109        let (_, report) = client.execute(elf, stdin).await.unwrap();
110        assert_eq!(report.exit_code, 1);
111    }
112
113    // TODO: reimplement the cycle limit logic and revive this test.
114    #[should_panic]
115    #[tokio::test]
116    #[ignore = "The cycle limit logic needs to be reimplemented."]
117    async fn test_cycle_limit_fail() {
118        utils::setup_logger();
119        let client = ProverClient::builder().cpu().build().await;
120        let elf = test_artifacts::PANIC_ELF;
121        let mut stdin = SP1Stdin::new();
122        stdin.write(&10usize);
123        client.execute(elf, stdin).cycle_limit(1).await.unwrap();
124    }
125
126    /// Test that cycle tracking via `client.execute()` populates the `ExecutionReport`.
127    ///
128    /// The cycle-tracker test program uses:
129    /// - `cycle-tracker-report-start/end: h` - should populate `cycle_tracker` `HashMap`
130    /// - `cycle-tracker-report-start/end: repeated` (3x) - should accumulate cycles
131    #[tokio::test]
132    async fn test_cycle_tracker_report_variants() {
133        utils::setup_logger();
134        let client = MockProver::new().await;
135        let elf = test_artifacts::CYCLE_TRACKER_ELF;
136        let stdin = SP1Stdin::new();
137
138        let (_pv, report) = client.execute(elf, stdin).await.unwrap();
139
140        // Verify cycle tracking for report variants
141        // "h" should have been tracked once
142        assert!(
143            report.cycle_tracker.contains_key("h"),
144            "Expected cycle_tracker to contain 'h', got: {:?}",
145            report.cycle_tracker
146        );
147        let h_cycles = *report.cycle_tracker.get("h").unwrap();
148        assert!(h_cycles > 0, "Expected 'h' to have positive cycles, got: {h_cycles}");
149
150        // "repeated" should have been tracked 3 times
151        assert!(
152            report.cycle_tracker.contains_key("repeated"),
153            "Expected cycle_tracker to contain 'repeated', got: {:?}",
154            report.cycle_tracker
155        );
156        let repeated_cycles =
157            *report.cycle_tracker.get("repeated").expect("repeated should be populated");
158        assert!(
159            repeated_cycles > 0,
160            "Expected 'repeated' to have positive cycles, got: {repeated_cycles}"
161        );
162
163        // Verify invocation tracker for repeated label
164        assert!(
165            report.invocation_tracker.contains_key("repeated"),
166            "Expected invocation_tracker to contain 'repeated', got: {:?}",
167            report.invocation_tracker
168        );
169        let repeated_invocations =
170            *report.invocation_tracker.get("repeated").expect("repeated should be populated");
171        assert_eq!(
172            repeated_invocations, 3,
173            "Expected 'repeated' to have 3 invocations, got: {repeated_invocations}"
174        );
175
176        // Non-report variants (f, g) should NOT be in the report
177        // (they use cycle-tracker-start/end without "report")
178        assert!(
179            !report.cycle_tracker.contains_key("f"),
180            "Expected cycle_tracker to NOT contain 'f' (non-report variant)"
181        );
182        assert!(
183            !report.cycle_tracker.contains_key("g"),
184            "Expected cycle_tracker to NOT contain 'g' (non-report variant)"
185        );
186
187        tracing::info!("report: {}", report);
188    }
189
190    /// Test that cycle tracking works with the derive macro (non-report variant).
191    /// The macro uses eprintln which goes to stderr (fd=2).
192    /// Non-report variants should be parsed but NOT populate the report.
193    #[tokio::test]
194    async fn test_cycle_tracker_macro_non_report() {
195        utils::setup_logger();
196        let client = MockProver::new().await;
197        let elf = test_artifacts::CYCLE_TRACKER_ELF;
198        let stdin = SP1Stdin::new();
199
200        let (_pv, report) = client.execute(elf, stdin).await.unwrap();
201
202        // The macro uses non-report variant, so "f" should NOT be in cycle_tracker
203        assert!(
204            !report.cycle_tracker.contains_key("f"),
205            "Non-report variant 'f' should not be in cycle_tracker"
206        );
207    }
208
209    /// Test that cycle tracking works correctly across chunk boundaries.
210    #[tokio::test]
211    async fn test_cycle_tracker_across_chunks() {
212        use sp1_core_executor::SP1CoreOpts;
213
214        utils::setup_logger();
215
216        // Use a small chunk threshold to force multiple chunks
217        let mut opts = SP1CoreOpts::default();
218        opts.minimal_trace_chunk_threshold = 1000;
219
220        let client = MockProver::new_with_opts(opts).await;
221        let elf = test_artifacts::CYCLE_TRACKER_ELF;
222        let stdin = SP1Stdin::new();
223
224        // Enable calculate_gas to use the chunk threshold
225        let (_pv, report) = client.execute(elf, stdin).calculate_gas(true).await.unwrap();
226
227        // Verify cycle tracking works correctly across chunks
228        assert!(report.cycle_tracker.contains_key("h"));
229        assert!(*report.cycle_tracker.get("h").unwrap() > 0);
230
231        assert!(report.cycle_tracker.contains_key("repeated"));
232        assert!(*report.cycle_tracker.get("repeated").unwrap() > 0);
233
234        assert_eq!(*report.invocation_tracker.get("repeated").unwrap_or(&0), 3);
235    }
236
237    #[tokio::test]
238    async fn test_e2e_core() {
239        utils::setup_logger();
240        let client = ProverClient::builder().cpu().build().await;
241        let elf = test_artifacts::FIBONACCI_ELF;
242        let pk = client.setup(elf).await.unwrap();
243        let mut stdin = SP1Stdin::new();
244        stdin.write(&10usize);
245
246        // Generate proof & verify.
247        let mut proof = client.prove(&pk, stdin).await.unwrap();
248        client.verify(&proof, &pk.vk, None).unwrap();
249
250        // Test invalid public values.
251        proof.public_values = SP1PublicValues::from(&[255, 4, 84]);
252        if client.verify(&proof, &pk.vk, None).is_ok() {
253            panic!("verified proof with invalid public values")
254        }
255    }
256
257    #[tokio::test]
258    async fn test_e2e_core_panic() {
259        use sp1_core_executor::StatusCode;
260
261        use crate::{prover::ProveRequest, CpuProver};
262
263        utils::setup_logger();
264        let client = CpuProver::new().await;
265        let elf = test_artifacts::PANIC_ELF;
266        let pk = client.setup(elf).await.unwrap();
267        let stdin = SP1Stdin::new();
268
269        // Generate proof & verify.
270        let proof = client.prove(&pk, stdin).core().await.unwrap();
271        client.verify(&proof, &pk.vk, StatusCode::new(1)).unwrap();
272
273        if client.verify(&proof, &pk.vk, None).is_ok() {
274            panic!("verified proof with invalid exit code")
275        }
276
277        if client.verify(&proof, &pk.vk, StatusCode::new(0)).is_ok() {
278            panic!("verified proof with invalid exit code")
279        }
280    }
281
282    // TODO: reimplement the custom stdout/stderr and revive this test
283    // #[tokio::test]
284    // async fn test_e2e_io_override() {
285    //     utils::setup_logger();
286    //     let client = ProverClient::builder().cpu().build().await;
287    //     let elf = test_artifacts::HELLO_WORLD_ELF;
288
289    //     let mut stdout = Vec::new();
290
291    //     // Generate proof & verify.
292    //     let stdin = SP1Stdin::new();
293    //     let _ = client.execute(elf, stdin).stdout(&mut stdout).run().unwrap();
294
295    //     assert_eq!(stdout, b"Hello, world!\n");
296    // }
297
298    #[tokio::test]
299    async fn test_e2e_compressed() {
300        use crate::{prover::ProveRequest, CpuProver};
301
302        utils::setup_logger();
303        let client = CpuProver::new().await;
304        let elf = test_artifacts::FIBONACCI_ELF;
305        let pk = client.setup(elf).await.unwrap();
306        let mut stdin = SP1Stdin::new();
307        stdin.write(&10usize);
308
309        // Generate proof & verify.
310        let mut proof = client.prove(&pk, stdin).compressed().await.unwrap();
311        client.verify(&proof, &pk.vk, None).unwrap();
312
313        // Test invalid public values.
314        proof.public_values = SP1PublicValues::from(&[255, 4, 84]);
315        if client.verify(&proof, &pk.vk, None).is_ok() {
316            panic!("verified proof with invalid public values")
317        }
318    }
319
320    #[tokio::test]
321    async fn test_e2e_compressed_panic() {
322        use sp1_core_executor::StatusCode;
323
324        use crate::{prover::ProveRequest, CpuProver};
325
326        utils::setup_logger();
327        let client = CpuProver::new().await;
328        let elf = test_artifacts::PANIC_ELF;
329        let pk = client.setup(elf).await.unwrap();
330        let stdin = SP1Stdin::new();
331
332        // Generate proof & verify.
333        let proof = client.prove(&pk, stdin).compressed().await.unwrap();
334        client.verify(&proof, &pk.vk, StatusCode::new(1)).unwrap();
335
336        if client.verify(&proof, &pk.vk, None).is_ok() {
337            panic!("verified proof with invalid exit code")
338        }
339
340        if client.verify(&proof, &pk.vk, StatusCode::new(0)).is_ok() {
341            panic!("verified proof with invalid exit code")
342        }
343    }
344
345    #[tokio::test]
346    async fn test_e2e_plonk() {
347        use crate::{prover::ProveRequest, CpuProver};
348
349        utils::setup_logger();
350        let client = CpuProver::new().await;
351        let pk = client.setup(test_artifacts::FIBONACCI_ELF).await.unwrap();
352        let mut stdin = SP1Stdin::new();
353        stdin.write(&10usize);
354
355        let proof = client.prove(&pk, stdin).plonk().await.unwrap();
356        client.verify(&proof, &pk.vk, None).unwrap();
357    }
358
359    #[tokio::test]
360    async fn test_e2e_groth16() {
361        use crate::{prover::ProveRequest, CpuProver};
362
363        utils::setup_logger();
364        let client = CpuProver::new().await;
365        let elf = test_artifacts::FIBONACCI_ELF;
366        let pk = client.setup(elf).await.unwrap();
367        let mut stdin = SP1Stdin::new();
368        stdin.write(&10usize);
369
370        let proof = client.prove(&pk, stdin).groth16().await.unwrap();
371
372        client.verify(&proof, &pk.vk, None).unwrap();
373    }
374}