From 9545e9adf8aa28e9620faa2ac01861dc071c4e61 Mon Sep 17 00:00:00 2001 From: MauroFab Date: Mon, 6 Jul 2026 17:19:34 -0300 Subject: [PATCH] test(continuation): remove flaky privacy byte-scan test Remove `test_bundle_carries_no_touched_cell_values`. It scanned the serialized bundle for the 8-byte window `[marker, 0,0,0,0,0,0,0]` to assert no touched-cell value leaks, but the check is unsound and flaky: - The bundle is non-deterministic. Grinding derives the nonce via `into_par_iter().find_any()` (crypto/stark/src/grinding.rs), returning a different valid nonce per run, which reseeds Fiat-Shamir and changes every FRI query position and thus the opened bytes serialized. - The needle is produced abundantly by legitimate content, not just a leak. A leaked private cell value is a single byte (memory is `PagedMem`), so its only signature is `[b, 0x00 x7]` -- 1 distinct byte plus 7 forced zeros. Those trailing zeros are exactly what every small bincode `usize` length prefix (e.g. a table's column count) and small field value already has, so the window collides with honest proof bytes. Empirically, byte value 194 (a column count) appears as this window 18x per proof, every run; whether an arbitrary marker byte collides is pure luck of the trace/serialization layout. The "astronomically unlikely" premise is therefore wrong: it treats the marker as a rare 8-byte value when it is really a 1-byte value against 7 ubiquitous zeros. No marker choice fixes this given byte-granular cells, and a larger multi-byte marker cannot help either -- the old `boundary` serialization emitted each cell interleaved with its epoch/timestamp fields, so a multi-byte value would leak as scattered single-byte windows, not a contiguous searchable run. The real guarantee is already deterministic and enforced at compile time: `InitClaim`/`FiniClaim`/`CellBoundary` have their serde derives deliberately stripped (prover/src/tables/local_to_global.rs), so re-introducing the leak is a compile error rather than something a runtime byte-scan must catch. The removed test added flake without adding coverage the compiler does not already provide. --- prover/src/continuation.rs | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/prover/src/continuation.rs b/prover/src/continuation.rs index 0f2b51168..3ef703514 100644 --- a/prover/src/continuation.rs +++ b/prover/src/continuation.rs @@ -1540,44 +1540,6 @@ mod tests { )); } - // Privacy regression for the touched-cell value leak. Pre-fix, `EpochProof.boundary` - // serialized each touched cell's `init.value`/`fini.value` as a u64, so a private - // byte `b` appeared in the bundle as the 8-byte window `[b,0,0,0,0,0,0,0]`. The fix - // drops `boundary` from the bundle entirely (only the value-free `touched_page_bases` - // ships), so those windows must be gone. We mark distinctive TOUCHED byte values - // (0xC7..) — their u64-LE encodings are astronomically unlikely to occur as any honest - // field/count/root byte-run — and assert none appear in the serialized bundle. (The - // committed `public_output` serializes bytes RAW, not as u64s, so it cannot produce - // these windows even for the committed markers.) - #[test] - fn test_bundle_carries_no_touched_cell_values() { - let _ = env_logger::builder().is_test(true).try_init(); - let elf_bytes = asm_elf_bytes("test_private_input_xpage"); - let mut input: Vec = (0u8..16).collect(); - let markers = [0xC7u8, 0xC8, 0xC9]; - input[4] = markers[0]; - input[5] = markers[1]; - input[6] = markers[2]; - - let bundle = - prove_continuation(&elf_bytes, &input, 2, &ProofOptions::default_test_options()) - .unwrap(); - let bytes = bincode::serialize(&bundle).unwrap(); - for m in markers { - let needle = (m as u64).to_le_bytes(); // [m,0,0,0,0,0,0,0] - assert!( - !bytes.windows(8).any(|w| w == needle), - "byte 0x{m:02X} appears as a u64 in the bundle — a touched-cell value leaked" - ); - } - // Sanity: still verifies from bundle + ELF alone. - assert!( - verify_continuation(&elf_bytes, &bundle, &ProofOptions::default_test_options()) - .unwrap() - .is_some() - ); - } - // Multi-page private input: the program reads private input across TWO pages // (page 0 for the length, page 1 for the committed bytes), so the run touches two // private pages → `num_private_input_pages >= 2` and two NON-preprocessed