@@ -621,25 +621,34 @@ fn verify_global(
621621}
622622
623623/// Prove a full continuation and return a self-contained [`ContinuationProof`]
624- /// (prove half only — no verification). Splits the execution into `epoch_size`-cycle
625- /// epochs, proves each, and proves the one cross-epoch global-memory linkage.
624+ /// (prove half only — no verification). Splits the execution into `2^epoch_size_log2`
625+ /// cycle epochs, proves each, and proves the one cross-epoch global-memory linkage.
626626///
627- /// Epoch size is rounded up to a power of two (min 4). An intermediate epoch runs
628- /// exactly `epoch_size` cycles, so a power-of-two size gives its CPU table a
629- /// power-of-two row count and therefore zero padding rows — important because CPU
630- /// padding rows participate in the inline-PC `memory` chain (carrying pc=1) which is
631- /// only anchored by the HALT chip's emit_pc/consume_pc, and intermediate epochs
632- /// exclude HALT. With padding rows present and no HALT their pc=1 tokens dangle and
633- /// the Memory bus fails to balance; zero padding rows sidestep that. The final epoch
634- /// keeps its remainder and its HALT, so its padding chain is anchored as usual. A
635- /// program that fits in one epoch runs as a single final (monolithic-style) epoch.
627+ /// Intermediate epochs run exactly `2^epoch_size_log2` cycles, so their CPU tables
628+ /// have power-of-two row counts and therefore zero padding rows — important because
629+ /// CPU padding rows participate in the inline-PC `memory` chain (carrying pc=1)
630+ /// which is only anchored by the HALT chip's emit_pc/consume_pc, and intermediate
631+ /// epochs exclude HALT. With padding rows present and no HALT their pc=1 tokens
632+ /// dangle and the Memory bus fails to balance; zero padding rows sidestep that. The
633+ /// final epoch keeps its remainder and its HALT, so its padding chain is anchored as
634+ /// usual. A program that fits in one epoch runs as a single final (monolithic-style)
635+ /// epoch.
636636pub fn prove_continuation (
637637 elf_bytes : & [ u8 ] ,
638638 private_inputs : & [ u8 ] ,
639- epoch_size : usize ,
639+ epoch_size_log2 : u32 ,
640640 opts : & ProofOptions ,
641641) -> Result < ContinuationProof , Error > {
642- let epoch_size = epoch_size. next_power_of_two ( ) . max ( 4 ) ;
642+ if epoch_size_log2 < 2 {
643+ return Err ( Error :: InvalidContinuationEpochSize (
644+ "epoch_size_log2 must be at least 2 (4 cycles)" . to_string ( ) ,
645+ ) ) ;
646+ }
647+ let epoch_size = 1usize . checked_shl ( epoch_size_log2) . ok_or_else ( || {
648+ Error :: InvalidContinuationEpochSize ( format ! (
649+ "epoch_size_log2 {epoch_size_log2} is too large for this platform"
650+ ) )
651+ } ) ?;
643652
644653 let elf = Elf :: load ( elf_bytes) . map_err ( |e| Error :: ElfLoad ( format ! ( "{e}" ) ) ) ?;
645654 let mut executor = Executor :: new ( & elf, private_inputs. to_vec ( ) )
@@ -844,10 +853,10 @@ pub fn verify_continuation(
844853pub fn prove_and_verify_continuation (
845854 elf_bytes : & [ u8 ] ,
846855 private_inputs : & [ u8 ] ,
847- epoch_size : usize ,
856+ epoch_size_log2 : u32 ,
848857 opts : & ProofOptions ,
849858) -> Result < Option < Vec < u8 > > , Error > {
850- let bundle = prove_continuation ( elf_bytes, private_inputs, epoch_size , opts) ?;
859+ let bundle = prove_continuation ( elf_bytes, private_inputs, epoch_size_log2 , opts) ?;
851860 verify_continuation ( elf_bytes, & bundle, opts)
852861}
853862
@@ -874,30 +883,26 @@ mod tests {
874883 . logs
875884 . len ( ) ;
876885
877- // Both commits in a single epoch (x254 starts at 0).
886+ // Both commits in a single 64-cycle epoch (x254 starts at 0).
878887 let single = prove_and_verify_continuation (
879888 & elf_bytes,
880889 & [ ] ,
881- total ,
890+ 6 ,
882891 & ProofOptions :: default_test_options ( ) ,
883892 )
884893 . unwrap ( ) ;
885894 assert_eq ! ( single. as_deref( ) , Some ( & expected_output[ ..] ) ) ;
895+ assert ! ( total <= ( 1 << 6 ) , "single-epoch log2 must cover the run" ) ;
886896
887897 // The late commit (only `halt` follows it) lands past the midpoint, so a
888- // half-sized epoch forces it into a later epoch where x254 is already 2.
898+ // 16-cycle epoch forces it into a later epoch where x254 is already 2.
889899 // Prove first so we can assert the run actually split into >1 epoch — without
890900 // this the test would silently pass even if it degraded to a single epoch.
891- let bundle = prove_continuation (
892- & elf_bytes,
893- & [ ] ,
894- ( total / 2 ) . max ( 1 ) ,
895- & ProofOptions :: default_test_options ( ) ,
896- )
897- . unwrap ( ) ;
901+ let bundle =
902+ prove_continuation ( & elf_bytes, & [ ] , 4 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
898903 assert ! (
899904 bundle. num_epochs( ) > 1 ,
900- "a half-sized epoch must split the run into multiple epochs"
905+ "16-cycle epochs must split the run into multiple epochs"
901906 ) ;
902907 let split = verify_continuation ( & elf_bytes, & bundle, & ProofOptions :: default_test_options ( ) )
903908 . unwrap ( ) ;
@@ -909,12 +914,13 @@ mod tests {
909914 }
910915
911916 // A memory-heavy multi-epoch continuation. `all_loadstore_32` is ~34 cycles, so
912- // a power-of-two `epoch_size` of 8 yields several intermediate epochs (each an
917+ // `epoch_size_log2 = 3` (8 cycles) yields several intermediate epochs (each an
913918 // exact power-of-two cycle count → no CPU padding rows) plus a final epoch.
914919 #[ test]
915920 fn test_prove_and_verify_continuation ( ) {
916921 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
917922 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
923+ let epoch_size_log2 = 3 ;
918924 let epoch_size = 8 ;
919925 // Guard against silent degradation: the program must be longer than one
920926 // epoch, otherwise this collapses to a single final epoch and stops testing
@@ -933,7 +939,7 @@ mod tests {
933939 prove_and_verify_continuation(
934940 & elf_bytes,
935941 & [ ] ,
936- epoch_size ,
942+ epoch_size_log2 ,
937943 & ProofOptions :: default_test_options( )
938944 )
939945 . unwrap( )
@@ -944,8 +950,9 @@ mod tests {
944950 // Regression for the `epoch_touched_cells` fresh-register bug. A syscall whose
945951 // operand pointers live in registers (ECSM reads a0/a1/a2) can have those
946952 // registers set in an EARLIER epoch than the call. `test_ecsm_split` sets
947- // a0/a1/a2 at the very start and runs the ECSM ~46 cycles later; epoch_size 32
948- // puts the pointer setup in epoch 0 and the ecall in epoch 1. The per-epoch
953+ // a0/a1/a2 at the very start and runs the ECSM ~46 cycles later;
954+ // `epoch_size_log2 = 5` (32 cycles) puts the pointer setup in epoch 0 and the
955+ // ecall in epoch 1. The per-epoch
949956 // touched-cell pass must carry registers across the boundary — otherwise it
950957 // reads the pointers as 0, mispredicts the touched cells (and the ECSM
951958 // operands), and the epoch cannot verify.
@@ -963,7 +970,7 @@ mod tests {
963970 let out = prove_and_verify_continuation (
964971 & elf_bytes,
965972 & [ ] ,
966- 32 ,
973+ 5 ,
967974 & ProofOptions :: default_test_options ( ) ,
968975 )
969976 . unwrap ( ) ;
@@ -973,27 +980,31 @@ mod tests {
973980 ) ;
974981 }
975982
976- // Guards the power-of-two epoch-size rounding in `prove_and_verify_continuation`.
977- // A non-power-of-two `epoch_size` (10) must still verify: the driver rounds it up
978- // to 16, so intermediate epochs have no CPU padding rows. Without the rounding
979- // this returns `Ok(None)` (dangling padding pc=1 tokens). 16-cycle epochs over
980- // the 33-cycle `test_commit_split` also put its two commits in different epochs,
981- // exercising the cross-epoch x254 carry; asserting the exact aggregated output
982- // keeps this test from silently degrading to a trivial pass.
983+ // Guards that the continuation API takes `epoch_size_log2` directly. A log2 of
984+ // 4 produces 16-cycle epochs over the 33-cycle `test_commit_split`, putting its
985+ // two commits in different epochs and exercising the cross-epoch x254 carry.
983986 #[ test]
984- fn test_continuation_non_power_of_two_epoch_size ( ) {
987+ fn test_continuation_epoch_size_log2 ( ) {
985988 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
986989 let elf_bytes = asm_elf_bytes ( "test_commit_split" ) ;
987990 let out = prove_and_verify_continuation (
988991 & elf_bytes,
989992 & [ ] ,
990- 10 ,
993+ 4 ,
991994 & ProofOptions :: default_test_options ( ) ,
992995 )
993996 . unwrap ( ) ;
994997 assert_eq ! ( out. as_deref( ) , Some ( & [ 0xAA , 0xBB , 0xCC , 0xDD ] [ ..] ) ) ;
995998 }
996999
1000+ #[ test]
1001+ fn test_continuation_rejects_too_small_epoch_size_log2 ( ) {
1002+ assert ! ( matches!(
1003+ prove_continuation( & [ ] , & [ ] , 1 , & ProofOptions :: default_test_options( ) ) ,
1004+ Err ( Error :: InvalidContinuationEpochSize ( _) )
1005+ ) ) ;
1006+ }
1007+
9971008 // ---- Standalone (split) prover/verifier ----
9981009
9991010 // Round-trip: a bundle from prove_continuation verifies on its own (only the
@@ -1003,7 +1014,7 @@ mod tests {
10031014 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10041015 let elf_bytes = asm_elf_bytes ( "test_commit_split" ) ;
10051016 let bundle =
1006- prove_continuation ( & elf_bytes, & [ ] , 10 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1017+ prove_continuation ( & elf_bytes, & [ ] , 4 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10071018 let out = verify_continuation ( & elf_bytes, & bundle, & ProofOptions :: default_test_options ( ) )
10081019 . unwrap ( ) ;
10091020 assert_eq ! ( out. as_deref( ) , Some ( & [ 0xAA , 0xBB , 0xCC , 0xDD ] [ ..] ) ) ;
@@ -1016,7 +1027,7 @@ mod tests {
10161027 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10171028 let elf_bytes = asm_elf_bytes ( "test_commit_split" ) ;
10181029 let bundle =
1019- prove_continuation ( & elf_bytes, & [ ] , 10 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1030+ prove_continuation ( & elf_bytes, & [ ] , 4 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10201031
10211032 let bytes = bincode:: serialize ( & bundle) . unwrap ( ) ;
10221033 let restored: ContinuationProof = bincode:: deserialize ( & bytes) . unwrap ( ) ;
@@ -1034,7 +1045,7 @@ mod tests {
10341045 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10351046 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
10361047 let mut bundle =
1037- prove_continuation ( & elf_bytes, & [ ] , 8 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1048+ prove_continuation ( & elf_bytes, & [ ] , 3 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10381049 assert ! ( bundle. epochs. len( ) >= 3 , "need multiple epochs" ) ;
10391050 bundle. epochs . pop ( ) ;
10401051 assert ! (
@@ -1052,7 +1063,7 @@ mod tests {
10521063 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10531064 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
10541065 let mut bundle =
1055- prove_continuation ( & elf_bytes, & [ ] , 8 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1066+ prove_continuation ( & elf_bytes, & [ ] , 3 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10561067 assert ! ( bundle. epochs. len( ) >= 3 , "need multiple epochs" ) ;
10571068 bundle. epochs . swap ( 0 , 1 ) ;
10581069 assert ! (
@@ -1071,7 +1082,7 @@ mod tests {
10711082 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10721083 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
10731084 let mut bundle =
1074- prove_continuation ( & elf_bytes, & [ ] , 8 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1085+ prove_continuation ( & elf_bytes, & [ ] , 3 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10751086 assert ! (
10761087 bundle. epochs. len( ) >= 2 ,
10771088 "need a second epoch to chain into"
@@ -1094,7 +1105,7 @@ mod tests {
10941105 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
10951106 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
10961107 let mut bundle =
1097- prove_continuation ( & elf_bytes, & [ ] , 8 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1108+ prove_continuation ( & elf_bytes, & [ ] , 3 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
10981109 assert ! ( !bundle. epochs. is_empty( ) ) ;
10991110 bundle. epochs [ 0 ] . reg_fini . pop ( ) ;
11001111 assert ! (
@@ -1182,7 +1193,7 @@ mod tests {
11821193 let _ = env_logger:: builder ( ) . is_test ( true ) . try_init ( ) ;
11831194 let elf_bytes = asm_elf_bytes ( "all_loadstore_32" ) ;
11841195 let mut bundle =
1185- prove_continuation ( & elf_bytes, & [ ] , 8 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
1196+ prove_continuation ( & elf_bytes, & [ ] , 3 , & ProofOptions :: default_test_options ( ) ) . unwrap ( ) ;
11861197 assert ! (
11871198 bundle. epochs. len( ) >= 2 ,
11881199 "need multiple epochs to exercise the binding"
0 commit comments