diff --git a/docs/analysis/cryptanalysis/report-acceptance-rule.md b/docs/analysis/cryptanalysis/report-acceptance-rule.md index ca83cdc54..26a520b93 100644 --- a/docs/analysis/cryptanalysis/report-acceptance-rule.md +++ b/docs/analysis/cryptanalysis/report-acceptance-rule.md @@ -255,6 +255,36 @@ it folds in act as fresh randomness on both sides: the closed form and the live the 50-program widening is running (gap-50.log). Also: the cheap 256-unit proxy did rank real tail programs, and 100064 sits 0.0032 above the 0.98 floor at 2^20, so the floor is live in the population tail, not idle. +### Widened live confirmation: the 20 lowest and 20 random (RUNNING, 2^24 nonces each) + +- box 1, 19:5x BST: `adv-live warps --program k --nonces 16777216 --threads 48 --diag 1` for the 20 lowest + stand-in-ratio seeds of the 16,337 (3664, 103378, 105756, 6610, 106474, 4765, 106700, 4346, 5245, + 101905, 1605, 1738, 105371, 6842, 838, 3387, 103265, 170, 106924, 107022; ratios 0.9945 to 0.9980), log + live-low20-16m.log. This row answers the defender's false-positive question: of the lowest 20 (plus the + five already confirmed), how many are NOT beyond 1.2x live. +- box 2, 19:5x BST: the same at 32 threads for 20 random accepted seeds (4107, 101710, 101886, 6132, + 105915, 5133, 1932, 2328, 3665, 101643, 1352, 100521, 106359, 4905, 102519, 101697, 106740, 327, 1180, + 101805; ratios 0.9994 to 0.9999), log live-random20-16m.log: the control for the correlation. + +| Seed | stand-in ratio (256 u) | live top 0.1% over window model | X_0.1% (X/f) | 6-sigma | hot set | +|---|---|---|---|---|---| +| 3664 (lowest) | 0.9945 | 1.31x BEYOND | | FLAGGED | clear | +| 4107 (random) | 0.9999 | 1.0004x within | | clear | clear | +| (rows land as the runs end) | | | | | | + +### Class v5 check of the exemplar (PENDING the lock; the defender's question) + +Does seed 100767 (id 9d68e6286fc817d4 under class v4) still read hot under class v5's state-derived +dataset? Tool: tools/attack/adv-accept-v5 (the same f8 harness over the vendored igneum-pow of branch +class-v5 at 25c8063f, read-only input; the program draw is the same since the state flag is not read by +the draw, generator 5; the dataset XORs the window's state leaf into every item). State stream: the Devnet +3 v5 pack's state.igsd1 (inputs/v5-dn3-epoch0-state.igsd1, 11 records, root 7e37a9fb...a311), the only +public state stream. Command, under the per-box sweep lock when my running confirmations end: +`adv-live-v5 warps --program 100767 --nonces 16777216 --threads 32 --diag 1 --state inputs/v5-dn3-epoch0-state.igsd1`. +Known-failed shape: `--plant const-item` on the same run must FLAG. Expectation to test: the hot items are +the era-stride images of small source values at site 6; the leaf changes the words every load folds in, so +the frequency of small source values may move; the images themselves do not. + ### Rule change 19:55 BST (box-hours honesty) The bounded class became 88 cores per box in total across all lanes: no new sweep starts except under the diff --git a/tools/attack/adv-accept/src/main.rs b/tools/attack/adv-accept/src/main.rs index 11df7f997..a94d61144 100644 --- a/tools/attack/adv-accept/src/main.rs +++ b/tools/attack/adv-accept/src/main.rs @@ -465,6 +465,8 @@ mod gap { pub name: &'static str, pub distinct: Vec, pub ratio: Vec, + /// share of the site's reads on word indices read 8 or more times over the units + pub rep8: Vec, pub distinct_mean_c: f64, pub saturated_c: u32, pub bias_max_c: u32, @@ -508,19 +510,33 @@ mod gap { let n = (units * LANES * ITERATIONS) as f64; let mut distinct = Vec::new(); let mut ratio = Vec::new(); + let mut rep8 = Vec::new(); let mut k = 0usize; for i in p.instrs.iter().filter(|i| i.op == Op::Load) { let mut v = std::mem::take(&mut sites[k]); v.sort_unstable(); - v.dedup(); + // the defender's statistic: the share of this site's reads landing on word indices read 8 or more + // times within the 2^20 evaluations (a uniform site on a 2^26 window repeats an index at most 2 or 3 times) + let (mut reads8, mut run, mut d) = (0u64, 0u64, 0u64); + for j in 0..v.len() { + if j == 0 || v[j] != v[j - 1] { + if run >= 8 { reads8 += run; } + run = 1; + d += 1; + } else { + run += 1; + } + } + if run >= 8 { reads8 += run; } let wsize = ((1u64 << ACCEPT_DATASET_LOG2) >> (i.win as u64).min(2)) as f64; - distinct.push(v.len() as u32); - ratio.push(v.len() as f64 / (n - n * n / (2.0 * wsize))); + distinct.push(d as u32); + ratio.push(d as f64 / (n - n * n / (2.0 * wsize))); + rep8.push(reads8 as f64 / v.len().max(1) as f64); k += 1; } let half = (ACCEPT_UNITS * LANES / 2) as u32; let bias_max_c = ones_c.iter().map(|&o| o.abs_diff(half)).max().unwrap_or(0); - Side { name, distinct, ratio, distinct_mean_c: ld_c as f64 / (ACCEPT_UNITS * LANES) as f64, saturated_c: sat_c, bias_max_c } + Side { name, distinct, ratio, rep8, distinct_mean_c: ld_c as f64 / (ACCEPT_UNITS * LANES) as f64, saturated_c: sat_c, bias_max_c } } pub fn run(p: &Program, day: u64, threads: usize, zero_plant: bool) { @@ -557,7 +573,7 @@ mod gap { for s in 0..c.ratio.len() { let d = c.ratio[s] - l.ratio[s]; if d.abs() > worst.0.abs() { worst = (d, s); } - println!("gap: site {s:2} closed ratio {:.4} ({}) live ratio {:.4} ({}) diff {:+.4}", c.ratio[s], c.distinct[s], l.ratio[s], l.distinct[s], d); + println!("gap: site {s:2} closed ratio {:.4} ({}) live ratio {:.4} ({}) diff {:+.4}; reads on indices read >= 8 times: closed {:.4}% live {:.4}%", c.ratio[s], c.distinct[s], l.ratio[s], l.distinct[s], d, c.rep8[s] * 100.0, l.rep8[s] * 100.0); } let cmin = c.ratio.iter().cloned().fold(9.0, f64::min); let lmin = l.ratio.iter().cloned().fold(9.0, f64::min);