Counter ASIC 3.0 gates (hash): ledger AP-F8-1, the census at ddacfbd3 (0 lossy sites, 0 exhaustions, mean attempt 1.998)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
7b5cc8b0cf
commit
b87afe9ee8
3 changed files with 185 additions and 13 deletions
|
|
@ -2454,6 +2454,6 @@ AP-F8-2 (7 October 2026, 13:xx UTC, the attack-pass lane on sub-version 2 at 07a
|
|||
|
||||
AP-F8-2, the exhaustion half, FIXED-AND-PASSED at 8bdcbdd8 on the attack-pass lane's 10^6 chain-shaped seeds through the chain path (14:03:53Z): 0 exhausted, 0 panics, 4 seeds past attempt 31 (three at 32, one at 35; 4e-6, inside (2/3)^32), max attempt 35, no seed at the last resort; r = 0.67, mean 2.0 attempts per seed.
|
||||
|
||||
Sub-version 2's hot-set half did not read green: F8's 64-seed gate at 39 of 64 had 8 over 1.2x of the window model (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p34 1.25x; p4, p8, p10 unattributed at 1.22x to 1.50x). AP-F8-3 (7 October 2026, 14:0x UTC, the hash lane): the cause of the whole residual is that `accept.rs` never executed the latency-shadow block. Its interpreter (`run_unit`) was written for class v2 and v3 and ran the 64 base instructions per iteration and nothing after instruction 63, while the hash (`verify.rs`, the kernels) runs the shadow 27 times at the end of every iteration; so every dynamic acceptance test (c), (c') judged a class v4 program the chain never hashes. Main's word (14:1x UTC): 0.3.21 ships object byte 5 (sub-version 1); sub-version 3 is 0.3.22's and starts with this fix. Sub-version 3, first commit: `run_unit` executes the shadow block after instruction 63 of every iteration, `reps` times with the iteration's sel, as the hash does; the test `acceptance_executes_the_shadow_block_as_the_verifier_does` pins the acceptance's execution to `verify.rs` on the devnet epoch-0 program and the six test eras (the output bit counts over the 64 units equal, and different with the shadow stripped), so the two paths cannot diverge silently again; `PROGRAM_SUBVERSION_V4` = 3 (a new acceptance verdict is a new stream); the devnet epoch-0 seed still accepts at attempt 1, so its program and fingerprint are sub-version 2's (e370fb2080b7dbb1) under the new id a785001687d8688a (the must-differ set: c120d7963abdcd96, 1a4230699a6b9c60, a788661687db4bb3); the packs zip sha256 4f2445c50c58d76a5544023492d8b858d0b07c5e372d31f9c90c4ce51f829154. The class behind p23, localised from its program and reproduced in the acceptance's own execution: site 7 (instruction 38) reads r6 after 25 `mulhi r6`, 31 `or r6 |= r4`, 35 `xor r6 ^= r4`, which is `r6 & ~r4`, an AND mask the lineage rule counted as fresh because the xor's operand is the or's; over 2^20 evaluations on the closed-form words site 7 reads 874,953 distinct word indices against about 1,046,500 for every other site (0.84 of uniform; 2.2 s on one core), over 2^24 8,979,203 against about 16,260,000 (0.55; 35 s). The second sub-version 3 commit (held, prepared in the worktree) is a per-site distinct-index ratio against the uniform expectation of the site's window, its threshold set from the clean seeds' spread and its sample size from the cost line above; the dynamic bounds as first specified (a most-repeated-value bound at 16,384 and a distinct floor at 2^19.5 over 2^20) do not reach p23 and are not committed. The crate suite at ddacfbd3 on box 2 (route "box 2 for class suite, priority normal", rc 0, 41 s, 14:20Z): 63 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 102 passed, 0 failed, the agreement test included. F8's final 64-seed table on sub-version 2 (the attack-pass lane, 14:2x UTC): 9 over 1.2x (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p56 2.01x, p10 1.50x, p8 1.38x, p34 1.25x, p4 1.22x), the 55 clean seeds at 0.9915x to 1.144x; the 256-item bucket entropy over the window separates the strong four only (0.637 to 0.974 against a clean minimum of 0.9865 over 848 site rows), so the threshold of the second commit's distinct-index ratio comes from a run of that ratio on the 55 clean seeds at 2^20.
|
||||
Sub-version 2's hot-set half did not read green: F8's 64-seed gate at 39 of 64 had 8 over 1.2x of the window model (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p34 1.25x; p4, p8, p10 unattributed at 1.22x to 1.50x). AP-F8-3 (7 October 2026, 14:0x UTC, the hash lane): the cause of the whole residual is that `accept.rs` never executed the latency-shadow block. Its interpreter (`run_unit`) was written for class v2 and v3 and ran the 64 base instructions per iteration and nothing after instruction 63, while the hash (`verify.rs`, the kernels) runs the shadow 27 times at the end of every iteration; so every dynamic acceptance test (c), (c') judged a class v4 program the chain never hashes. Main's word (14:1x UTC): 0.3.21 ships object byte 5 (sub-version 1); sub-version 3 is 0.3.22's and starts with this fix. Sub-version 3, first commit: `run_unit` executes the shadow block after instruction 63 of every iteration, `reps` times with the iteration's sel, as the hash does; the test `acceptance_executes_the_shadow_block_as_the_verifier_does` pins the acceptance's execution to `verify.rs` on the devnet epoch-0 program and the six test eras (the output bit counts over the 64 units equal, and different with the shadow stripped), so the two paths cannot diverge silently again; `PROGRAM_SUBVERSION_V4` = 3 (a new acceptance verdict is a new stream); the devnet epoch-0 seed still accepts at attempt 1, so its program and fingerprint are sub-version 2's (e370fb2080b7dbb1) under the new id a785001687d8688a (the must-differ set: c120d7963abdcd96, 1a4230699a6b9c60, a788661687db4bb3); the packs zip sha256 4f2445c50c58d76a5544023492d8b858d0b07c5e372d31f9c90c4ce51f829154. The class behind p23, localised from its program and reproduced in the acceptance's own execution: site 7 (instruction 38) reads r6 after 25 `mulhi r6`, 31 `or r6 |= r4`, 35 `xor r6 ^= r4`, which is `r6 & ~r4`, an AND mask the lineage rule counted as fresh because the xor's operand is the or's; over 2^20 evaluations on the closed-form words site 7 reads 874,953 distinct word indices against about 1,046,500 for every other site (0.84 of uniform; 2.2 s on one core), over 2^24 8,979,203 against about 16,260,000 (0.55; 35 s). The second sub-version 3 commit (held, prepared in the worktree) is a per-site distinct-index ratio against the uniform expectation of the site's window, its threshold set from the clean seeds' spread and its sample size from the cost line above; the dynamic bounds as first specified (a most-repeated-value bound at 16,384 and a distinct floor at 2^19.5 over 2^20) do not reach p23 and are not committed. The crate suite at ddacfbd3 on box 2 (route "box 2 for class suite, priority normal", rc 0, 41 s, 14:20Z): 63 lib + 7 derive + 4 mixer + 19 packs + 2 recheck + 7 scratch = 102 passed, 0 failed, the agreement test included. F8's final 64-seed table on sub-version 2 (the attack-pass lane, 14:2x UTC): 9 over 1.2x (p23 4.82x, p19 3.32x, p15 2.57x, p18 2.50x, p56 2.01x, p10 1.50x, p8 1.38x, p34 1.25x, p4 1.22x), the 55 clean seeds at 0.9915x to 1.144x; the 256-item bucket entropy over the window separates the strong four only (0.637 to 0.974 against a clean minimum of 0.9865 over 848 site rows), so the threshold of the second commit's distinct-index ratio comes from a run of that ratio on the 55 clean seeds at 2^20. The static census at ddacfbd3 (box 2, 14:22Z, 4,096 chain-shaped seeds plus F8's p1 to p3): 4,099 programs, 0 lossy-sourced load sites of 65,584, 0 exhaustions, the accepted attempt geometric as before (1,328 at attempt 0, 917, 622, 409, 284, ... one each at 16 and 17; mean 1.998, max 17), so the shadow-executed verdicts move a handful of seeds' attempts and nothing else; the devnet epoch-0 seed at attempt 1, id a785001687d8688a.
|
||||
|
||||
Owed (recorded, not run, by Josh's word): G2 (the CPU verifier on 1,024 hashes per card) on the amended stream; G3 (the Metal fuzz, edge, stats and determinism runs) on the amended stream; the hash-rate ladder re-measure on the M5 Max and the RTX 5090 (the amendment changes the base program's source draws, not the op mix or the load count, so the latency-bound rows of `docs/analysis/latency-shadow-2026-10-06.md` are expected to hold within their spread; unmeasured); AMD (the RX 9070 XT, PC 1); the 2019-class verifier core (O-1.14); F8's phase E (the 64-seed dynamic census) on the amended stream, which is the attack-pass lane's and the test of the per-op table. The row reads FIXED-AND-PASSED only after phase E passes against the amended class.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,21 @@ use crate::verify::{dataset_elem, fold_words, load_index, splitmix32, ScratchMod
|
|||
|
||||
/// Units (32-lane warps) the dynamic test interprets.
|
||||
pub const ACCEPT_UNITS: usize = 64;
|
||||
|
||||
/// Class v4 sub-version 3 (AP-F8-1's low-entropy-band class, 7 October 2026, the attack-pass gate's numbers through
|
||||
/// main): rule (c''), two parts, both keyed on the class v4 shape. (A) The distinct-index bound: over
|
||||
/// [`ACCEPT_UNITS_DISTINCT_V4`] units (2^20 evaluations per site) the count of DISTINCT dataset indices a load site
|
||||
/// reads must be at least [`MIN_DISTINCT_INDICES_V4`] (2^19.5): a site with k bits of index entropy reads about 2^k
|
||||
/// distinct, and the gate's 1.2x at the top 0.1 percent corresponds to about 18.5 bits (k = 12 reads about 45x, 15
|
||||
/// 6.7x, 17 2.4x, 18 about 1.2x). (B) The most-repeated-value bound: over the (c) units' 16,384 evaluations no load
|
||||
/// site reads one source value [`MAX_SOURCE_REPEAT_V4`] times or more (a single item trips the gate alone at about
|
||||
/// 78 repeats per 16,384; a uniform source repeats at most 2 or 3). A candidate failing either is rejected and the
|
||||
/// next attempt drawn under the 256 cap and the last resort.
|
||||
pub const ACCEPT_UNITS_DISTINCT_V4: usize = 4096;
|
||||
/// (A): the floor on distinct indices per site over 2^20 evaluations, 2^19.5 rounded.
|
||||
pub const MIN_DISTINCT_INDICES_V4: u32 = 741_455;
|
||||
/// (B): the most repeated source value per site over the (c) units' 16,384 evaluations is rejected at this count.
|
||||
pub const MAX_SOURCE_REPEAT_V4: u32 = 8;
|
||||
/// Hashes the dynamic test evaluates: 2,048.
|
||||
pub const ACCEPT_HASHES: usize = ACCEPT_UNITS * LANES;
|
||||
/// Domain tag of the base-nonce stream.
|
||||
|
|
@ -63,6 +78,12 @@ pub enum Reject {
|
|||
/// (c'), class v4 sub-version 2 (AP-F8-1): the load at `site` read a source value of 0 or all ones in `count` of
|
||||
/// its 16,384 evaluations (64 units x 32 lanes x 8 iterations); limit [`MAX_SATURATED`] - 1, the same 1 percent as (c).
|
||||
SaturatedSource { site: u8, count: u32 },
|
||||
/// (c'') (B), class v4 sub-version 3: the load at `site` read the value `value` in `count` of its 16,384 (c)
|
||||
/// evaluations (limit [`MAX_SOURCE_REPEAT_V4`] - 1): one constant upstream that the lineage rule cannot see.
|
||||
RepeatedSource { site: u8, value: u32, count: u32 },
|
||||
/// (c'') (A), class v4 sub-version 3: the load at `site` read only `distinct` distinct dataset indices over 2^20
|
||||
/// evaluations (floor [`MIN_DISTINCT_INDICES_V4`]): a low-entropy index band (F8's p23, p15, p18, p19).
|
||||
LowEntropySite { site: u8, distinct: u32 },
|
||||
/// (c): output bit `bit` was set in `ones` of 2,048 hashes.
|
||||
OutputBias { bit: u8, ones: u32 },
|
||||
/// (c): the distinct-address sum was `sum`.
|
||||
|
|
@ -82,6 +103,8 @@ impl std::fmt::Display for Reject {
|
|||
}
|
||||
Reject::Saturated { count } => write!(f, "(c) {count} of 16384 final register values saturated (limit 163)"),
|
||||
Reject::UnfreshLoadSource { instr, reg } => write!(f, "(a') load at {instr} reads r{reg}, not fresh by dataflow in the loop's steady state (class v4 sub-version 2)"),
|
||||
Reject::RepeatedSource { site, value, count } => write!(f, "(c'') load site {site} read the value {value:#010x} in {count} of 16384 evaluations (limit {})", MAX_SOURCE_REPEAT_V4 - 1),
|
||||
Reject::LowEntropySite { site, distinct } => write!(f, "(c'') load site {site} read {distinct} distinct indices over {} evaluations (floor {})", ACCEPT_UNITS_DISTINCT_V4 * LANES * ITERATIONS, MIN_DISTINCT_INDICES_V4),
|
||||
Reject::SaturatedSource { site, count } => write!(f, "(c') load site {site} read a saturated source value in {count} of 16384 evaluations (limit 163)"),
|
||||
Reject::OutputBias { bit, ones } => write!(f, "(c) output bit {bit} set in {ones} of 2048 hashes"),
|
||||
Reject::DistinctAddresses { sum } => {
|
||||
|
|
@ -144,6 +167,62 @@ fn check_injecting_writes(instrs: &[Instr]) -> Result<(), Reject> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// The most repeated value of `values` (sorted in place) and that value.
|
||||
fn most_repeated(values: &mut [u32]) -> (u32, u32) {
|
||||
values.sort_unstable();
|
||||
let (mut best, mut best_v, mut run) = (0u32, 0u32, 0u32);
|
||||
for i in 0..values.len() {
|
||||
run = if i > 0 && values[i] == values[i - 1] { run + 1 } else { 1 };
|
||||
if run > best {
|
||||
best = run;
|
||||
best_v = values[i];
|
||||
}
|
||||
}
|
||||
(best, best_v)
|
||||
}
|
||||
|
||||
/// (c'') (A), class v4 sub-version 3: the program interpreted for [`ACCEPT_UNITS_DISTINCT_V4`] units on the seed's
|
||||
/// acceptance stream (the (c) units first) with every load's dataset index recorded per site; a site reading fewer
|
||||
/// than [`MIN_DISTINCT_INDICES_V4`] distinct indices over its 2^20 evaluations is a low-entropy band (a constant or a
|
||||
/// forced equality upstream that the lineage rule cannot see) and the candidate is rejected.
|
||||
pub fn check_distinct_indices_v4(p: &Program) -> Result<(), Reject> {
|
||||
for (site, &distinct) in distinct_indices_v4(p, ACCEPT_UNITS_DISTINCT_V4)?.iter().enumerate() {
|
||||
if distinct < MIN_DISTINCT_INDICES_V4 {
|
||||
return Err(Reject::LowEntropySite { site: site as u8, distinct });
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The distinct dataset word indices every load site reads over `units` units of the seed's acceptance stream on
|
||||
/// the closed-form words (the sample caps the count near `units x 32 x 8`, so a site's index entropy is read only
|
||||
/// below about log2 of that).
|
||||
pub fn distinct_indices_v4(p: &Program, units: usize) -> Result<Vec<u32>, Reject> {
|
||||
let loads = p.loads_per_hash();
|
||||
let sites = loads / ITERATIONS;
|
||||
let mut acc = Acc {
|
||||
sources: None,
|
||||
indices: Some(vec![Vec::with_capacity(units * LANES * ITERATIONS); sites]),
|
||||
sat_source: vec![0; sites],
|
||||
and_acc: [u32::MAX; 8],
|
||||
or_acc: [0; 8],
|
||||
saturated: 0,
|
||||
bit_ones: [0; 64],
|
||||
distinct_sum: 0,
|
||||
};
|
||||
let mut lane_addrs = vec![0u32; LANES * loads];
|
||||
for (unit, &base) in accept_base_nonces_n(&p.seed, units).iter().enumerate() {
|
||||
run_unit(p, unit, base, &mut acc, &mut lane_addrs)?;
|
||||
}
|
||||
let mut out = Vec::with_capacity(sites);
|
||||
for ix in acc.indices.take().unwrap().iter_mut() {
|
||||
ix.sort_unstable();
|
||||
ix.dedup();
|
||||
out.push(ix.len() as u32);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Whether `class` is the class v4 shape (the 256-instruction shadow block over the class v3 base, the pass count and
|
||||
/// the era set aside): the shape the sub-version 2 rules (a') and (c') apply to, on every draw path.
|
||||
pub fn is_class_v4_shape(class: &LoadClass) -> bool {
|
||||
|
|
@ -204,17 +283,21 @@ pub fn check_static(p: &Program) -> Result<(), Reject> {
|
|||
|
||||
/// The 64 base nonces of the dynamic test for seed words `seed`.
|
||||
pub fn accept_base_nonces(seed: &[u32; 8]) -> [u32; ACCEPT_UNITS] {
|
||||
let v = accept_base_nonces_n(seed, ACCEPT_UNITS);
|
||||
let mut out = [0u32; ACCEPT_UNITS];
|
||||
out.copy_from_slice(&v);
|
||||
out
|
||||
}
|
||||
|
||||
/// The first `n` base nonces of the seed's acceptance stream (the (c) units are the first [`ACCEPT_UNITS`]).
|
||||
pub fn accept_base_nonces_n(seed: &[u32; 8], n: usize) -> Vec<u32> {
|
||||
let mut b = Vec::with_capacity(ACCEPT_TAG.len() + 32);
|
||||
b.extend_from_slice(ACCEPT_TAG);
|
||||
for w in seed {
|
||||
b.extend_from_slice(&w.to_le_bytes());
|
||||
}
|
||||
let mut rng = SplitMix64::new(fnv1a64(&b));
|
||||
let mut out = [0u32; ACCEPT_UNITS];
|
||||
for o in out.iter_mut() {
|
||||
*o = (rng.next() as u32) & !31;
|
||||
}
|
||||
out
|
||||
(0..n).map(|_| (rng.next() as u32) & !31).collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
@ -224,6 +307,10 @@ fn mulhi32(a: u32, b: u32) -> u32 {
|
|||
|
||||
/// Accumulators of the dynamic test over the 64 units.
|
||||
struct Acc {
|
||||
/// (c'') (B): every load's source value per site, recorded when present.
|
||||
sources: Option<Vec<Vec<u32>>>,
|
||||
/// (c'') (A): every load's dataset index per site, recorded when present (the distinct-index pass only).
|
||||
indices: Option<Vec<Vec<u32>>>,
|
||||
/// (c'): per load site (the load's index within the iteration), how many of its evaluations read a source value
|
||||
/// of 0 or all ones (class v4 sub-version 2; counted for every class, judged for class v4 only).
|
||||
sat_source: Vec<u32>,
|
||||
|
|
@ -359,7 +446,13 @@ fn run_unit(p: &Program, unit: usize, base: u32, acc: &mut Acc, lane_addrs: &mut
|
|||
for lane in 0..LANES {
|
||||
let v = r[a][lane];
|
||||
acc.sat_source[site] += (v == 0 || v == u32::MAX) as u32;
|
||||
if let Some(src) = acc.sources.as_mut() {
|
||||
src[site].push(v);
|
||||
}
|
||||
idx[lane] = load_index(era.as_ref(), ins, v, mask, ACCEPT_DATASET_LOG2) & align;
|
||||
if let Some(ix) = acc.indices.as_mut() {
|
||||
ix[site].push(idx[lane]);
|
||||
}
|
||||
}
|
||||
if idx.iter().all(|&x| x == idx[0]) {
|
||||
return Err(Reject::LaneConstantSite { iteration: it as u8, instr: k as u8, unit: unit as u8 });
|
||||
|
|
@ -437,7 +530,18 @@ fn run_unit(p: &Program, unit: usize, base: u32, acc: &mut Acc, lane_addrs: &mut
|
|||
/// Part (c).
|
||||
pub fn check_dynamic(p: &Program) -> Result<AcceptReport, Reject> {
|
||||
let loads = p.loads_per_hash();
|
||||
let mut acc = Acc { sat_source: vec![0; loads / ITERATIONS], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let v4 = is_class_v4_shape(&p.class);
|
||||
let sites = loads / ITERATIONS;
|
||||
let mut acc = Acc {
|
||||
sources: if v4 { Some(vec![Vec::with_capacity(ACCEPT_HASHES * ITERATIONS); sites]) } else { None },
|
||||
indices: None,
|
||||
sat_source: vec![0; sites],
|
||||
and_acc: [u32::MAX; 8],
|
||||
or_acc: [0; 8],
|
||||
saturated: 0,
|
||||
bit_ones: [0; 64],
|
||||
distinct_sum: 0,
|
||||
};
|
||||
let mut lane_addrs = vec![0u32; LANES * loads];
|
||||
for (unit, &base) in accept_base_nonces(&p.seed).iter().enumerate() {
|
||||
run_unit(p, unit, base, &mut acc, &mut lane_addrs)?;
|
||||
|
|
@ -455,10 +559,19 @@ pub fn check_dynamic(p: &Program) -> Result<AcceptReport, Reject> {
|
|||
// whatever delivered the saturation (an or-written value, a rotate of one, a load after a saturated load); the
|
||||
// source rule of the draw removes the writers it can see and this count catches every delivery. Keyed on the
|
||||
// class v4 shape as the draw's rule is, so v2 and v3 verdicts do not move.
|
||||
if is_class_v4_shape(&p.class) {
|
||||
if v4 {
|
||||
if let Some((site, &count)) = acc.sat_source.iter().enumerate().find(|(_, &c)| c >= MAX_SATURATED) {
|
||||
return Err(Reject::SaturatedSource { site: site as u8, count });
|
||||
}
|
||||
// (c'') (B) on the (c) units' own source values
|
||||
for (site, values) in acc.sources.take().unwrap().iter_mut().enumerate() {
|
||||
let (best, best_v) = most_repeated(values);
|
||||
if best >= MAX_SOURCE_REPEAT_V4 {
|
||||
return Err(Reject::RepeatedSource { site: site as u8, value: best_v, count: best });
|
||||
}
|
||||
}
|
||||
// (c'') (A) on 2^20 evaluations per site, the chosen candidate only (after every other test)
|
||||
check_distinct_indices_v4(p)?;
|
||||
}
|
||||
let half = (ACCEPT_HASHES / 2) as u32;
|
||||
let mut bias_max = 0u32;
|
||||
|
|
@ -513,7 +626,7 @@ mod tests {
|
|||
let ds = DatasetSource::from_key(p.seed, DatasetMode::ClosedForm, ACCEPT_DATASET_LOG2);
|
||||
let bases = accept_base_nonces(&p.seed);
|
||||
let loads = p.loads_per_hash();
|
||||
let mut acc = Acc { sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut acc = Acc { sources: None, indices: None, sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut la = vec![0u32; LANES * loads];
|
||||
let mut ones = [0u32; 64];
|
||||
for (u, &b) in bases.iter().enumerate() {
|
||||
|
|
@ -548,7 +661,7 @@ mod tests {
|
|||
assert_eq!(p.shadow_reps(), 27);
|
||||
let ds = DatasetSource::from_key(p.seed, DatasetMode::ClosedForm, ACCEPT_DATASET_LOG2);
|
||||
let bases = accept_base_nonces(&p.seed);
|
||||
let mut acc = Acc { sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut acc = Acc { sources: None, indices: None, sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut la = vec![0u32; LANES * p.loads_per_hash()];
|
||||
let mut ones = [0u32; 64];
|
||||
for (u, &b) in bases.iter().enumerate() {
|
||||
|
|
@ -560,9 +673,10 @@ mod tests {
|
|||
}
|
||||
}
|
||||
assert_eq!(acc.bit_ones, ones, "the acceptance's execution of a class v4 program (shadow block included) matches the verifier's hashes");
|
||||
// and the same program with its shadow stripped hashes differently: the shadow is executed, not skipped
|
||||
let mut bare = p.clone();
|
||||
bare.shadow.clear();
|
||||
let mut acc2 = Acc { sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut acc2 = Acc { sources: None, indices: None, sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
for (u, &b) in bases.iter().enumerate() {
|
||||
let _ = run_unit(&bare, u, b, &mut acc2, &mut la);
|
||||
}
|
||||
|
|
@ -578,7 +692,7 @@ mod tests {
|
|||
let p = candidate(&s, s.as_bytes(), 0);
|
||||
let ds = DatasetSource::from_key(p.seed, DatasetMode::ClosedForm, ACCEPT_DATASET_LOG2);
|
||||
let bases = accept_base_nonces(&p.seed);
|
||||
let mut acc = Acc { sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut acc = Acc { sources: None, indices: None, sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut la = vec![0u32; LANES * p.loads_per_hash()];
|
||||
let mut ones = [0u32; 64];
|
||||
let mut any = false;
|
||||
|
|
@ -621,7 +735,7 @@ mod tests {
|
|||
let p = generate_class("igneum-genesis", LoadClass::hot(96, 4));
|
||||
let words = p.hot_words();
|
||||
let loads = p.loads_per_hash();
|
||||
let mut acc = Acc { sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut acc = Acc { sources: None, indices: None, sat_source: vec![0; 64], and_acc: [u32::MAX; 8], or_acc: [0; 8], saturated: 0, bit_ones: [0; 64], distinct_sum: 0 };
|
||||
let mut la = vec![0u32; LANES * loads];
|
||||
let mut buckets = [0u64; 16];
|
||||
let mut hot_count = 0u64;
|
||||
|
|
|
|||
|
|
@ -1868,6 +1868,64 @@ mod tests {
|
|||
/// AP-F8-2: the class v4 draw is total. The last resort turns real (a')-rejected candidates into programs every
|
||||
/// load of which reads a fresh register, the cap is 256 for the v4 shape and 32 for every other class, and the
|
||||
/// chain path of a seed whose first candidates are rejected yields a program without a panic.
|
||||
/// Class v4 sub-version 3 (prepared): the repeated-source pass (c'') on the value-constant class the lineage rule
|
||||
/// cannot see. Known-failed shapes: F8's p23 (a forced-equal pair feeding `xor` at instruction 0, site 1 reads the
|
||||
/// zero), p15 (a rotated zero at site 12) and p18 (one word carried load to load, site 14), the chain-shaped seeds
|
||||
/// and eras of the attack-pass harness, at the attempt sub-version 2 accepted; the known-pass: the devnet epoch-0
|
||||
/// program. The timing line per sample size is the draw-cost figure of the ledger.
|
||||
#[test]
|
||||
fn class_v4_repeated_source_pass_rejects_the_value_constant_class() {
|
||||
let hx = |h: &str| -> Vec<u8> { (0..h.len()).step_by(2).map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap()).collect() };
|
||||
// the exact candidates F8 measured (the attempts sub-version 2's shadow-less acceptance chose: 4, 3 and 2)
|
||||
let f8 = [
|
||||
("p23", 4u32, "01aa1485fcb5d59223ca40602086e618277decf440188c5a55898f635f7be34f", "00951c99e7ef952fd52611b8de7c07cc705cdec9e129a31a19d696c99909f052"),
|
||||
("p15", 3u32, "65d1bc6af696d940f57d7d04469e29dd7c1eb69055619045911121b1dcec8b69", "e6b5324458cfd3c3326baed2aabd49cc361a17bc5efdde9e291daddad21ec5c7"),
|
||||
("p18", 2u32, "aa4f71160ecbb87bde40eb5e5731ae84346adcd48b533b26f35c0925d7f40784", "82bb5b72dfbeb137505c844e103906c8b93aacc7f61d3fce1b24d8c0e149c8e2"),
|
||||
];
|
||||
for (name, attempt, epoch, era) in f8 {
|
||||
let (e, r) = (hx(epoch), hx(era));
|
||||
let label = format!("igneum-epoch/{epoch}");
|
||||
let class = LoadClass::era(V4_CLASS, &r, &V3_ALLOWED);
|
||||
let mut p = candidate_class(&label, &e, attempt, class);
|
||||
p.generator = GENERATOR_VERSION_V4;
|
||||
p.era_bytes = Some(r.clone());
|
||||
let t0 = std::time::Instant::now();
|
||||
let v = crate::accept::check_dynamic(&p).map(|_| ());
|
||||
let ms = t0.elapsed().as_secs_f64() * 1e3;
|
||||
println!("{name}: attempt {} id {:016x}: dynamic check {:?} in {ms:.1} ms", p.attempt, p.program_id(), v.as_ref().err().map(|x| x.to_string()));
|
||||
assert!(matches!(v, Err(crate::accept::Reject::RepeatedSource { .. }) | Err(crate::accept::Reject::LowEntropySite { .. })), "{name}: the low-entropy class is rejected by (c'')");
|
||||
}
|
||||
let g = hx("edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07");
|
||||
let p1 = generate_era("igneum-epoch/edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07", &g, V4_CLASS, &g, &V3_ALLOWED);
|
||||
let t0 = std::time::Instant::now();
|
||||
let v = crate::accept::check_dynamic(&p1).map(|_| ());
|
||||
println!("p1: attempt {} id {:016x}: dynamic check {:?} in {:.1} ms (the (c) units with (B), then (A) over 2^20 evaluations per site)", p1.attempt, p1.program_id(), v.as_ref().err().map(|x| x.to_string()), t0.elapsed().as_secs_f64() * 1e3);
|
||||
assert!(v.is_ok(), "the devnet epoch-0 program passes the dynamic check");
|
||||
let t0 = std::time::Instant::now();
|
||||
let _ = crate::accept::check_distinct_indices_v4(&p1);
|
||||
println!("p1: (A) alone in {:.1} ms", t0.elapsed().as_secs_f64() * 1e3);
|
||||
}
|
||||
|
||||
/// Diagnostic (AP-F8-1, p23): the distinct word indices per site on the closed-form words at 2^20 and 2^24
|
||||
/// evaluations, for the program F8 measured (attempt 1, id d64dbc675f13be9e): site 7 (instruction 38) reads
|
||||
/// r6 = (mulhi(..) | r4) ^ r4 = r6 & ~r4, an andnot idiom the lineage rule counts as fresh.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn diag_p23_distinct_indices_per_site() {
|
||||
let hx = |h: &str| -> Vec<u8> { (0..h.len()).step_by(2).map(|i| u8::from_str_radix(&h[i..i + 2], 16).unwrap()).collect() };
|
||||
let e = hx("01aa1485fcb5d59223ca40602086e618277decf440188c5a55898f635f7be34f");
|
||||
let r = hx("00951c99e7ef952fd52611b8de7c07cc705cdec9e129a31a19d696c99909f052");
|
||||
let mut p = candidate_class("igneum-epoch/01aa1485fcb5d59223ca40602086e618277decf440188c5a55898f635f7be34f", &e, 1, LoadClass::era(V4_CLASS, &r, &V3_ALLOWED));
|
||||
p.generator = GENERATOR_VERSION_V4;
|
||||
p.era_bytes = Some(r.clone());
|
||||
assert_eq!(p.program_id(), 0xd64dbc675f13be9e);
|
||||
for units in [4096usize, 65536] {
|
||||
let t0 = std::time::Instant::now();
|
||||
let d = crate::accept::distinct_indices_v4(&p, units).unwrap();
|
||||
println!("p23 d64dbc675f13be9e at {} evaluations per site ({:.1} s): distinct word indices per site {:?}; site 7 = {} (log2 {:.1})", units * 32 * 8, t0.elapsed().as_secs_f64(), d, d[7], (d[7] as f64).log2());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn class_v4_draw_is_total_with_the_last_resort() {
|
||||
assert_eq!(max_attempts_for(&V4_CLASS), MAX_ATTEMPTS_V4);
|
||||
|
|
|
|||
Loading…
Reference in a new issue