Skip to content

Commit 6c0616b

Browse files
authored
Polish continuation verification and CLI (#728)
1 parent bace1a3 commit 6c0616b

9 files changed

Lines changed: 462 additions & 135 deletions

File tree

bin/cli/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ cargo run -p cli --release -- prove <PROGRAM.elf> -o proof.bin [flags]
5757
| `--private-input <FILE>` | Pass private input bytes to the guest. |
5858
| `--blowup <N>` | FRI blowup factor (power of 2). Higher = fewer queries, smaller proof, slower proving. [default: 2] |
5959
| `--time` | Print total proving time. |
60-
| `--cycles` | Run one extra execution outside the timer and print the dynamic instruction count. |
61-
| `--elements` | Build traces and print main-trace and aux-trace field element counts. |
60+
| `--cycles` | Run one extra execution outside the timer and print the dynamic instruction count. Also supported with `--continuations`. |
61+
| `--elements` | Build traces and print main-trace and aux-trace field element counts. Monolithic proving only; conflicts with `--continuations`. |
6262
| `--continuations` | Prove as a continuation bundle split into fixed-size epochs. |
6363
| `--epoch-size-log2 <N>` | Continuation epoch size as `2^N` cycles. Requires `--continuations`. Defaults to `20`; values below `18` are rejected. |
6464

@@ -76,7 +76,8 @@ cargo run -p cli --release -- verify <PROOF> <PROGRAM.elf> [flags]
7676
| `--time` | Print verification time. |
7777
| `--continuations` | Verify a continuation proof bundle produced by `prove --continuations`. |
7878

79-
Returns exit code `0` on successful verification, `1` on failure.
79+
Returns exit code `0` on successful verification, `1` on failure. `--blowup` must
80+
match the value used during proving.
8081

8182
### Count Elements
8283

@@ -103,6 +104,9 @@ cargo run -p cli --release -- verify /tmp/proof.bin executor/program_artifacts/a
103104
cargo run -p cli --release -- prove program.elf -o /tmp/cont.bin --continuations --epoch-size-log2 20
104105
cargo run -p cli --release -- verify /tmp/cont.bin program.elf --continuations
105106

107+
# Generate a continuation proof and print total dynamic instruction count
108+
cargo run -p cli --release -- prove program.elf -o /tmp/cont.bin --continuations --cycles
109+
106110
# Prove with private input and print metrics
107111
cargo run -p cli --release -- prove program.elf -o /tmp/proof.bin --private-input input.bin --time --cycles
108112
```
@@ -114,6 +118,11 @@ As rough ethrex 10-transfer distinct-account reference points from a local sweep
114118
about 26.8 GB. For a new workload, use the highest value the machine can run
115119
without swapping.
116120

121+
Continuation proof bundles are self-contained for standalone verification. When
122+
`--private-input` is used, the serialized continuation proof includes the raw
123+
private input bytes so the verifier can rebuild the genesis memory commitment.
124+
Do not treat continuation proof files as confidential-input hiding artifacts.
125+
117126
## Guest Program Flamegraphs
118127

119128
Generate flamegraphs showing where the guest RISC-V program spends its execution time (by instruction count).

bin/cli/src/main.rs

Lines changed: 97 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ enum Commands {
133133

134134
/// Blowup factor (power of 2). Higher = fewer queries, smaller proof, slower proving.
135135
#[arg(long, default_value = "2")]
136-
blowup: Option<u8>,
136+
blowup: u8,
137137

138138
/// Print proving time
139139
#[arg(long)]
@@ -145,7 +145,7 @@ enum Commands {
145145

146146
/// Build traces and print total main-trace field elements (rows × columns summed across
147147
/// all tables) and aux-trace field elements (committed EF columns × rows)
148-
#[arg(long)]
148+
#[arg(long, conflicts_with = "continuations")]
149149
elements: bool,
150150

151151
/// Prove with continuations (split execution into epochs; flat peak memory)
@@ -175,7 +175,7 @@ enum Commands {
175175

176176
/// Blowup factor used during proving (must match)
177177
#[arg(long, default_value = "2")]
178-
blowup: Option<u8>,
178+
blowup: u8,
179179

180180
/// Print verification time
181181
#[arg(long)]
@@ -221,7 +221,15 @@ fn main() -> ExitCode {
221221
epoch_size_log2,
222222
} => {
223223
if continuations {
224-
cmd_prove_continuation(elf, output, private_input, epoch_size_log2, blowup, time)
224+
cmd_prove_continuation(
225+
elf,
226+
output,
227+
private_input,
228+
epoch_size_log2,
229+
blowup,
230+
time,
231+
cycles,
232+
)
225233
} else {
226234
cmd_prove(elf, output, private_input, blowup, time, cycles, elements)
227235
}
@@ -253,6 +261,17 @@ fn read_private_input(path: Option<&PathBuf>) -> Result<Vec<u8>, String> {
253261
}
254262
}
255263

264+
fn count_cycles(elf_data: &[u8], private_inputs: &[u8]) -> Result<u64, String> {
265+
let program =
266+
Elf::load(elf_data).map_err(|e| format!("Failed to load ELF for cycle count: {e:?}"))?;
267+
let executor = Executor::new(&program, private_inputs.to_vec())
268+
.map_err(|e| format!("Failed to create executor for cycle count: {e:?}"))?;
269+
executor
270+
.run()
271+
.map(|result| result.logs.len() as u64)
272+
.map_err(|e| format!("Execution failed during cycle count: {e:?}"))
273+
}
274+
256275
fn cmd_execute(
257276
elf_path: PathBuf,
258277
private_input_path: Option<PathBuf>,
@@ -360,7 +379,7 @@ fn cmd_prove(
360379
elf_path: PathBuf,
361380
output_path: PathBuf,
362381
private_input_path: Option<PathBuf>,
363-
blowup: Option<u8>,
382+
blowup: u8,
364383
time: bool,
365384
cycles: bool,
366385
elements: bool,
@@ -386,24 +405,10 @@ fn cmd_prove(
386405
// Mirrors SP1's cycle-count pass so both provers report the same kind of
387406
// number without inflating the measured proving time.
388407
let cycle_count = if cycles {
389-
let program = match Elf::load(&elf_data) {
390-
Ok(p) => p,
408+
match count_cycles(&elf_data, &private_inputs) {
409+
Ok(count) => Some(count),
391410
Err(e) => {
392-
eprintln!("Failed to load ELF for cycle count: {:?}", e);
393-
return ExitCode::FAILURE;
394-
}
395-
};
396-
let executor = match Executor::new(&program, private_inputs.clone()) {
397-
Ok(e) => e,
398-
Err(e) => {
399-
eprintln!("Failed to create executor for cycle count: {:?}", e);
400-
return ExitCode::FAILURE;
401-
}
402-
};
403-
match executor.run() {
404-
Ok(result) => Some(result.logs.len() as u64),
405-
Err(e) => {
406-
eprintln!("Execution failed during cycle count: {:?}", e);
411+
eprintln!("{e}");
407412
return ExitCode::FAILURE;
408413
}
409414
}
@@ -434,31 +439,23 @@ fn cmd_prove(
434439
});
435440

436441
let start = Instant::now();
437-
let proof = match blowup {
438-
Some(b) => {
439-
let opts = match GoldilocksCubicProofOptions::with_blowup(b) {
440-
Ok(opts) => opts,
441-
Err(e) => {
442-
eprintln!("Invalid proof options: {e}");
443-
return ExitCode::FAILURE;
444-
}
445-
};
446-
eprintln!(
447-
"Generating proof (blowup={b}, queries={})...",
448-
opts.fri_number_of_queries
449-
);
450-
prover::prove_with_options_and_inputs(
451-
&elf_data,
452-
&private_inputs,
453-
&opts,
454-
&Default::default(),
455-
)
456-
}
457-
None => {
458-
eprintln!("Generating proof...");
459-
prover::prove_with_inputs(&elf_data, &private_inputs)
442+
let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) {
443+
Ok(opts) => opts,
444+
Err(e) => {
445+
eprintln!("Invalid proof options: {e}");
446+
return ExitCode::FAILURE;
460447
}
461448
};
449+
eprintln!(
450+
"Generating proof (blowup={blowup}, queries={})...",
451+
opts.fri_number_of_queries
452+
);
453+
let proof = prover::prove_with_options_and_inputs(
454+
&elf_data,
455+
&private_inputs,
456+
&opts,
457+
&Default::default(),
458+
);
462459
let prove_elapsed = start.elapsed();
463460
let proof = match proof {
464461
Ok(proof) => proof,
@@ -510,7 +507,7 @@ fn cmd_prove(
510507
ExitCode::SUCCESS
511508
}
512509

513-
fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: Option<u8>, time: bool) -> ExitCode {
510+
fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: u8, time: bool) -> ExitCode {
514511
eprintln!("Reading ELF file...");
515512
let elf_data = match std::fs::read(&elf_path) {
516513
Ok(data) => data,
@@ -539,19 +536,14 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: Option<u8>, time:
539536

540537
eprintln!("Verifying proof...");
541538
let start = Instant::now();
542-
let result = match blowup {
543-
Some(b) => {
544-
let opts = match GoldilocksCubicProofOptions::with_blowup(b) {
545-
Ok(opts) => opts,
546-
Err(e) => {
547-
eprintln!("Invalid proof options: {e}");
548-
return ExitCode::FAILURE;
549-
}
550-
};
551-
prover::verify_with_options(&proof, &elf_data, &opts, None, None)
539+
let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) {
540+
Ok(opts) => opts,
541+
Err(e) => {
542+
eprintln!("Invalid proof options: {e}");
543+
return ExitCode::FAILURE;
552544
}
553-
None => prover::verify(&proof, &elf_data),
554545
};
546+
let result = prover::verify_with_options(&proof, &elf_data, &opts, None, None);
555547
let verify_elapsed = start.elapsed();
556548
let result = match result {
557549
Ok(valid) => valid,
@@ -568,7 +560,7 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: Option<u8>, time:
568560
}
569561
ExitCode::SUCCESS
570562
} else {
571-
eprintln!("Verification failed!");
563+
eprintln!("Verification failed! Ensure --blowup matches the value used for proving.");
572564
ExitCode::FAILURE
573565
}
574566
}
@@ -578,8 +570,9 @@ fn cmd_prove_continuation(
578570
output_path: PathBuf,
579571
private_input_path: Option<PathBuf>,
580572
epoch_size_log2: Option<u32>,
581-
blowup: Option<u8>,
573+
blowup: u8,
582574
time: bool,
575+
cycles: bool,
583576
) -> ExitCode {
584577
eprintln!("Reading ELF file...");
585578
let elf_data = match std::fs::read(&elf_path) {
@@ -598,6 +591,18 @@ fn cmd_prove_continuation(
598591
}
599592
};
600593

594+
let cycle_count = if cycles {
595+
match count_cycles(&elf_data, &private_inputs) {
596+
Ok(count) => Some(count),
597+
Err(e) => {
598+
eprintln!("{e}");
599+
return ExitCode::FAILURE;
600+
}
601+
}
602+
} else {
603+
None
604+
};
605+
601606
let epoch_size_log2 = epoch_size_log2.unwrap_or(DEFAULT_CONTINUATION_EPOCH_SIZE_LOG2);
602607
let epoch_size = match continuation_epoch_size(epoch_size_log2) {
603608
Ok(size) => size,
@@ -607,7 +612,6 @@ fn cmd_prove_continuation(
607612
}
608613
};
609614

610-
let blowup = blowup.unwrap_or(2);
611615
let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) {
612616
Ok(opts) => opts,
613617
Err(e) => {
@@ -656,6 +660,9 @@ fn cmd_prove_continuation(
656660
}
657661

658662
eprintln!("Proof written to {:?}", output_path);
663+
if let Some(c) = cycle_count {
664+
println!("Cycles: {}", c);
665+
}
659666
println!("Epochs: {}", bundle.num_epochs());
660667
if time {
661668
println!("Proving time: {:.3}s", prove_elapsed.as_secs_f64());
@@ -666,7 +673,7 @@ fn cmd_prove_continuation(
666673
fn cmd_verify_continuation(
667674
proof_path: PathBuf,
668675
elf_path: PathBuf,
669-
blowup: Option<u8>,
676+
blowup: u8,
670677
time: bool,
671678
) -> ExitCode {
672679
eprintln!("Reading ELF file...");
@@ -694,7 +701,6 @@ fn cmd_verify_continuation(
694701
}
695702
};
696703

697-
let blowup = blowup.unwrap_or(2);
698704
let opts = match GoldilocksCubicProofOptions::with_blowup(blowup) {
699705
Ok(opts) => opts,
700706
Err(e) => {
@@ -719,7 +725,7 @@ fn cmd_verify_continuation(
719725
ExitCode::SUCCESS
720726
}
721727
Ok(None) => {
722-
eprintln!("Verification failed!");
728+
eprintln!("Verification failed! Ensure --blowup matches the value used for proving.");
723729
ExitCode::FAILURE
724730
}
725731
Err(e) => {
@@ -819,6 +825,34 @@ mod tests {
819825
assert!(r.is_ok());
820826
}
821827

828+
#[test]
829+
fn cycles_accepts_continuations() {
830+
let r = Cli::command().try_get_matches_from([
831+
"cli",
832+
"prove",
833+
"prog.elf",
834+
"-o",
835+
"out",
836+
"--continuations",
837+
"--cycles",
838+
]);
839+
assert!(r.is_ok());
840+
}
841+
842+
#[test]
843+
fn elements_conflicts_with_continuations() {
844+
let r = Cli::command().try_get_matches_from([
845+
"cli",
846+
"prove",
847+
"prog.elf",
848+
"-o",
849+
"out",
850+
"--continuations",
851+
"--elements",
852+
]);
853+
assert!(r.is_err());
854+
}
855+
822856
#[test]
823857
fn epoch_size_log2_rejects_tiny_cli_values() {
824858
let r = Cli::command().try_get_matches_from([

0 commit comments

Comments
 (0)