Skip to content

Commit c4711f8

Browse files
authored
ci: use minimal bitwise table in prover tests for ~100x speedup (#574)
Add test-only `from_elf_and_logs_minimal`, `prove_vm_minimal`, and `verify_vm_minimal` helpers that trim zero rows from the bitwise table. Convert instruction-coverage tests to the fast path while keeping security/tamper tests on the production `crate::prove*`/`crate::verify*` path so regressions in the public API are still caught. Both helpers hardcode `default_test_options()` internally (no asymmetry). The full preprocessed bitwise path is also covered by the `#[ignore]`d `test_prove_elfs_all_instructions_64_full`.
1 parent 8ebca95 commit c4711f8

2 files changed

Lines changed: 140 additions & 42 deletions

File tree

prover/src/tables/trace_builder.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2863,6 +2863,22 @@ impl Traces {
28632863
) -> Result<Self, Error> {
28642864
Self::from_logs_trimmed(logs, instructions, max_rows)
28652865
}
2866+
2867+
/// Like [`from_elf_and_logs`] but trims the bitwise table (TEST ONLY).
2868+
///
2869+
/// Produces PAGE and REGISTER tables (requires ELF) while keeping the
2870+
/// bitwise table small. Same unsoundness caveats as [`from_logs_trimmed`].
2871+
#[cfg(test)]
2872+
pub fn from_elf_and_logs_minimal(
2873+
elf: &Elf,
2874+
logs: &[Log],
2875+
max_rows: &super::MaxRowsConfig,
2876+
private_input: &[u8],
2877+
) -> Result<Self, Error> {
2878+
let mut traces = Self::from_elf_and_logs(elf, logs, max_rows, private_input)?;
2879+
traces.bitwise = bitwise::trim_zero_rows(traces.bitwise);
2880+
Ok(traces)
2881+
}
28662882
}
28672883

28682884
#[cfg(test)]

prover/src/tests/prove_elfs_tests.rs

Lines changed: 124 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ use stark::prover::{IsStarkProver, Prover};
2222
use stark::traits::AIR;
2323
use stark::verifier::{IsStarkVerifier, Verifier};
2424

25+
use crate::VmProof;
26+
use crate::tables::MaxRowsConfig;
2527
use crate::tables::trace_builder::Traces;
2628
use crate::tables::types::{GoldilocksExtension, GoldilocksField};
2729

2830
use executor::elf::Elf;
31+
use executor::vm::execution::Executor;
2932

3033
// Import shared utilities
3134
use crate::VmAirs;
@@ -83,6 +86,79 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool {
8386
)
8487
}
8588

89+
/// Like [`crate::prove_with_options_and_inputs`] but with trimmed bitwise (TEST ONLY).
90+
///
91+
/// ~100x faster than the production path. Same unsoundness caveats as
92+
/// [`Traces::from_elf_and_logs_minimal`]. The full preprocessed bitwise
93+
/// path is covered by `test_prove_elfs_all_instructions_64_full`.
94+
fn prove_vm_minimal(elf_bytes: &[u8], private_inputs: &[u8], max_rows: &MaxRowsConfig) -> VmProof {
95+
let proof_options = ProofOptions::default_test_options();
96+
let elf = Elf::load(elf_bytes).expect("ELF load");
97+
let executor = Executor::new(&elf, private_inputs.to_vec()).expect("executor");
98+
let result = executor.run().expect("execution");
99+
let mut traces =
100+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, max_rows, private_inputs).unwrap();
101+
let table_counts = traces.table_counts();
102+
let airs = VmAirs::new(
103+
&elf,
104+
&proof_options,
105+
true,
106+
&traces.page_configs,
107+
&table_counts,
108+
);
109+
let runtime_page_ranges = traces.runtime_page_ranges();
110+
let proof = Prover::multi_prove(
111+
airs.air_trace_pairs(&mut traces),
112+
&mut DefaultTranscript::<E>::new(&[]),
113+
)
114+
.expect("prove");
115+
let num_private_input_pages = traces
116+
.page_configs
117+
.iter()
118+
.filter(|c| c.is_private_input)
119+
.count();
120+
VmProof {
121+
proof,
122+
runtime_page_ranges,
123+
table_counts,
124+
public_output: traces.public_output_bytes.clone(),
125+
num_private_input_pages,
126+
}
127+
}
128+
129+
/// Like [`crate::verify_with_options`] but matches the minimal bitwise AIR.
130+
///
131+
/// Must be used to verify proofs from [`prove_vm_minimal`].
132+
fn verify_vm_minimal(vm_proof: &VmProof, elf_bytes: &[u8]) -> bool {
133+
let proof_options = ProofOptions::default_test_options();
134+
let elf = Elf::load(elf_bytes).expect("ELF load");
135+
let page_configs = Traces::page_configs_from_elf_and_runtime(
136+
&elf,
137+
&vm_proof.runtime_page_ranges,
138+
vm_proof.num_private_input_pages,
139+
);
140+
let airs = VmAirs::new(
141+
&elf,
142+
&proof_options,
143+
true,
144+
&page_configs,
145+
&vm_proof.table_counts,
146+
);
147+
let air_refs = airs.air_refs();
148+
let expected_bus_balance = crate::compute_expected_commit_bus_balance(
149+
&air_refs,
150+
&vm_proof.proof,
151+
&vm_proof.public_output,
152+
)
153+
.expect("fingerprint collision in test");
154+
Verifier::multi_verify(
155+
&air_refs,
156+
&vm_proof.proof,
157+
&mut DefaultTranscript::<E>::new(&[]),
158+
&expected_bus_balance,
159+
)
160+
}
161+
86162
// =============================================================================
87163
// Integration tests
88164
// =============================================================================
@@ -157,8 +233,9 @@ fn test_cpu_only_no_bus() {
157233
fn test_prove_elfs_sub_fast() {
158234
let _ = env_logger::builder().is_test(true).try_init();
159235
let (elf, logs, _instructions) = run_asm_elf("sub");
160-
// Use from_elf_and_logs to get PAGE and REGISTER tables for Memory bus
161-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
236+
// Use from_elf_and_logs_minimal to get PAGE and REGISTER tables for Memory bus
237+
let mut traces =
238+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
162239

163240
assert!(
164241
prove_and_verify_vm_minimal(&elf, &mut traces),
@@ -597,7 +674,8 @@ fn test_prove_elfs_test_xor_8() {
597674
#[test]
598675
fn test_prove_elfs_test_lb_lh_8() {
599676
let (elf, logs, _instructions) = run_asm_elf("test_lb_lh_8");
600-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
677+
let mut traces =
678+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
601679
assert!(
602680
prove_and_verify_vm_minimal(&elf, &mut traces),
603681
"test_lb_lh_8 failed"
@@ -607,7 +685,8 @@ fn test_prove_elfs_test_lb_lh_8() {
607685
#[test]
608686
fn test_prove_elfs_test_sb_sh_8() {
609687
let (elf, logs, _instructions) = run_asm_elf("test_sb_sh_8");
610-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
688+
let mut traces =
689+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
611690
assert!(
612691
!traces.memws.is_empty(),
613692
"test_sb_sh_8 should produce MEMW rows for byte/halfword memory accesses"
@@ -624,7 +703,8 @@ fn test_prove_elfs_test_sb_sh_8() {
624703
#[test]
625704
fn test_prove_elfs_lw_sw() {
626705
let (elf, logs, _instructions) = run_asm_elf("lw_sw");
627-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
706+
let mut traces =
707+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
628708
assert!(
629709
!traces.memw_aligneds.is_empty(),
630710
"lw_sw should produce MEMW_A rows for aligned word accesses"
@@ -644,7 +724,8 @@ fn test_prove_elfs_lw_sw() {
644724
#[test]
645725
fn test_prove_elfs_test_memw_split_ts() {
646726
let (elf, logs, _instructions) = run_asm_elf("test_memw_split_ts");
647-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
727+
let mut traces =
728+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
648729
assert!(
649730
!traces.memws.is_empty(),
650731
"test_memw_split_ts should produce MEMW rows (split old_timestamps from sb+sb+lh)"
@@ -683,7 +764,8 @@ fn test_prove_elfs_all_branches_16() {
683764
#[test]
684765
fn test_prove_elfs_all_loadstore_32() {
685766
let (elf, logs, _instructions) = run_asm_elf("all_loadstore_32");
686-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
767+
let mut traces =
768+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
687769
assert!(
688770
prove_and_verify_vm_minimal(&elf, &mut traces),
689771
"all_loadstore_32 failed"
@@ -826,7 +908,7 @@ fn test_prove_elfs_test_commit_4() {
826908
);
827909

828910
let mut traces =
829-
Traces::from_elf_and_logs(&elf, &result.logs, &Default::default(), &[]).unwrap();
911+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, &Default::default(), &[]).unwrap();
830912
assert_eq!(
831913
traces.public_output_bytes,
832914
result.return_values.memory_values
@@ -852,7 +934,7 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() {
852934
executor::vm::execution::Executor::new(&elf, vec![]).expect("Failed to create executor");
853935
let result = executor.run().expect("Failed to run program");
854936
let mut traces =
855-
Traces::from_elf_and_logs(&elf, &result.logs, &Default::default(), &[]).unwrap();
937+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, &Default::default(), &[]).unwrap();
856938

857939
// Prover uses correct page configs
858940
let table_counts = traces.table_counts();
@@ -1553,7 +1635,8 @@ fn test_debug_memory_tokens_sb_sh() {
15531635
#[test]
15541636
fn test_deep_stack_passes() {
15551637
let (elf, logs, _instructions) = run_asm_elf("deep_stack");
1556-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
1638+
let mut traces =
1639+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
15571640

15581641
assert!(
15591642
prove_and_verify_vm_minimal(&elf, &mut traces),
@@ -1575,7 +1658,7 @@ fn test_deep_stack_runtime_pages_roundtrip() {
15751658
executor::vm::execution::Executor::new(&elf, vec![]).expect("Failed to create executor");
15761659
let result = executor.run().expect("Failed to run program");
15771660
let mut traces =
1578-
Traces::from_elf_and_logs(&elf, &result.logs, &Default::default(), &[]).unwrap();
1661+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, &Default::default(), &[]).unwrap();
15791662

15801663
let runtime_page_ranges = traces.runtime_page_ranges();
15811664
let table_counts = traces.table_counts();
@@ -1635,7 +1718,7 @@ fn test_deep_stack_missing_pages_rejected() {
16351718
executor::vm::execution::Executor::new(&elf, vec![]).expect("Failed to create executor");
16361719
let result = executor.run().expect("Failed to run program");
16371720
let mut traces =
1638-
Traces::from_elf_and_logs(&elf, &result.logs, &Default::default(), &[]).unwrap();
1721+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, &Default::default(), &[]).unwrap();
16391722

16401723
// Prover uses correct page configs (auto-detected from MemoryState)
16411724
let table_counts = traces.table_counts();
@@ -1686,7 +1769,8 @@ fn test_deep_stack_missing_pages_rejected() {
16861769
#[test]
16871770
fn test_heap_alloc_passes() {
16881771
let (elf, logs, _instructions) = run_asm_elf("heap_alloc");
1689-
let mut traces = Traces::from_elf_and_logs(&elf, &logs, &Default::default(), &[]).unwrap();
1772+
let mut traces =
1773+
Traces::from_elf_and_logs_minimal(&elf, &logs, &Default::default(), &[]).unwrap();
16901774

16911775
// Verify runtime_page_ranges includes the heap page
16921776
let ranges = traces.runtime_page_ranges();
@@ -1715,7 +1799,7 @@ fn test_heap_alloc_runtime_pages_roundtrip() {
17151799
executor::vm::execution::Executor::new(&elf, vec![]).expect("Failed to create executor");
17161800
let result = executor.run().expect("Failed to run program");
17171801
let mut traces =
1718-
Traces::from_elf_and_logs(&elf, &result.logs, &Default::default(), &[]).unwrap();
1802+
Traces::from_elf_and_logs_minimal(&elf, &result.logs, &Default::default(), &[]).unwrap();
17191803

17201804
let runtime_page_ranges = traces.runtime_page_ranges();
17211805
let table_counts = traces.table_counts();
@@ -1926,11 +2010,9 @@ fn test_crafted_zero_count_proof_must_not_verify() {
19262010
#[test]
19272011
fn test_small_max_rows_splits_tables() {
19282012
let elf_bytes = crate::test_utils::asm_elf_bytes("all_instructions_64");
1929-
let proof_options = ProofOptions::default_test_options();
19302013
let max_rows = crate::tables::MaxRowsConfig::small();
19312014

1932-
let vm_proof = crate::prove_with_options(&elf_bytes, &proof_options, &max_rows)
1933-
.expect("Prover should succeed with small max_rows");
2015+
let vm_proof = prove_vm_minimal(&elf_bytes, &[], &max_rows);
19342016

19352017
// With 2^5 max rows and 64+ instructions, tables should have multiple chunks.
19362018
assert!(
@@ -1939,9 +2021,10 @@ fn test_small_max_rows_splits_tables() {
19392021
vm_proof.table_counts.cpu
19402022
);
19412023

1942-
let verified = crate::verify_with_options(&vm_proof, &elf_bytes, &proof_options)
1943-
.expect("Verifier should not error");
1944-
assert!(verified, "Proof with small max_rows should verify");
2024+
assert!(
2025+
verify_vm_minimal(&vm_proof, &elf_bytes),
2026+
"Proof with small max_rows should verify"
2027+
);
19452028
}
19462029

19472030
// =============================================================================
@@ -2006,8 +2089,11 @@ fn test_verify_rejects_inflated_table_counts() {
20062089
#[test]
20072090
fn test_prove_wsuffix_64bit() {
20082091
let elf_bytes = crate::test_utils::asm_elf_bytes("test_wsuffix_64bit");
2009-
let result = crate::prove_and_verify(&elf_bytes).expect("prove_and_verify failed");
2010-
assert!(result, "W-suffix 64-bit register test should verify");
2092+
let vm_proof = prove_vm_minimal(&elf_bytes, &[], &Default::default());
2093+
assert!(
2094+
verify_vm_minimal(&vm_proof, &elf_bytes),
2095+
"W-suffix 64-bit register test should verify"
2096+
);
20112097
}
20122098

20132099
/// Proves a minimal Rust std program that uses `init_allocator()` and
@@ -2024,9 +2110,9 @@ fn test_prove_allocator_minimal_reproducer() {
20242110
let elf_bytes =
20252111
std::fs::read(workspace_root.join("executor/program_artifacts/rust/allocator.elf"))
20262112
.expect("allocator.elf not found — run `make compile-programs-rust`");
2027-
let proof = crate::prove(&elf_bytes).expect("prove should succeed");
2113+
let proof = prove_vm_minimal(&elf_bytes, &[], &Default::default());
20282114
assert!(
2029-
crate::verify(&proof, &elf_bytes).expect("verify should not error"),
2115+
verify_vm_minimal(&proof, &elf_bytes),
20302116
"allocator.elf should verify"
20312117
);
20322118
assert_eq!(proof.public_output, b"Hello World");
@@ -2043,9 +2129,9 @@ fn test_pure_commit_rust() {
20432129
let elf_bytes =
20442130
std::fs::read(workspace_root.join("executor/program_artifacts/rust/pure_commit.elf"))
20452131
.expect("pure_commit.elf not found — run `make compile-programs-rust`");
2046-
let proof = crate::prove(&elf_bytes).expect("prove should succeed");
2132+
let proof = prove_vm_minimal(&elf_bytes, &[], &Default::default());
20472133
assert!(
2048-
crate::verify(&proof, &elf_bytes).expect("verify should not error"),
2134+
verify_vm_minimal(&proof, &elf_bytes),
20492135
"pure_commit.elf should verify"
20502136
);
20512137
assert_eq!(proof.public_output, vec![0xAA, 0xBB, 0xCC, 0xDD]);
@@ -2068,12 +2154,8 @@ fn test_prove_with_input_empty() {
20682154
fn test_prove_private_input_xpage() {
20692155
let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage");
20702156
let input: Vec<u8> = (0u8..16).collect();
2071-
let proof =
2072-
crate::prove_with_inputs(&elf_bytes, &input).expect("prove_with_inputs should succeed");
2073-
assert!(
2074-
crate::verify(&proof, &elf_bytes).expect("verify should not error"),
2075-
"proof should verify"
2076-
);
2157+
let proof = prove_vm_minimal(&elf_bytes, &input, &Default::default());
2158+
assert!(verify_vm_minimal(&proof, &elf_bytes), "proof should verify");
20772159
assert_eq!(proof.public_output, input[4..12].to_vec());
20782160
}
20792161

@@ -2085,11 +2167,8 @@ fn test_prove_private_input_different_values() {
20852167
0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
20862168
0x00,
20872169
];
2088-
let proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove");
2089-
assert!(
2090-
crate::verify(&proof, &elf_bytes).expect("verify"),
2091-
"proof should verify"
2092-
);
2170+
let proof = prove_vm_minimal(&elf_bytes, &input, &Default::default());
2171+
assert!(verify_vm_minimal(&proof, &elf_bytes), "proof should verify");
20932172
assert_eq!(proof.public_output, input[4..12].to_vec());
20942173
}
20952174

@@ -2104,9 +2183,9 @@ fn test_prove_commit_sum() {
21042183
std::fs::read(workspace_root.join("executor/program_artifacts/rust/commit_sum.elf"))
21052184
.expect("commit_sum.elf not found — run `make compile-programs-rust`");
21062185
let input = &[3u8, 5u8];
2107-
let proof = crate::prove_with_inputs(&elf_bytes, input).expect("prove should succeed");
2186+
let proof = prove_vm_minimal(&elf_bytes, input, &Default::default());
21082187
assert!(
2109-
crate::verify(&proof, &elf_bytes).expect("verify should not error"),
2188+
verify_vm_minimal(&proof, &elf_bytes),
21102189
"commit_sum should verify"
21112190
);
21122191
assert_eq!(proof.public_output, vec![8u8]);
@@ -2222,7 +2301,7 @@ fn test_verify_rejects_private_input_with_tampered_public_output() {
22222301
let vm_proof = crate::prove_with_inputs(&elf_bytes, &input).expect("prove should succeed");
22232302

22242303
assert!(
2225-
crate::verify(&vm_proof, &elf_bytes).expect("verify"),
2304+
crate::verify(&vm_proof, &elf_bytes).expect("verify should not error"),
22262305
"Baseline must verify"
22272306
);
22282307

@@ -2271,8 +2350,11 @@ fn test_proof_does_not_contain_private_input_field() {
22712350
#[test]
22722351
fn test_addiw_neg_immediate() {
22732352
let elf_bytes = crate::test_utils::asm_elf_bytes("test_addiw_neg");
2274-
let result = crate::prove_and_verify(&elf_bytes).expect("prove_and_verify failed");
2275-
assert!(result, "addiw with negative immediate should verify");
2353+
let proof = prove_vm_minimal(&elf_bytes, &[], &Default::default());
2354+
assert!(
2355+
verify_vm_minimal(&proof, &elf_bytes),
2356+
"addiw with negative immediate should verify"
2357+
);
22762358
}
22772359

22782360
/// Regression test: both main and aux field element counts must be nonzero for any real ELF.

0 commit comments

Comments
 (0)