Skip to content

Commit 3f87626

Browse files
authored
Add Stwo cairo runner API (#2351)
* Add Stwo cairo runner API * Make proof_mode and disable_trace_padding configurable in Stwo API * Gate run_for_steps on proof_mode in cairo_run_stwo * Make trace_enabled and disable_trace_padding explicit in stwo runner API * Add tests for stwo cairo runner API * Add stwo vs legacy equivalence test * Add changelog entry for stwo cairo runner API * Document new_stwo and initialize_stwo pairing * Add cairo_run_pie_stwo for running CairoPIEs without layouts * Add tests for cairo_run_pie_stwo * Fix get_cairo_pie in ExecutionMode for stwo runner
1 parent d72a99e commit 3f87626

4 files changed

Lines changed: 700 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0.
1212

1313
#### Upcoming Changes
1414

15+
* Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351)
16+
1517
* Add union merge strategy for CHANGELOG.md [#2345](https://github.com/lambdaclass/cairo-vm/pull/2345)
1618

1719
* fix: Fix off-by-one comparisons in `split_int`, `assert_250_bit`, and `sqrt` hints [#2348](https://github.com/lambdaclass/cairo-vm/pull/2348)

vm/src/cairo_run.rs

Lines changed: 338 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use crate::{
88
errors::{
99
cairo_run_errors::CairoRunError, runner_errors::RunnerError, vm_exception::VmException,
1010
},
11-
runners::{cairo_pie::CairoPie, cairo_runner::CairoRunner},
11+
runners::{
12+
cairo_pie::CairoPie,
13+
cairo_runner::{CairoRunner, RunnerMode},
14+
},
1215
security::verify_secure_runner,
1316
trace::trace_entry::RelocatedTraceEntry,
1417
},
@@ -67,6 +70,82 @@ impl Default for CairoRunConfig<'_> {
6770
}
6871
}
6972

73+
pub struct StwoCairoRunConfig {
74+
pub trace_enabled: bool,
75+
pub relocate_mem: bool,
76+
pub relocate_trace: bool,
77+
pub fill_holes: bool,
78+
pub secure_run: bool,
79+
pub disable_trace_padding: bool,
80+
}
81+
82+
impl Default for StwoCairoRunConfig {
83+
fn default() -> Self {
84+
StwoCairoRunConfig {
85+
trace_enabled: true,
86+
relocate_mem: false,
87+
relocate_trace: true,
88+
fill_holes: false,
89+
secure_run: true,
90+
disable_trace_padding: true,
91+
}
92+
}
93+
}
94+
95+
#[allow(clippy::result_large_err)]
96+
pub fn cairo_run_stwo(
97+
program: &Program,
98+
runner_mode: RunnerMode,
99+
allowed_builtins: &[BuiltinName],
100+
hint_processor: &mut dyn HintProcessor,
101+
exec_scopes: ExecutionScopes,
102+
cairo_run_config: &StwoCairoRunConfig,
103+
) -> Result<CairoRunner, CairoRunError> {
104+
let _span = span!(Level::INFO, "cairo run stwo").entered();
105+
106+
let proof_mode = runner_mode != RunnerMode::ExecutionMode;
107+
let mut cairo_runner = CairoRunner::new_stwo(
108+
program,
109+
runner_mode,
110+
cairo_run_config.trace_enabled,
111+
cairo_run_config.disable_trace_padding,
112+
)?;
113+
cairo_runner.exec_scopes = exec_scopes;
114+
115+
let end = cairo_runner.initialize_stwo(allowed_builtins)?;
116+
117+
cairo_runner
118+
.run_until_pc(end, hint_processor)
119+
.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
120+
121+
if proof_mode {
122+
cairo_runner.run_for_steps(1, hint_processor)?;
123+
}
124+
125+
cairo_runner.end_run(
126+
cairo_run_config.disable_trace_padding,
127+
false,
128+
hint_processor,
129+
cairo_run_config.fill_holes,
130+
)?;
131+
132+
cairo_runner.read_return_values(false)?;
133+
if proof_mode {
134+
cairo_runner.finalize_segments()?;
135+
}
136+
137+
if cairo_run_config.secure_run {
138+
verify_secure_runner(&cairo_runner, true, None)?;
139+
}
140+
141+
cairo_runner.relocate(
142+
cairo_run_config.relocate_mem,
143+
cairo_run_config.relocate_trace,
144+
)?;
145+
146+
Ok(cairo_runner)
147+
}
148+
70149
#[allow(clippy::result_large_err)]
71150
/// Runs a program with a customized execution scope.
72151
pub fn cairo_run_program_with_initial_scope(
@@ -251,6 +330,86 @@ pub fn cairo_run_pie(
251330
Ok(cairo_runner)
252331
}
253332

333+
/// Runs a Cairo PIE using the Stwo runtime API.
334+
/// Same as `cairo_run_pie` but uses `new_stwo` + `initialize_stwo` instead of layouts.
335+
/// Note: Cairo PIEs cannot be run in proof mode.
336+
/// WARNING: As the RunResources are part of the HintProcessor trait, the caller should make sure that
337+
/// the number of steps in the `RunResources` matches that of the `ExecutionResources` in the `CairoPie`.
338+
/// An error will be returned if this doesn't hold.
339+
#[allow(clippy::result_large_err)]
340+
pub fn cairo_run_pie_stwo(
341+
pie: &CairoPie,
342+
allowed_builtins: &[BuiltinName],
343+
hint_processor: &mut dyn HintProcessor,
344+
cairo_run_config: &StwoCairoRunConfig,
345+
) -> Result<CairoRunner, CairoRunError> {
346+
if hint_processor
347+
.get_n_steps()
348+
.is_none_or(|steps| steps != pie.execution_resources.n_steps)
349+
{
350+
return Err(RunnerError::PieNStepsVsRunResourcesNStepsMismatch.into());
351+
}
352+
pie.run_validity_checks()?;
353+
354+
let program = Program::from_stripped_program(&pie.metadata.program);
355+
let mut cairo_runner = CairoRunner::new_stwo(
356+
&program,
357+
RunnerMode::ExecutionMode,
358+
cairo_run_config.trace_enabled,
359+
cairo_run_config.disable_trace_padding,
360+
)?;
361+
362+
let end = cairo_runner.initialize_stwo(allowed_builtins)?;
363+
cairo_runner.vm.finalize_segments_by_cairo_pie(pie);
364+
// Load builtin additional data
365+
for (name, data) in pie.additional_data.0.iter() {
366+
// Data is not trusted in secure_run, therefore we skip extending the hash builtin's data
367+
if matches!(name, BuiltinName::pedersen) && cairo_run_config.secure_run {
368+
continue;
369+
}
370+
if let Some(builtin) = cairo_runner
371+
.vm
372+
.builtin_runners
373+
.iter_mut()
374+
.find(|b| b.name() == *name)
375+
{
376+
builtin.extend_additional_data(data)?;
377+
}
378+
}
379+
// Load previous execution memory
380+
let has_zero_segment = cairo_runner.vm.segments.has_zero_segment() as usize;
381+
let n_extra_segments = pie.metadata.extra_segments.len() - has_zero_segment;
382+
cairo_runner
383+
.vm
384+
.segments
385+
.load_pie_memory(&pie.memory, n_extra_segments)?;
386+
387+
cairo_runner
388+
.run_until_pc(end, hint_processor)
389+
.map_err(|err| VmException::from_vm_error(&cairo_runner, err))?;
390+
391+
cairo_runner.end_run(
392+
cairo_run_config.disable_trace_padding,
393+
false,
394+
hint_processor,
395+
cairo_run_config.fill_holes,
396+
)?;
397+
398+
cairo_runner.read_return_values(false)?;
399+
400+
if cairo_run_config.secure_run {
401+
verify_secure_runner(&cairo_runner, true, None)?;
402+
// Check that the Cairo PIE produced by this run is compatible with the Cairo PIE received
403+
cairo_runner.get_cairo_pie()?.check_pie_compatibility(pie)?;
404+
}
405+
cairo_runner.relocate(
406+
cairo_run_config.relocate_mem,
407+
cairo_run_config.relocate_trace,
408+
)?;
409+
410+
Ok(cairo_runner)
411+
}
412+
254413
#[cfg(feature = "test_utils")]
255414
#[allow(clippy::result_large_err)]
256415
pub fn cairo_run_fuzzed_program(
@@ -553,6 +712,184 @@ mod tests {
553712
)));
554713
}
555714

715+
fn make_cairo_pie(program_content: &[u8]) -> CairoPie {
716+
let runner = cairo_run(
717+
program_content,
718+
&CairoRunConfig {
719+
layout: LayoutName::all_cairo_stwo,
720+
..Default::default()
721+
},
722+
&mut BuiltinHintProcessor::new_empty(),
723+
)
724+
.unwrap();
725+
runner.get_cairo_pie().unwrap()
726+
}
727+
728+
fn stwo_allowed_builtins() -> Vec<BuiltinName> {
729+
let mut allowed = vec![
730+
BuiltinName::output,
731+
BuiltinName::pedersen,
732+
BuiltinName::range_check,
733+
BuiltinName::bitwise,
734+
BuiltinName::ec_op,
735+
BuiltinName::poseidon,
736+
BuiltinName::range_check96,
737+
];
738+
if cfg!(feature = "mod_builtin") {
739+
allowed.push(BuiltinName::add_mod);
740+
allowed.push(BuiltinName::mul_mod);
741+
}
742+
allowed
743+
}
744+
745+
fn make_cairo_pie_stwo(program_content: &[u8]) -> CairoPie {
746+
let program = Program::from_bytes(program_content, Some("main")).unwrap();
747+
let runner = cairo_run_stwo(
748+
&program,
749+
RunnerMode::ExecutionMode,
750+
&stwo_allowed_builtins(),
751+
&mut BuiltinHintProcessor::new_empty(),
752+
ExecutionScopes::new(),
753+
&StwoCairoRunConfig {
754+
disable_trace_padding: false,
755+
..Default::default()
756+
},
757+
)
758+
.unwrap();
759+
runner.get_cairo_pie().unwrap()
760+
}
761+
762+
fn stwo_pie_config() -> StwoCairoRunConfig {
763+
StwoCairoRunConfig {
764+
disable_trace_padding: false,
765+
..Default::default()
766+
}
767+
}
768+
769+
#[rstest]
770+
#[case(include_bytes!("../../cairo_programs/fibonacci.json"))]
771+
#[case(include_bytes!("../../cairo_programs/integration.json"))]
772+
#[case(include_bytes!("../../cairo_programs/relocate_segments.json"))]
773+
#[case(include_bytes!("../../cairo_programs/ec_op.json"))]
774+
#[case(include_bytes!("../../cairo_programs/bitwise_output.json"))]
775+
fn get_and_run_cairo_pie_stwo(#[case] program_content: &[u8]) {
776+
let cairo_pie = make_cairo_pie_stwo(program_content);
777+
let allowed: Vec<BuiltinName> = cairo_pie
778+
.metadata
779+
.builtin_segments
780+
.keys()
781+
.copied()
782+
.collect();
783+
let mut hint_processor = BuiltinHintProcessor::new(
784+
Default::default(),
785+
RunResources::new(cairo_pie.execution_resources.n_steps),
786+
);
787+
let config = stwo_pie_config();
788+
let runner =
789+
cairo_run_pie_stwo(&cairo_pie, &allowed, &mut hint_processor, &config).unwrap();
790+
assert!(runner.relocated_trace.is_some());
791+
}
792+
793+
#[rstest]
794+
#[case(include_bytes!("../../cairo_programs/fibonacci.json"))]
795+
#[case(include_bytes!("../../cairo_programs/bitwise_output.json"))]
796+
fn cairo_run_pie_stwo_matches_legacy(#[case] program_content: &[u8]) {
797+
let cairo_pie = make_cairo_pie(program_content);
798+
let allowed: Vec<BuiltinName> = cairo_pie
799+
.metadata
800+
.builtin_segments
801+
.keys()
802+
.copied()
803+
.collect();
804+
let legacy_runner = cairo_run_pie(
805+
&cairo_pie,
806+
&CairoRunConfig {
807+
layout: LayoutName::all_cairo_stwo,
808+
trace_enabled: true,
809+
relocate_mem: true,
810+
..Default::default()
811+
},
812+
&mut BuiltinHintProcessor::new(
813+
Default::default(),
814+
RunResources::new(cairo_pie.execution_resources.n_steps),
815+
),
816+
)
817+
.unwrap();
818+
let stwo_config = StwoCairoRunConfig {
819+
relocate_mem: true,
820+
..stwo_pie_config()
821+
};
822+
let stwo_runner = cairo_run_pie_stwo(
823+
&cairo_pie,
824+
&allowed,
825+
&mut BuiltinHintProcessor::new(
826+
Default::default(),
827+
RunResources::new(cairo_pie.execution_resources.n_steps),
828+
),
829+
&stwo_config,
830+
)
831+
.unwrap();
832+
assert_eq!(legacy_runner.relocated_memory, stwo_runner.relocated_memory);
833+
assert_eq!(legacy_runner.relocated_trace, stwo_runner.relocated_trace);
834+
}
835+
836+
#[test]
837+
fn cairo_run_pie_stwo_n_steps_not_set() {
838+
let cairo_pie = make_cairo_pie(include_bytes!("../../cairo_programs/fibonacci.json"));
839+
let res = cairo_run_pie_stwo(
840+
&cairo_pie,
841+
&[],
842+
&mut BuiltinHintProcessor::new_empty(),
843+
&stwo_pie_config(),
844+
);
845+
assert!(res.is_err_and(|err| matches!(
846+
err,
847+
CairoRunError::Runner(RunnerError::PieNStepsVsRunResourcesNStepsMismatch)
848+
)));
849+
}
850+
851+
#[test]
852+
fn cairo_run_pie_stwo_n_steps_mismatch() {
853+
let cairo_pie = make_cairo_pie(include_bytes!("../../cairo_programs/fibonacci.json"));
854+
let wrong_steps = cairo_pie.execution_resources.n_steps + 1;
855+
let res = cairo_run_pie_stwo(
856+
&cairo_pie,
857+
&[],
858+
&mut BuiltinHintProcessor::new(Default::default(), RunResources::new(wrong_steps)),
859+
&stwo_pie_config(),
860+
);
861+
assert!(res.is_err_and(|err| matches!(
862+
err,
863+
CairoRunError::Runner(RunnerError::PieNStepsVsRunResourcesNStepsMismatch)
864+
)));
865+
}
866+
867+
#[test]
868+
fn cairo_run_pie_stwo_without_secure_run() {
869+
let cairo_pie = make_cairo_pie(include_bytes!("../../cairo_programs/fibonacci.json"));
870+
let allowed: Vec<BuiltinName> = cairo_pie
871+
.metadata
872+
.builtin_segments
873+
.keys()
874+
.copied()
875+
.collect();
876+
let config = StwoCairoRunConfig {
877+
secure_run: false,
878+
..stwo_pie_config()
879+
};
880+
let runner = cairo_run_pie_stwo(
881+
&cairo_pie,
882+
&allowed,
883+
&mut BuiltinHintProcessor::new(
884+
Default::default(),
885+
RunResources::new(cairo_pie.execution_resources.n_steps),
886+
),
887+
&config,
888+
)
889+
.unwrap();
890+
assert!(runner.relocated_trace.is_some());
891+
}
892+
556893
/// A simple slice writer for testing BinaryWrite in no_std-like conditions.
557894
struct SliceWriter<'a> {
558895
buf: &'a mut [u8],

vm/src/vm/errors/runner_errors.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ pub enum RunnerError {
124124
DynamicLayoutLogDilutedUnitsPerStepOverflow(i32),
125125
#[error("Initialization failure: Cannot run with trace padding disabled without proof mode")]
126126
DisableTracePaddingWithoutProofMode,
127+
#[error("Builtin {0} is not supported in Stwo mode")]
128+
UnsupportedStwoBuiltin(BuiltinName),
127129
}
128130

129131
#[cfg(test)]

0 commit comments

Comments
 (0)