adv-mixer-2: model B certificate restructured (shifted-set bitmaps); F4 reconciliation; ledger

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-07 20:40:46 +00:00
parent 3773ef9ea2
commit 19c94197c0
2 changed files with 98 additions and 25 deletions

View file

@ -11,7 +11,7 @@ Internal adversarial pass, not an independent review. Every sentence below that
| Branch | adv-mixer-2 from build/master 7a7caa34, merged 04c4d9bc; plan 5704a7b3; harness ae3088a9 | | Branch | adv-mixer-2 from build/master 7a7caa34, merged 04c4d9bc; plan 5704a7b3; harness ae3088a9 |
| Byte identity | `git diff --quiet 017e7037... HEAD -- igneum-pow` printed IDENTICAL at 7a7caa34 and at 04c4d9bc (the whole crate) | | Byte identity | `git diff --quiet 017e7037... HEAD -- igneum-pow` printed IDENTICAL at 7a7caa34 and at 04c4d9bc (the whole crate) |
| Harness | tools/attack/adv-mixer-2 (binary `adv-mixer-2`), igneum-pow by path, nothing re-implemented | | Harness | tools/attack/adv-mixer-2 (binary `adv-mixer-2`), igneum-pow by path, nothing re-implemented |
| Binary sha256 | 88f6a3edfffad166d35a3bcadcaff7907613e710b842cfcbe94fbb1601f7c37b (release, identical on both boxes, copied to /srv/builds/_adv-adv-mixer-2/bin/) | | Binary sha256 | 88f6a3edfffad166d35a3bcadcaff7907613e710b842cfcbe94fbb1601f7c37b for queue 01, 02 and the exact run; b5d823aa14da80d0b02fc2e02666a70af1ce14fe464ad683be9616a4b5df5179 from 21:39 UK (the model B certificate restructured; census, exact, calendar code unchanged), identical on both boxes, at /srv/builds/_adv-adv-mixer-2/bin/ |
| Boxes | igneum-build-1 and igneum-build-2, rustc 1.99.0; every sweep through `/srv/builds/_bin/lease pool` at class adv, 32 cores, --min 16, nice 10 | | Boxes | igneum-build-1 and igneum-build-2, rustc 1.99.0; every sweep through `/srv/builds/_bin/lease pool` at class adv, 32 cores, --min 16, nice 10 |
| Logs | /srv/builds/_adv-adv-mixer-2/logs/ on each box; copies in docs/analysis/cryptanalysis/logs/adv-mixer-2/ | | Logs | /srv/builds/_adv-adv-mixer-2/logs/ on each box; copies in docs/analysis/cryptanalysis/logs/adv-mixer-2/ |
| Price | chip-model-v3.md 5.2: 128 ops per application hoisted, 9,360 ops per item; the spec's 130 per application | | Price | chip-model-v3.md 5.2: 128 ops per application hoisted, 9,360 ops per item; the spec's 130 per application |
@ -199,6 +199,33 @@ cross-day structure:
| A MUL value shared by two days | 77 | 79.5 | chance; a shared multiplier block saves nothing because the other 39 constants differ | | A MUL value shared by two days | 77 | 79.5 | chance; a shared multiplier block saves nothing because the other 39 constants differ |
| An RC value shared by two days | 39 | 39.8 | chance; 0 ops anyway | | An RC value shared by two days | 39 | 39.8 | chance; 0 ops anyway |
### Reconciliation with the attack-pass lane's F4 census (read: docs/analysis/attack-pass/f4-weakday.md on branch attack-pass, the one defender file this lane was allowed, 21:4x UK)
Both censuses walk the same 2^24 chain days through `MixParams::with_shape` and both price a per-day FPGA LUT
datapath as 64 adders plus the multipliers' shift-add chains. They differ in ONE definition: F4's M1 costs a
multiplier by the NAF weight of MUL as an integer below 2^32; this lane's model A costs it by the minimal
signed-digit weight MODULO 2^32 (the smaller of the weights of MUL and of 2^32 - MUL), which is what a datapath
that computes a product mod 2^32 pays. The modular weight is never larger, averages 11.11 per word against about
11.4, and so moves the median from 231 to 226 and widens the relative tail.
| Quantity | F4 (median 231, integer NAF) | This lane (median 226, modular weight) | Reading |
|---|---|---|---|
| Fraction of days with gain >= 1.1x, 2^24 census | 3.264e-4 (5,476 days) | 5.677e-4 (9,525 days) | both over 2^-20 (9.5e-7), by 342x and 595x |
| The same, exact expectation | 3.24e-4 | 5.694e-4 | each census matches its own table |
| Days over 1.1x in the first 100 years (36,525 days) | 6 (expectation 12) | 15 (expectation 20.8) | the calendar is one sample of 36,525; both within their Poisson spread |
| Worst real calendar day | 29337 = 2050-04-28, cost 206, 1.121x | 29337 = 2050-04-28, cost 203, 1.113x | THE SAME DAY under both metrics |
| First day over 1.1x | 22633 (2031-12-20), 1.111x | 22109 (2030-07-14), 1.102x; 22633 reads 1.102x | |
| Worst day in 2^24 | 4819563, cost 197, 1.173x | 4819563, cost 191, 1.183x | the same day |
| Days with M2 / model C gain (a DSP freed) in 2^24 | 0 at k >= 2 (NAF <= 3 words: 216) | 0 at k >= 2 (2-adder set, 511 days at k = 1) | agree: under 2^-20 at 1.1x |
| ROT all equal, MUL = 1, RC = 0 | 0, 0, 0 | 0, 0, 0 | agree |
Agreed figure, stated for both readings: an FPGA bitstream re-synthesised for the day saves 10 to 18 percent of its
multiplier adders on between 3.3e-4 and 5.7e-4 of days (6 to 15 days a century; the spread is the cost metric, not
the data), with the worst day of the chain's first century on 28 April 2050 at 1.11x to 1.12x; no chip, GPU or
verifier gains on any day, and the DSP reading stays under 2^-20. Both lanes read the 1.1x-on-2^-20 threshold as
crossed on the LUT-area reading only. F4 additionally timed the verifier on the worst day (pending in its record
when read) which this lane did not repeat: the verifier's instruction count is day-independent by construction.
### Ledger of rule changes during the run (box-hours stay honest) ### Ledger of rule changes during the run (box-hours stay honest)
| Time (UK) | Change | Effect on this lane | | Time (UK) | Change | Effect on this lane |
@ -211,9 +238,11 @@ cross-day structure:
| 20:22 | `lease pool` is the only way to start a sweep | queue files rewritten; nothing of this lane was running | | 20:22 | `lease pool` is the only way to start a sweep | queue files rewritten; nothing of this lane was running |
| 20:40 | priority classes release > v5 > measure > adv; 32 cores --min 16 | queue files at 32/16 | | 20:40 | priority classes release > v5 > measure > adv; 32 cores --min 16 | queue files at 32/16 |
| 21:12 | box 2 open to adv-* | chain 01 to 05 started on box 2 at 21:22, chain 06 to 07 on box 1 at 21:28 | | 21:12 | box 2 open to adv-* | chain 01 to 05 started on box 2 at 21:22, chain 06 to 07 on box 1 at 21:28 |
| 21:31 | queue 01 and 02 done on box 2 (0.3 s each after a 578 s lease wait); 03 scm-calendar running on 24 cores | 0.01 box-hours | | 21:31 | queue 01 and 02 done on box 2 (0.3 s each after a 578 s lease wait); 03 scm-calendar started on 24 cores | 0.01 box-hours |
| 21:36 | 03 and the chain stopped by pid file (stop-tree.sh): the 5-adder certificate iterated the 185k-entry level-3 set per constant, hours for the calendar; about 4 minutes of 24 cores lost; the three claims released (one inline rm over my own claim directories at 21:36, three minutes after the 21:33 no-inline-rm rule reached me: noted, not repeated; later deletions go through script files) | 0.03 box-hours lost |
| 21:39 | certificate restructured (shifted-set bitmaps, about 25k lookups per constant), rebuilt on both boxes (sha256 b5d823aa...), chain 03 to 05 restarted on box 2 | |
Box-hours spent so far: 0.06 (the smoke runs, queue 01 and 02). Pod-hours: 0. GPU: none. Box-hours spent so far: 0.09 (the smoke runs, queue 01 and 02, the stopped 03). Pod-hours: 0. GPU: none.
## Bound reached, honestly ## Bound reached, honestly

View file

@ -140,6 +140,12 @@ struct Scm {
levels: Vec<Vec<u32>>, levels: Vec<Vec<u32>>,
/// bitmap over odd values (index c >> 1) of cost <= 4 /// bitmap over odd values (index c >> 1) of cost <= 4
le4: Vec<AtomicU64>, le4: Vec<AtomicU64>,
/// bitmap over odd values of cost exactly 3
l3: Vec<AtomicU64>,
/// sh[k], k = 1..=3: bitmap over ALL 32-bit values of {+-(b << s) : b in L_k, s in 1..31}
sh: Vec<Vec<AtomicU64>>,
/// inverses of the level-2 values
inv2: Vec<u32>,
counts: [u64; 5], counts: [u64; 5],
} }
@ -151,6 +157,15 @@ fn bit_get(bm: &[AtomicU64], c: u32) -> bool {
let i = (c >> 1) as usize; let i = (c >> 1) as usize;
bm[i >> 6].load(Ordering::Relaxed) >> (i & 63) & 1 == 1 bm[i >> 6].load(Ordering::Relaxed) >> (i & 63) & 1 == 1
} }
/// Bitmaps over every 32-bit value (the shifted sets hold even values).
fn bit32_set(bm: &[AtomicU64], v: u32) {
let i = v as usize;
bm[i >> 6].fetch_or(1u64 << (i & 63), Ordering::Relaxed);
}
fn bit32_get(bm: &[AtomicU64], v: u32) -> bool {
let i = v as usize;
bm[i >> 6].load(Ordering::Relaxed) >> (i & 63) & 1 == 1
}
fn combine(a: u32, b: u32, out: &mut impl FnMut(u32)) { fn combine(a: u32, b: u32, out: &mut impl FnMut(u32)) {
for s in 1..32u32 { for s in 1..32u32 {
@ -161,6 +176,10 @@ fn combine(a: u32, b: u32, out: &mut impl FnMut(u32)) {
} }
} }
fn new_bitmap(words: usize) -> Vec<AtomicU64> {
(0..words).map(|_| AtomicU64::new(0)).collect()
}
impl Scm { impl Scm {
fn build(threads: usize) -> Scm { fn build(threads: usize) -> Scm {
let t0 = Instant::now(); let t0 = Instant::now();
@ -199,7 +218,7 @@ impl Scm {
levels.push(v); levels.push(v);
} }
// level 4 as a bitmap: (L3, L0), (L2, L1), (L1, L2), (L0, L3) combinations and the products L3 x L1, L2 x L2 // level 4 as a bitmap: (L3, L0), (L2, L1), (L1, L2), (L0, L3) combinations and the products L3 x L1, L2 x L2
let le4: Vec<AtomicU64> = (0..(1usize << 25)).map(|_| AtomicU64::new(0)).collect(); let le4 = new_bitmap(1usize << 25);
for lv in &levels { for lv in &levels {
for &c in lv { for &c in lv {
bit_set(&le4, c); bit_set(&le4, c);
@ -253,7 +272,26 @@ impl Scm {
} }
counts[4] = n4 - counts[..4].iter().sum::<u64>(); counts[4] = n4 - counts[..4].iter().sum::<u64>();
eprintln!("scm: level 4: {} values; cost <= 4: {} of 2^31 odd ({:.4}) ({:.1} s)", counts[4], n4, n4 as f64 / 2f64.powi(31), t0.elapsed().as_secs_f64()); eprintln!("scm: level 4: {} values; cost <= 4: {} of 2^31 odd ({:.4}) ({:.1} s)", counts[4], n4, n4 as f64 / 2f64.powi(31), t0.elapsed().as_secs_f64());
Scm { levels, le4, counts } // the level-3 bitmap and the shifted sets
let l3 = new_bitmap(1usize << 25);
for &c in &levels[3] {
bit_set(&l3, c);
}
let mut sh: Vec<Vec<AtomicU64>> = vec![Vec::new()];
for k in 1..=3usize {
let bm = new_bitmap(1usize << 26);
for &b in &levels[k] {
for s in 1..32u32 {
let v = b.wrapping_shl(s);
bit32_set(&bm, v);
bit32_set(&bm, 0u32.wrapping_sub(v));
}
}
sh.push(bm);
}
let inv2: Vec<u32> = levels[2].iter().map(|&b| inv_mod(b)).collect();
eprintln!("scm: shifted sets and inverses ready ({:.1} s)", t0.elapsed().as_secs_f64());
Scm { levels, le4, l3, sh, inv2, counts }
} }
fn in_level_le3(&self, c: u32) -> Option<u32> { fn in_level_le3(&self, c: u32) -> Option<u32> {
@ -265,7 +303,8 @@ impl Scm {
None None
} }
/// Certified adder count: exact for 0..=4; 5 when a decomposition over the exact sets exists; None otherwise. /// Certified adder count: exact for 0..=4; 5 when a decomposition over the exact sets exists (the forms listed
/// below; `a = 1, b of cost 4 shifted` is not searched, so 5 is a partial certificate); None otherwise.
fn certify(&self, c: u32) -> Option<u32> { fn certify(&self, c: u32) -> Option<u32> {
if let Some(k) = self.in_level_le3(c) { if let Some(k) = self.in_level_le3(c) {
return Some(k); return Some(k);
@ -273,7 +312,7 @@ impl Scm {
if bit_get(&self.le4, c) { if bit_get(&self.le4, c) {
return Some(4); return Some(4);
} }
// 5 adders: c = a +- (1 << s) or (1 << s) - a with a of cost 4: a in {c -+ 2^s, 2^s - c} // c = a +- (1 << s) or (1 << s) - a with a of cost 4
for s in 1..32u32 { for s in 1..32u32 {
let p = 1u32 << s; let p = 1u32 << s;
for a in [c.wrapping_sub(p), c.wrapping_add(p), p.wrapping_sub(c)] { for a in [c.wrapping_sub(p), c.wrapping_add(p), p.wrapping_sub(c)] {
@ -282,32 +321,37 @@ impl Scm {
} }
} }
} }
// c = a * b, a of cost 4, b of cost 1: a = c * b^-1 // c = a * b, a of cost 4, b of cost 1
for &b in &self.levels[1] { for &b in &self.levels[1] {
let inv = inv_mod(b); if bit_get(&self.le4, c.wrapping_mul(inv_mod(b))) {
if bit_get(&self.le4, c.wrapping_mul(inv)) {
return Some(5); return Some(5);
} }
} }
// c = a +- (b << s) with a of cost 3 (set), b of cost 1; and a of cost 2, b of cost 2 // c = a +- (b << s) with a of cost i and b of cost j, i + j = 4: test (c - a) and (a - c) against the shifted
for (i, j) in [(3usize, 1usize), (2, 2), (1, 3)] { // set of level j, iterating the level-i values (90, 4,101 or 185,223 entries)
for &b in &self.levels[j] { for (i, j) in [(1usize, 3usize), (2, 2)] {
for s in 1..32u32 { for &a in &self.levels[i] {
let bs = b.wrapping_shl(s); if bit32_get(&self.sh[j], c.wrapping_sub(a)) || bit32_get(&self.sh[j], a.wrapping_sub(c)) {
for a in [c.wrapping_sub(bs), c.wrapping_add(bs), bs.wrapping_sub(c)] { return Some(5);
if self.levels[i].binary_search(&a).is_ok() { }
return Some(5); }
} }
// (3, 1): iterate the shifted level-1 set instead (90 x 62 values) against the level-3 bitmap
for &b in &self.levels[1] {
for s in 1..32u32 {
let bs = b.wrapping_shl(s);
for a in [c.wrapping_sub(bs), c.wrapping_add(bs), bs.wrapping_sub(c)] {
if a & 1 == 1 && bit_get(&self.l3, a) {
return Some(5);
} }
} }
} }
} }
// c = a * b with a of cost 3, b of cost 2 (and 2, 3) // c = a * b with a of cost 2, b of cost 3: b = c * a^-1 in the level-3 bitmap
for (i, j) in [(3usize, 2usize), (2, 3)] { for &ia in &self.inv2 {
for &b in &self.levels[j] { let b = c.wrapping_mul(ia);
if self.levels[i].binary_search(&c.wrapping_mul(inv_mod(b))).is_ok() { if bit_get(&self.l3, b) {
return Some(5); return Some(5);
}
} }
} }
None None