diff --git a/docs/plans/cryptanalysis/plan-acceptance-rule.md b/docs/plans/cryptanalysis/plan-acceptance-rule.md index 170294bbb..598fa8842 100644 --- a/docs/plans/cryptanalysis/plan-acceptance-rule.md +++ b/docs/plans/cryptanalysis/plan-acceptance-rule.md @@ -114,6 +114,24 @@ memory. chip-model-v3.md section 5 prices the on-die-cache recompute chip at 0.9 (f=1) chip over 2x per joule; a hot-set that concentrates reads would hand a chip exactly what section 5.7's "hot set" lever asks about. Items per hash target: 128 distinct of 128 loads. +## 2.4 Re-scope (main, 7 October 2026, 19:2x BST) and the operating changes + +The acceptance rule now has three lanes. This lane (adv-accept) is THE BYPASS: a program that passes +(a) to (c'') on the closed-form stand-in and has exploitable locality on the LIVE dataset. That is Q1 +(the 10^6-seed passing-program search with the hot-set measure), Q2 (the stand-in gap reproduced and +bounded) and Q5 (the generator distinguishers that pass the rule). Q4 (header grinding) is lane +adv-accept-2's and Q3 (exhaustion and steering of the draw, the 256 cap, the last resort, the program +id) is lane adv-accept-3's; their methods below stay as written for the record and are handed over, +not run here. Shared queue for spill-over: /srv/builds/_adv/accept/queue/ on build-2, claimed by +mkdir under claims/. Operating changes from the coordinator: both boxes, nice 10 on every idle core +(no core band), the capacity layer's yield (SIGSTOP the run's process group while any build slot or +hold is taken, SIGCONT when clear; copied into run-box.sh), sweeps queued back to back, 8 box-hours +is a reading not a stop (ask line 16), first results by 00:00 BST tonight. + +The seed space of the sweep is the attack-pass F8 harness's own label space +("igneum-attack-f8/program/k", ".../era/k"), so the unmodified f8 harness (built here as adv-live) +measures the live hot set of exactly the program my sweep reports for seed k. + ## 3. The questions, in my order Q1 (rank 1). Steering: a program that passes every part of the rule but concentrates its 128 live diff --git a/tools/attack/adv-accept/Cargo.toml b/tools/attack/adv-accept/Cargo.toml index e2a866f04..a898739f3 100644 --- a/tools/attack/adv-accept/Cargo.toml +++ b/tools/attack/adv-accept/Cargo.toml @@ -19,3 +19,7 @@ igneum-pow = { path = "../../../igneum-pow" } opt-level = 3 lto = true codegen-units = 1 + +[[bin]] +name = "adv-live" +path = "src/live.rs" diff --git a/tools/attack/adv-accept/inputs/v4-devnet3-epoch0-program.json b/tools/attack/adv-accept/inputs/v4-devnet3-epoch0-program.json new file mode 100644 index 000000000..00cf4447b --- /dev/null +++ b/tools/attack/adv-accept/inputs/v4-devnet3-epoch0-program.json @@ -0,0 +1,406 @@ +{ + "format": "igneum-program-pack-3", + "generator": 4, + "attempt": 0, + "program_id": "0xfce15bf61030be57", + "program_id_derivation": "FNV-1a 64 over 'igneum-program/' || generator_le32 || seed_words as little-endian bytes || attempt_le32", + "dataset_mode": "memory-hard", + "seed": "igneum-epoch/4020cb4382e3fe4b281c817c02582e147d8f851f566ae9172b28912b8e68b925/day/69676e65756d2d6461792ffd50000000000000", + "seed_bytes": "4020cb4382e3fe4b281c817c02582e147d8f851f566ae9172b28912b8e68b925", + "seed_words": ["0xbe8c5a0c", "0x7646e626", "0x508e29d0", "0xfb8a5b4a", "0x53c50955", "0x685d62fc", "0x6065c013", "0x881096eb"], + "seed_derivation": "seed_words = FNV-1a 64 over seed_bytes (attempt 0) or seed_bytes || attempt_le32 (attempt k >= 1), basis ^ (salt * 0x9E3779B97F4A7C15) for salt 0..3, then h ^= h>>33; h *= 0xff51afd7ed558ccd; h ^= h>>33; words[2*salt] = low 32, words[2*salt+1] = high 32", + "generator_rule": "version 2: exactly 16 load slots drawn first from instructions 1..63 (partial Fisher-Yates), the other 48 ops from the ten non-load weights (sum 75); a load's source is drawn from the registers other than dst written by an earlier instruction and not read by a load since; the candidate must pass the acceptance rule of spec 01 section 1.4.6 (static: no cyclically stale load source, every register has an injecting write; dynamic: 64 units on the seed-keyed closed-form dataset with no constant register bit, no lane-constant load site, under 164 saturated final values, every output bit within 136 of 1024, distinct addresses above 245760), else the next attempt of the seed is tried", + "lanes": 32, + "registers": 8, + "iterations": 8, + "instruction_count": 64, + "loads_per_hash": 128, + "program_class": "v4", + "sub_version": 3, + "era_seed_bytes": "4020cb4382e3fe4b281c817c02582e147d8f851f566ae9172b28912b8e68b925", + "load_class": "mx8-eraaf3a9139+sh256x27", + "mixer_mult": 8, + "cache_growth": true, + "mixer": "class v3 (Counter ASIC 2.0, 5 October 2026, docs/plans/mixer-x4.md): every mixer application of the item derivation is 8 applications with round keys (r * 8 + j + 1) * 0x9E3779B9, the 8 dependent cache reads per item unchanged; cache growth rule option C: cache words = 2^(26 + doublings(day)), dataset words = 2^(genesis_log2 + doublings(day)), doublings(day) = floor(log2(1 + day / 1460)) for day = days since genesis", + "load_slots": 16, + "load_mix_percent_4_16_64": [100, 0, 0], + "load_width_counts_4_16_64": [16, 0, 0], + "bytes_per_hash": 512, + "wide_load": "read-width experiment (5 October 2026, docs/plans/read-width.md), NOT the lottery hash: a load of W words (width field, 4 or 16) reads dataset[b .. b + W) with b = (src & mask) & ~(W - 1) and folds every word into dst: x = dst ^ w[0]; for j in 1..W: x = (rotl(x, 11) * 0x9e3779b1) ^ w[j]; dst = x; width 1 is the plain load; the width is drawn per instruction from the class mix with one extra below(100) draw after the nine of version 2, and the program id is FNV-1a 64 over 'igneum-program-rw/' || generator_le32 || seed words || attempt_le32 || mix[3] || load_slots", + "era": { + "label": "af3a9139", + "seed_words": ["0xaf3a9139", "0x894db311", "0xd6173573", "0xc346bc0f", "0x2d547cec", "0xd5634ab8", "0xb9584827", "0x305d030e"], + "draw": "docs/plans/era-layout.md 1.1: SplitMix64 seeded with seed_words[0] | seed_words[1] << 32 of seed_words_from_bytes('igneum-era/' || n_le64 || E_n); width = allowed[below(|allowed|)], stride_mul = low32(next()) | 1, stride_rot = 1 + below(31), then four next() draws for a partial Fisher-Yates over positions log2(W)..15 of which 4 - log2(W) are used", + "allowed_widths": [1], + "width_words": 1, + "stride_mul": "0x2cb18b35", + "stride_rot": 9, + "interleave": [1, 9, 13, 14], + "address": "y = rotl(src * stride_mul, stride_rot); k = min(win, D - 26); idx = ((y & (mask >> k)) | ((off & (2^k - 1)) << (D - k))) & mask; a wide load aligns idx down to W words", + "windows": "per instruction, after the width roll: win = below(3), off = low32(next()) & (2^win - 1); used on a load slot (the instruction's win and off fields)", + "dataset_word": "dataset[w] = item(t(w))[j(w)]: j(w) gathers the bits of w at the interleave positions, t(w) is w with those bits removed", + "program_id_suffix": "'era/' || allowed[3] || width_words || stride_mul_le32 || stride_rot_le32 || interleave[4]" + }, + "op_mix": {"load": 16, "add": 11, "shfl": 7, "mad": 6, "xor": 6, "mul": 4, "mulhi": 4, "rotr": 4, "rotl": 3, "sub": 2, "or": 1}, + "register_init": "for i in 0..7: x = nonce ^ seed_words[i]; x += 0x9e3779b9 * (i+1) (mod 2^32); x = splitmix32(x); r[i] = x ^ seed_words[(i+1) & 7]", + "splitmix32": "x ^= x>>16; x *= 0x7feb352d; x ^= x>>15; x *= 0x846ca68b; x ^= x>>16", + "iteration": "sel = r0 sampled once at the top of each iteration, then all instructions in order", + "output": "lo = r0 ^ rotl(r1,7) ^ rotl(r2,14) ^ rotl(r3,21); hi = r4 ^ rotl(r5,9) ^ rotl(r6,18) ^ rotl(r7,27); out = (hi << 32) | lo", + "op_semantics": { + "add": "dst = dst + src + (bit `bit` of sel ? imm2 : imm)", + "sub": "dst = dst - src", + "mul": "dst = dst * src (low 32)", + "mulhi": "dst = high 32 bits of dst * src", + "xor": "dst = dst ^ src", + "or": "dst = dst | src", + "rotl": "dst = rotl(dst, rot), rot in 1..31", + "rotr": "dst = rotr(dst, src & 31)", + "mad": "dst = src * src2 + dst", + "shfl": "dst = dst ^ (src of lane (lane ^ mask)), mask in {1,2,4,8,16}, within the 32-lane warp", + "load": "dst = dst ^ dataset[src & dataset.mask]", + "wload": "base = (src of lane 0 & dataset.mask) & ~31; dst = dst ^ dataset[base + lane] (warp-coalesced 128-byte load, lever b, only when --wide-frac > 0)" + }, + "dataset": { + "log2_words": 28, + "bytes": 1073741824, + "mask": "0x0fffffff", + "day": "bytes:69676e65756d2d6461792ffd50000000000000", + "day_bytes": "69676e65756d2d6461792ffd50000000000000", + "day_words_from": "seed_words_from_bytes(day_bytes)", + "d0": "0xe23f9008", + "d1": "0xde33e763", + "mode": "memory-hard", + "spec": "proto-metal/MEMHARD.md", + "key": ["0xe23f9008", "0xde33e763", "0xc5ba415d", "0x8ddf6786", "0x59f4a4be", "0x8a3bc680", "0x701b8e40", "0x3b59025a"], + "key_derivation": "the 8 words of seed_words_from_bytes(day_bytes); d0, d1 are key[0], key[1]", + "cache": {"log2_words": 26, "bytes": 268435456, "line_words": 16, "segment_lines": 64, "segments": 65536, "block": "ChaCha12 core + feed-forward, rotations 16 12 8 7", "sigma": ["0x61707865", "0x3320646e", "0x79622d32", "0x6b206574"], "tag": ["0x49676e65", "0x756d4d48"], "chain": "in_j = prev_line ^ (sigma[0..3] || key[0..7] || seg || j || tag[0..1]); line_j = block(in_j); prev_0 = 0"}, + "mixer": {"draw": "SplitMix64 seeded with key[0] | key[1] << 32: rot[0..7] = 1 + next() % 31, mul[0..15] = low32(next()) | 1, rc[0..15] = low32(next())", "rot": [28, 11, 18, 18, 13, 28, 5, 13], "mul": ["0xd1d79e7f", "0xbcb61f8b", "0x970e91ab", "0xd749d96d", "0x143d7339", "0x2cde0d69", "0x12d0a8c1", "0x2d335be9", "0x80a8aae9", "0x7ac896c7", "0x9de23db7", "0xc362827d", "0x5f4cdb5b", "0xfc6c5097", "0x6f547f83", "0x1ac31b47"], "rc": ["0xe5bef5a3", "0xdb2a7d90", "0xcd1fa7e1", "0x30289419", "0x0a730d58", "0x432f8579", "0x6ab978a5", "0x8c984f49", "0x788c3c9e", "0x051eef02", "0x05e77db9", "0x8b3cd4f3", "0x6948d6cf", "0x0d8677f6", "0xf9505c8c", "0x4513c163"], "round": "for i in 0..15: s[i] = (s[i] ^ (rc[i] + (r+1) * 0x9E3779B9)) * mul[i]; then quarter rounds on columns (0,4,8,12) (1,5,9,13) (2,6,10,14) (3,7,11,15) with rot[0..3] and diagonals (0,5,10,15) (1,6,11,12) (2,7,8,13) (3,4,9,14) with rot[4..7]", "quarter_round": "a += b; d ^= a; d = rotl(d, r1); c += d; b ^= c; b = rotl(b, r2); a += b; d ^= a; d = rotl(d, r3); c += d; b ^= c; b = rotl(b, r4)"}, + "mixer_mult": 8, + "item": "s[0..7] = key; s[8+i] = t * mul[i] + rc[i] for i in 0..7; for r in 0..7: for j in 0..7: s = M(s, rk = (r * 8 + j + 1) * 0x9E3779B9); line = s[0] & 0x003fffff; s[i] ^= cache[line * 16 + i]; then for j in 0..7: s = M(s, rk = (64 + j + 1) * 0x9E3779B9); item(t) = s", + "word": "dataset[w] = item(w >> 4)[w & 15]" + }, + "shadow": {"instrs": 256, "reps": 27, "instrs_per_hash": 55296, "op_mix": {"add": 45, "mad": 31, "shfl": 31, "xor": 30, "rotl": 28, "mul": 22, "or": 18, "mulhi": 17, "rotr": 17, "sub": 17}, "rule": "Counter ASIC 3.0 item 8 (docs/analysis/latency-shadow-2026-10-06.md): after the 64 base instructions the program stream draws instrs more ALU instructions (the non-load table, nine draws each, the source as on an ALU slot); the block runs reps times at the end of every iteration with the iteration's sel; the acceptance rule interprets the base program only", "program_id_suffix": "'shadow/' || instrs_le16 || reps_le16", "instructions": [ + {"i": 0, "op": "or", "dst": 6, "src": 0, "src2": 2, "imm": "0xc1eb1523", "imm2": "0x925958ea", "rot": 14, "bit": 14, "mask": 8}, + {"i": 1, "op": "mul", "dst": 7, "src": 3, "src2": 4, "imm": "0xc7044334", "imm2": "0x755566b6", "rot": 19, "bit": 17, "mask": 8}, + {"i": 2, "op": "mad", "dst": 3, "src": 5, "src2": 5, "imm": "0x33b52f17", "imm2": "0xcc459a7a", "rot": 5, "bit": 19, "mask": 16}, + {"i": 3, "op": "add", "dst": 7, "src": 4, "src2": 4, "imm": "0x97d3d105", "imm2": "0x38e58a06", "rot": 11, "bit": 6, "mask": 1}, + {"i": 4, "op": "add", "dst": 6, "src": 0, "src2": 6, "imm": "0x29b87b9a", "imm2": "0x9629673d", "rot": 16, "bit": 20, "mask": 8}, + {"i": 5, "op": "shfl", "dst": 6, "src": 2, "src2": 5, "imm": "0xb61f2e77", "imm2": "0x30e3807c", "rot": 1, "bit": 7, "mask": 1}, + {"i": 6, "op": "mad", "dst": 5, "src": 3, "src2": 5, "imm": "0x0de66253", "imm2": "0xfdfdd03e", "rot": 4, "bit": 27, "mask": 4}, + {"i": 7, "op": "rotl", "dst": 3, "src": 7, "src2": 1, "imm": "0x688514dd", "imm2": "0x139d08b5", "rot": 5, "bit": 1, "mask": 1}, + {"i": 8, "op": "add", "dst": 0, "src": 7, "src2": 3, "imm": "0xa7c1fb0f", "imm2": "0x4a5f55e1", "rot": 15, "bit": 12, "mask": 1}, + {"i": 9, "op": "mul", "dst": 5, "src": 7, "src2": 2, "imm": "0x1a64de5a", "imm2": "0xe37b0875", "rot": 5, "bit": 27, "mask": 16}, + {"i": 10, "op": "xor", "dst": 4, "src": 5, "src2": 3, "imm": "0x750d4f1a", "imm2": "0x2f3d9402", "rot": 3, "bit": 14, "mask": 2}, + {"i": 11, "op": "mulhi", "dst": 6, "src": 4, "src2": 5, "imm": "0x2eaa86c6", "imm2": "0x9fd5b22f", "rot": 30, "bit": 10, "mask": 1}, + {"i": 12, "op": "rotl", "dst": 5, "src": 2, "src2": 4, "imm": "0x8df50473", "imm2": "0x23d780ce", "rot": 13, "bit": 9, "mask": 1}, + {"i": 13, "op": "mad", "dst": 0, "src": 2, "src2": 1, "imm": "0xa956a7e6", "imm2": "0x58dbb356", "rot": 31, "bit": 11, "mask": 8}, + {"i": 14, "op": "mulhi", "dst": 2, "src": 4, "src2": 6, "imm": "0x6fbfb145", "imm2": "0x064eb015", "rot": 20, "bit": 2, "mask": 4}, + {"i": 15, "op": "or", "dst": 3, "src": 1, "src2": 5, "imm": "0xd313bc27", "imm2": "0x9f0fc235", "rot": 17, "bit": 16, "mask": 1}, + {"i": 16, "op": "sub", "dst": 0, "src": 5, "src2": 0, "imm": "0x86cbd336", "imm2": "0x94715167", "rot": 17, "bit": 31, "mask": 8}, + {"i": 17, "op": "mul", "dst": 0, "src": 6, "src2": 3, "imm": "0x95911c6b", "imm2": "0x816cea32", "rot": 23, "bit": 1, "mask": 2}, + {"i": 18, "op": "shfl", "dst": 5, "src": 1, "src2": 3, "imm": "0xf563dcad", "imm2": "0xb2889856", "rot": 14, "bit": 24, "mask": 4}, + {"i": 19, "op": "or", "dst": 3, "src": 4, "src2": 2, "imm": "0x7bb48d19", "imm2": "0xbf95ad6f", "rot": 8, "bit": 30, "mask": 16}, + {"i": 20, "op": "mad", "dst": 6, "src": 3, "src2": 2, "imm": "0xeb686a8d", "imm2": "0x8bda9f8a", "rot": 11, "bit": 18, "mask": 1}, + {"i": 21, "op": "rotl", "dst": 0, "src": 6, "src2": 0, "imm": "0x0915df51", "imm2": "0xa1365cda", "rot": 11, "bit": 25, "mask": 1}, + {"i": 22, "op": "rotl", "dst": 3, "src": 0, "src2": 3, "imm": "0xc09e4cdc", "imm2": "0x50c1369d", "rot": 25, "bit": 31, "mask": 2}, + {"i": 23, "op": "xor", "dst": 6, "src": 4, "src2": 1, "imm": "0xce01abec", "imm2": "0x5ff8c52e", "rot": 30, "bit": 19, "mask": 8}, + {"i": 24, "op": "or", "dst": 4, "src": 2, "src2": 4, "imm": "0xdacf9e4b", "imm2": "0x086a5684", "rot": 2, "bit": 10, "mask": 2}, + {"i": 25, "op": "add", "dst": 1, "src": 6, "src2": 3, "imm": "0x53e74799", "imm2": "0xfc2f6d17", "rot": 8, "bit": 7, "mask": 16}, + {"i": 26, "op": "rotr", "dst": 7, "src": 6, "src2": 7, "imm": "0xc89a75a0", "imm2": "0x67813253", "rot": 24, "bit": 13, "mask": 1}, + {"i": 27, "op": "add", "dst": 7, "src": 5, "src2": 4, "imm": "0xb8fae90e", "imm2": "0x787048dc", "rot": 26, "bit": 27, "mask": 1}, + {"i": 28, "op": "xor", "dst": 4, "src": 1, "src2": 0, "imm": "0x4c90c2ea", "imm2": "0x2ce7865b", "rot": 4, "bit": 7, "mask": 2}, + {"i": 29, "op": "xor", "dst": 6, "src": 1, "src2": 6, "imm": "0x6131ab08", "imm2": "0xd1825676", "rot": 4, "bit": 30, "mask": 2}, + {"i": 30, "op": "mulhi", "dst": 2, "src": 0, "src2": 1, "imm": "0xbe4a545f", "imm2": "0xdc29e823", "rot": 17, "bit": 5, "mask": 8}, + {"i": 31, "op": "rotr", "dst": 1, "src": 4, "src2": 1, "imm": "0x4d2f9cdc", "imm2": "0xdee1cc73", "rot": 5, "bit": 16, "mask": 4}, + {"i": 32, "op": "xor", "dst": 7, "src": 4, "src2": 5, "imm": "0x9d277aaa", "imm2": "0xa902fca5", "rot": 11, "bit": 3, "mask": 1}, + {"i": 33, "op": "mad", "dst": 3, "src": 2, "src2": 6, "imm": "0x7db09644", "imm2": "0x077bf3d9", "rot": 30, "bit": 19, "mask": 2}, + {"i": 34, "op": "add", "dst": 3, "src": 1, "src2": 1, "imm": "0xd2dc42eb", "imm2": "0x9ffd510b", "rot": 17, "bit": 12, "mask": 2}, + {"i": 35, "op": "add", "dst": 4, "src": 7, "src2": 0, "imm": "0xad8eda6f", "imm2": "0xf871ba37", "rot": 24, "bit": 7, "mask": 4}, + {"i": 36, "op": "add", "dst": 7, "src": 4, "src2": 3, "imm": "0xb0cef8f4", "imm2": "0xd02d30ca", "rot": 22, "bit": 3, "mask": 16}, + {"i": 37, "op": "mad", "dst": 4, "src": 3, "src2": 2, "imm": "0x6ed99a66", "imm2": "0x83dc97c0", "rot": 4, "bit": 1, "mask": 4}, + {"i": 38, "op": "mad", "dst": 5, "src": 7, "src2": 7, "imm": "0xe24fdd24", "imm2": "0x360f5c8b", "rot": 19, "bit": 9, "mask": 8}, + {"i": 39, "op": "add", "dst": 3, "src": 6, "src2": 4, "imm": "0x2bd506e2", "imm2": "0x82e98a22", "rot": 28, "bit": 26, "mask": 1}, + {"i": 40, "op": "rotl", "dst": 0, "src": 2, "src2": 1, "imm": "0xdb1faed3", "imm2": "0xf968a20d", "rot": 21, "bit": 30, "mask": 16}, + {"i": 41, "op": "or", "dst": 1, "src": 0, "src2": 1, "imm": "0xe6dfe522", "imm2": "0xb75813da", "rot": 10, "bit": 13, "mask": 1}, + {"i": 42, "op": "add", "dst": 7, "src": 5, "src2": 7, "imm": "0xc0386e49", "imm2": "0x6435f6d8", "rot": 26, "bit": 28, "mask": 16}, + {"i": 43, "op": "mulhi", "dst": 1, "src": 0, "src2": 4, "imm": "0xda497f7d", "imm2": "0xc4b748f4", "rot": 30, "bit": 10, "mask": 4}, + {"i": 44, "op": "add", "dst": 5, "src": 6, "src2": 0, "imm": "0x3a8921a4", "imm2": "0x1f682c68", "rot": 12, "bit": 12, "mask": 1}, + {"i": 45, "op": "add", "dst": 4, "src": 5, "src2": 6, "imm": "0xeeca4334", "imm2": "0xf246e180", "rot": 26, "bit": 1, "mask": 1}, + {"i": 46, "op": "xor", "dst": 2, "src": 3, "src2": 5, "imm": "0x052178c3", "imm2": "0xaab31f10", "rot": 28, "bit": 16, "mask": 8}, + {"i": 47, "op": "xor", "dst": 2, "src": 5, "src2": 0, "imm": "0x91fd7273", "imm2": "0xacc42b07", "rot": 22, "bit": 27, "mask": 4}, + {"i": 48, "op": "mad", "dst": 7, "src": 4, "src2": 1, "imm": "0x05a2970f", "imm2": "0x14876824", "rot": 10, "bit": 5, "mask": 1}, + {"i": 49, "op": "mad", "dst": 2, "src": 6, "src2": 1, "imm": "0x4440d6de", "imm2": "0x352023f9", "rot": 10, "bit": 25, "mask": 8}, + {"i": 50, "op": "mad", "dst": 7, "src": 0, "src2": 7, "imm": "0xa6f82633", "imm2": "0x78465772", "rot": 13, "bit": 8, "mask": 4}, + {"i": 51, "op": "add", "dst": 5, "src": 6, "src2": 5, "imm": "0x745776d8", "imm2": "0x41bd68cc", "rot": 29, "bit": 26, "mask": 1}, + {"i": 52, "op": "shfl", "dst": 3, "src": 0, "src2": 6, "imm": "0x8f94ebac", "imm2": "0xe4a208a0", "rot": 5, "bit": 16, "mask": 1}, + {"i": 53, "op": "or", "dst": 4, "src": 0, "src2": 5, "imm": "0x7b1235f3", "imm2": "0x408b4ea9", "rot": 27, "bit": 19, "mask": 8}, + {"i": 54, "op": "mad", "dst": 0, "src": 2, "src2": 4, "imm": "0x30114050", "imm2": "0xe71dff8f", "rot": 6, "bit": 30, "mask": 1}, + {"i": 55, "op": "rotl", "dst": 5, "src": 1, "src2": 7, "imm": "0x69f92198", "imm2": "0x65496f29", "rot": 9, "bit": 20, "mask": 4}, + {"i": 56, "op": "mul", "dst": 5, "src": 7, "src2": 6, "imm": "0x88b884d4", "imm2": "0x041b67f7", "rot": 25, "bit": 8, "mask": 16}, + {"i": 57, "op": "rotl", "dst": 7, "src": 5, "src2": 6, "imm": "0x7d1e18bc", "imm2": "0xf54ade27", "rot": 27, "bit": 20, "mask": 1}, + {"i": 58, "op": "sub", "dst": 7, "src": 6, "src2": 4, "imm": "0xa457bbab", "imm2": "0x4b05eb29", "rot": 6, "bit": 6, "mask": 4}, + {"i": 59, "op": "rotl", "dst": 2, "src": 7, "src2": 7, "imm": "0x4890bdda", "imm2": "0xadcb3d99", "rot": 22, "bit": 26, "mask": 16}, + {"i": 60, "op": "shfl", "dst": 7, "src": 1, "src2": 7, "imm": "0xe4da58eb", "imm2": "0x1d50080d", "rot": 22, "bit": 26, "mask": 2}, + {"i": 61, "op": "add", "dst": 4, "src": 0, "src2": 5, "imm": "0x60d0ff55", "imm2": "0xcba6a161", "rot": 6, "bit": 7, "mask": 8}, + {"i": 62, "op": "mul", "dst": 1, "src": 5, "src2": 1, "imm": "0x0fdc702e", "imm2": "0x6b1bd03a", "rot": 9, "bit": 23, "mask": 4}, + {"i": 63, "op": "mulhi", "dst": 7, "src": 6, "src2": 1, "imm": "0xcc44ed62", "imm2": "0x931dd006", "rot": 1, "bit": 0, "mask": 2}, + {"i": 64, "op": "mul", "dst": 5, "src": 0, "src2": 7, "imm": "0x448c3aba", "imm2": "0x1136818a", "rot": 23, "bit": 7, "mask": 1}, + {"i": 65, "op": "rotr", "dst": 2, "src": 5, "src2": 1, "imm": "0xb520dfb8", "imm2": "0xdce7d8f4", "rot": 22, "bit": 10, "mask": 8}, + {"i": 66, "op": "mulhi", "dst": 0, "src": 3, "src2": 4, "imm": "0x19fada48", "imm2": "0x3cc04e0e", "rot": 1, "bit": 24, "mask": 16}, + {"i": 67, "op": "or", "dst": 0, "src": 7, "src2": 3, "imm": "0x1c5905b4", "imm2": "0xf896dfb7", "rot": 31, "bit": 30, "mask": 8}, + {"i": 68, "op": "rotl", "dst": 0, "src": 1, "src2": 5, "imm": "0xa28511c1", "imm2": "0xbf649695", "rot": 19, "bit": 27, "mask": 1}, + {"i": 69, "op": "rotl", "dst": 0, "src": 7, "src2": 5, "imm": "0x22e2fef3", "imm2": "0x468bec9e", "rot": 1, "bit": 28, "mask": 1}, + {"i": 70, "op": "or", "dst": 2, "src": 3, "src2": 6, "imm": "0xcb542821", "imm2": "0xe084cf11", "rot": 15, "bit": 5, "mask": 8}, + {"i": 71, "op": "mad", "dst": 3, "src": 7, "src2": 5, "imm": "0xf06b8f6a", "imm2": "0x7382e15f", "rot": 6, "bit": 28, "mask": 16}, + {"i": 72, "op": "mad", "dst": 7, "src": 0, "src2": 4, "imm": "0x4f64c588", "imm2": "0x47b63ee9", "rot": 8, "bit": 10, "mask": 2}, + {"i": 73, "op": "or", "dst": 4, "src": 3, "src2": 2, "imm": "0xff138eea", "imm2": "0x6fd90eda", "rot": 23, "bit": 3, "mask": 16}, + {"i": 74, "op": "xor", "dst": 3, "src": 1, "src2": 0, "imm": "0x6011ece1", "imm2": "0x89579f15", "rot": 11, "bit": 2, "mask": 4}, + {"i": 75, "op": "add", "dst": 1, "src": 3, "src2": 4, "imm": "0x3f4ffea2", "imm2": "0x8e35924e", "rot": 12, "bit": 15, "mask": 2}, + {"i": 76, "op": "mul", "dst": 1, "src": 3, "src2": 7, "imm": "0x15efa846", "imm2": "0xa1c971f1", "rot": 14, "bit": 20, "mask": 1}, + {"i": 77, "op": "mad", "dst": 0, "src": 3, "src2": 2, "imm": "0x8660530a", "imm2": "0xa013524b", "rot": 14, "bit": 12, "mask": 8}, + {"i": 78, "op": "shfl", "dst": 2, "src": 0, "src2": 0, "imm": "0xcc2e96bd", "imm2": "0xe32c6f87", "rot": 10, "bit": 11, "mask": 16}, + {"i": 79, "op": "mulhi", "dst": 1, "src": 0, "src2": 6, "imm": "0x61931e3f", "imm2": "0x86383e7f", "rot": 17, "bit": 3, "mask": 1}, + {"i": 80, "op": "mulhi", "dst": 0, "src": 4, "src2": 7, "imm": "0xd9991afc", "imm2": "0xd00e8678", "rot": 24, "bit": 15, "mask": 16}, + {"i": 81, "op": "mul", "dst": 4, "src": 2, "src2": 3, "imm": "0x472f70f4", "imm2": "0x9fde4236", "rot": 10, "bit": 5, "mask": 4}, + {"i": 82, "op": "sub", "dst": 5, "src": 7, "src2": 6, "imm": "0x55a7acb1", "imm2": "0x9fde8df1", "rot": 30, "bit": 29, "mask": 16}, + {"i": 83, "op": "mad", "dst": 0, "src": 2, "src2": 3, "imm": "0x1c487c40", "imm2": "0xd9d9a1c3", "rot": 9, "bit": 5, "mask": 4}, + {"i": 84, "op": "sub", "dst": 7, "src": 2, "src2": 4, "imm": "0x515c0a72", "imm2": "0x6df5c83d", "rot": 28, "bit": 21, "mask": 4}, + {"i": 85, "op": "xor", "dst": 1, "src": 2, "src2": 4, "imm": "0x917f1174", "imm2": "0x8e72e519", "rot": 9, "bit": 18, "mask": 16}, + {"i": 86, "op": "shfl", "dst": 6, "src": 3, "src2": 3, "imm": "0xda856cc2", "imm2": "0x932ae8d8", "rot": 28, "bit": 1, "mask": 16}, + {"i": 87, "op": "mad", "dst": 3, "src": 0, "src2": 0, "imm": "0x4ef22321", "imm2": "0x5666ada3", "rot": 4, "bit": 4, "mask": 1}, + {"i": 88, "op": "rotl", "dst": 0, "src": 1, "src2": 3, "imm": "0xbb3ec00c", "imm2": "0xffe78937", "rot": 20, "bit": 19, "mask": 4}, + {"i": 89, "op": "sub", "dst": 1, "src": 5, "src2": 4, "imm": "0x8f295708", "imm2": "0x6608577f", "rot": 17, "bit": 18, "mask": 16}, + {"i": 90, "op": "shfl", "dst": 6, "src": 4, "src2": 5, "imm": "0xd953aa15", "imm2": "0x4ef9f062", "rot": 19, "bit": 9, "mask": 16}, + {"i": 91, "op": "xor", "dst": 5, "src": 1, "src2": 5, "imm": "0x6c02cde2", "imm2": "0x118dbd2b", "rot": 2, "bit": 20, "mask": 16}, + {"i": 92, "op": "rotl", "dst": 5, "src": 0, "src2": 5, "imm": "0x9f0b0c61", "imm2": "0xe26dbe7a", "rot": 12, "bit": 17, "mask": 2}, + {"i": 93, "op": "mad", "dst": 6, "src": 1, "src2": 2, "imm": "0x78a44a91", "imm2": "0x3ce118a9", "rot": 18, "bit": 16, "mask": 16}, + {"i": 94, "op": "rotr", "dst": 2, "src": 5, "src2": 3, "imm": "0x02295fcb", "imm2": "0xc3353c64", "rot": 5, "bit": 31, "mask": 16}, + {"i": 95, "op": "or", "dst": 0, "src": 4, "src2": 4, "imm": "0x727120c3", "imm2": "0x528f4416", "rot": 6, "bit": 20, "mask": 8}, + {"i": 96, "op": "mul", "dst": 7, "src": 3, "src2": 4, "imm": "0x919c42ea", "imm2": "0x87526aa5", "rot": 28, "bit": 1, "mask": 8}, + {"i": 97, "op": "or", "dst": 5, "src": 0, "src2": 7, "imm": "0x8a0a100c", "imm2": "0xb2c65cc3", "rot": 13, "bit": 7, "mask": 4}, + {"i": 98, "op": "add", "dst": 3, "src": 1, "src2": 1, "imm": "0xbcfdd8a7", "imm2": "0x386bb642", "rot": 8, "bit": 13, "mask": 8}, + {"i": 99, "op": "add", "dst": 0, "src": 6, "src2": 7, "imm": "0xc921a021", "imm2": "0xa3962d03", "rot": 31, "bit": 9, "mask": 1}, + {"i": 100, "op": "rotr", "dst": 1, "src": 0, "src2": 3, "imm": "0x918c6f69", "imm2": "0x1a125801", "rot": 18, "bit": 29, "mask": 16}, + {"i": 101, "op": "add", "dst": 2, "src": 4, "src2": 1, "imm": "0x70da4067", "imm2": "0xc5990c5c", "rot": 3, "bit": 18, "mask": 4}, + {"i": 102, "op": "rotl", "dst": 2, "src": 7, "src2": 0, "imm": "0x9048d081", "imm2": "0xc25a9f56", "rot": 3, "bit": 25, "mask": 16}, + {"i": 103, "op": "shfl", "dst": 1, "src": 4, "src2": 7, "imm": "0x70ca1427", "imm2": "0x6632093d", "rot": 18, "bit": 28, "mask": 4}, + {"i": 104, "op": "xor", "dst": 0, "src": 2, "src2": 0, "imm": "0x0637204c", "imm2": "0x3b1b7f94", "rot": 1, "bit": 12, "mask": 2}, + {"i": 105, "op": "add", "dst": 4, "src": 3, "src2": 5, "imm": "0x1a5880cb", "imm2": "0xb15aec75", "rot": 31, "bit": 28, "mask": 1}, + {"i": 106, "op": "shfl", "dst": 4, "src": 0, "src2": 4, "imm": "0x7c22cb00", "imm2": "0xc4df7949", "rot": 3, "bit": 15, "mask": 2}, + {"i": 107, "op": "shfl", "dst": 7, "src": 0, "src2": 0, "imm": "0xd43c76ca", "imm2": "0x8d5b9751", "rot": 31, "bit": 28, "mask": 4}, + {"i": 108, "op": "mad", "dst": 1, "src": 7, "src2": 3, "imm": "0x69879993", "imm2": "0x3c4e18b0", "rot": 19, "bit": 25, "mask": 4}, + {"i": 109, "op": "mad", "dst": 0, "src": 2, "src2": 3, "imm": "0x8ce3a4a6", "imm2": "0x1b3f0b74", "rot": 19, "bit": 8, "mask": 2}, + {"i": 110, "op": "or", "dst": 2, "src": 4, "src2": 5, "imm": "0xcdbdac98", "imm2": "0x8d0e9f92", "rot": 19, "bit": 13, "mask": 1}, + {"i": 111, "op": "shfl", "dst": 6, "src": 1, "src2": 1, "imm": "0xe74157f6", "imm2": "0xf92dead6", "rot": 4, "bit": 14, "mask": 1}, + {"i": 112, "op": "mad", "dst": 2, "src": 5, "src2": 3, "imm": "0x200831bc", "imm2": "0x1ee98048", "rot": 29, "bit": 14, "mask": 1}, + {"i": 113, "op": "mulhi", "dst": 4, "src": 0, "src2": 7, "imm": "0xd6827ead", "imm2": "0xc0ee1933", "rot": 26, "bit": 23, "mask": 16}, + {"i": 114, "op": "rotl", "dst": 7, "src": 2, "src2": 2, "imm": "0x6ae1be2b", "imm2": "0x83216a4e", "rot": 29, "bit": 25, "mask": 16}, + {"i": 115, "op": "sub", "dst": 3, "src": 7, "src2": 1, "imm": "0xa1d1646e", "imm2": "0x6fa998ce", "rot": 15, "bit": 29, "mask": 2}, + {"i": 116, "op": "mulhi", "dst": 0, "src": 1, "src2": 4, "imm": "0xd7921c15", "imm2": "0x8b2345a7", "rot": 10, "bit": 19, "mask": 4}, + {"i": 117, "op": "rotl", "dst": 4, "src": 0, "src2": 4, "imm": "0xc1b1b0ab", "imm2": "0xd19b3f39", "rot": 12, "bit": 16, "mask": 1}, + {"i": 118, "op": "add", "dst": 1, "src": 4, "src2": 5, "imm": "0x24494ad3", "imm2": "0xc5e98ee5", "rot": 22, "bit": 7, "mask": 16}, + {"i": 119, "op": "add", "dst": 0, "src": 4, "src2": 6, "imm": "0x1b2d1c83", "imm2": "0xa97bc949", "rot": 8, "bit": 4, "mask": 16}, + {"i": 120, "op": "shfl", "dst": 5, "src": 4, "src2": 7, "imm": "0xb93015ff", "imm2": "0xc22366cd", "rot": 23, "bit": 16, "mask": 4}, + {"i": 121, "op": "shfl", "dst": 3, "src": 2, "src2": 5, "imm": "0xc0aaacdb", "imm2": "0xaaa7d229", "rot": 31, "bit": 29, "mask": 8}, + {"i": 122, "op": "rotl", "dst": 7, "src": 1, "src2": 3, "imm": "0xcce6a73f", "imm2": "0x517b6b86", "rot": 3, "bit": 16, "mask": 8}, + {"i": 123, "op": "rotr", "dst": 4, "src": 6, "src2": 0, "imm": "0x8e156aca", "imm2": "0x0a894b3e", "rot": 2, "bit": 12, "mask": 2}, + {"i": 124, "op": "shfl", "dst": 6, "src": 5, "src2": 2, "imm": "0xd88c8446", "imm2": "0xf49fedf3", "rot": 3, "bit": 23, "mask": 1}, + {"i": 125, "op": "rotl", "dst": 5, "src": 2, "src2": 0, "imm": "0xd10b27cf", "imm2": "0x2dde1faa", "rot": 21, "bit": 7, "mask": 2}, + {"i": 126, "op": "mad", "dst": 0, "src": 3, "src2": 1, "imm": "0x619e1818", "imm2": "0x62add310", "rot": 26, "bit": 1, "mask": 4}, + {"i": 127, "op": "add", "dst": 0, "src": 3, "src2": 6, "imm": "0xff14c08d", "imm2": "0xa7f6bb54", "rot": 25, "bit": 24, "mask": 4}, + {"i": 128, "op": "xor", "dst": 7, "src": 4, "src2": 4, "imm": "0x34224086", "imm2": "0x5e9f858e", "rot": 4, "bit": 16, "mask": 8}, + {"i": 129, "op": "mulhi", "dst": 3, "src": 5, "src2": 6, "imm": "0x89c3cbdd", "imm2": "0x4e80334c", "rot": 29, "bit": 4, "mask": 1}, + {"i": 130, "op": "shfl", "dst": 5, "src": 7, "src2": 1, "imm": "0x37d4c3e2", "imm2": "0xbd54378c", "rot": 14, "bit": 26, "mask": 8}, + {"i": 131, "op": "mulhi", "dst": 6, "src": 4, "src2": 3, "imm": "0x4f5fac7c", "imm2": "0x74070028", "rot": 5, "bit": 25, "mask": 8}, + {"i": 132, "op": "xor", "dst": 5, "src": 3, "src2": 0, "imm": "0x690eb003", "imm2": "0x8516a581", "rot": 20, "bit": 21, "mask": 2}, + {"i": 133, "op": "rotl", "dst": 0, "src": 5, "src2": 7, "imm": "0x42b31885", "imm2": "0x08249acb", "rot": 19, "bit": 6, "mask": 8}, + {"i": 134, "op": "rotr", "dst": 4, "src": 3, "src2": 3, "imm": "0x07d729ed", "imm2": "0xf564da8a", "rot": 17, "bit": 9, "mask": 16}, + {"i": 135, "op": "shfl", "dst": 1, "src": 2, "src2": 0, "imm": "0x19130ef2", "imm2": "0xf59815e5", "rot": 17, "bit": 12, "mask": 16}, + {"i": 136, "op": "sub", "dst": 5, "src": 2, "src2": 5, "imm": "0xdeb56168", "imm2": "0x871104c4", "rot": 13, "bit": 17, "mask": 16}, + {"i": 137, "op": "xor", "dst": 3, "src": 5, "src2": 4, "imm": "0x884fc7da", "imm2": "0x83a4a5bf", "rot": 23, "bit": 12, "mask": 4}, + {"i": 138, "op": "sub", "dst": 0, "src": 2, "src2": 6, "imm": "0x3e67a6fd", "imm2": "0x844d2039", "rot": 3, "bit": 24, "mask": 4}, + {"i": 139, "op": "mul", "dst": 3, "src": 7, "src2": 7, "imm": "0x965933b4", "imm2": "0xf37ef93d", "rot": 6, "bit": 9, "mask": 4}, + {"i": 140, "op": "add", "dst": 5, "src": 3, "src2": 3, "imm": "0x82ab98f4", "imm2": "0x2d465ca8", "rot": 11, "bit": 24, "mask": 1}, + {"i": 141, "op": "add", "dst": 4, "src": 1, "src2": 7, "imm": "0x80594390", "imm2": "0x237a9d8e", "rot": 14, "bit": 11, "mask": 8}, + {"i": 142, "op": "mad", "dst": 3, "src": 7, "src2": 0, "imm": "0xd37861a1", "imm2": "0x2fe70eae", "rot": 2, "bit": 11, "mask": 16}, + {"i": 143, "op": "or", "dst": 0, "src": 3, "src2": 5, "imm": "0x1a018e2c", "imm2": "0x301aa5f6", "rot": 15, "bit": 26, "mask": 1}, + {"i": 144, "op": "mulhi", "dst": 0, "src": 1, "src2": 5, "imm": "0x69d22dfc", "imm2": "0x4a90cb1e", "rot": 21, "bit": 9, "mask": 16}, + {"i": 145, "op": "xor", "dst": 6, "src": 2, "src2": 0, "imm": "0xf3f41f47", "imm2": "0xdf8d33e9", "rot": 22, "bit": 0, "mask": 8}, + {"i": 146, "op": "shfl", "dst": 7, "src": 1, "src2": 1, "imm": "0xf8a52377", "imm2": "0x0613ef41", "rot": 24, "bit": 12, "mask": 16}, + {"i": 147, "op": "add", "dst": 0, "src": 1, "src2": 3, "imm": "0x07f15148", "imm2": "0xe68cf57e", "rot": 2, "bit": 18, "mask": 4}, + {"i": 148, "op": "or", "dst": 7, "src": 3, "src2": 0, "imm": "0xbfd39b49", "imm2": "0xc7f6e97c", "rot": 13, "bit": 27, "mask": 2}, + {"i": 149, "op": "xor", "dst": 4, "src": 2, "src2": 7, "imm": "0x47409e43", "imm2": "0x04ef7db9", "rot": 8, "bit": 6, "mask": 4}, + {"i": 150, "op": "xor", "dst": 7, "src": 1, "src2": 1, "imm": "0x5d42a156", "imm2": "0xc2f77c48", "rot": 27, "bit": 17, "mask": 4}, + {"i": 151, "op": "rotr", "dst": 5, "src": 2, "src2": 7, "imm": "0x7c170030", "imm2": "0xa3102916", "rot": 5, "bit": 6, "mask": 1}, + {"i": 152, "op": "mul", "dst": 0, "src": 7, "src2": 5, "imm": "0x16d009af", "imm2": "0x2a89533b", "rot": 26, "bit": 8, "mask": 4}, + {"i": 153, "op": "rotl", "dst": 4, "src": 5, "src2": 0, "imm": "0x6e54eb71", "imm2": "0xe2d27256", "rot": 16, "bit": 18, "mask": 1}, + {"i": 154, "op": "rotr", "dst": 6, "src": 4, "src2": 5, "imm": "0x128f24a4", "imm2": "0x59888463", "rot": 15, "bit": 17, "mask": 16}, + {"i": 155, "op": "xor", "dst": 6, "src": 1, "src2": 2, "imm": "0x34f276ff", "imm2": "0xef5086b4", "rot": 2, "bit": 10, "mask": 4}, + {"i": 156, "op": "or", "dst": 6, "src": 3, "src2": 4, "imm": "0x16cc2c2f", "imm2": "0xfae5a09b", "rot": 12, "bit": 31, "mask": 4}, + {"i": 157, "op": "add", "dst": 1, "src": 6, "src2": 7, "imm": "0x14878c5a", "imm2": "0x84c2a09d", "rot": 30, "bit": 19, "mask": 8}, + {"i": 158, "op": "sub", "dst": 4, "src": 1, "src2": 0, "imm": "0xfe18e4b0", "imm2": "0x239df0cc", "rot": 14, "bit": 20, "mask": 2}, + {"i": 159, "op": "mad", "dst": 4, "src": 6, "src2": 7, "imm": "0xbdfcc4e0", "imm2": "0x0870dee8", "rot": 1, "bit": 7, "mask": 8}, + {"i": 160, "op": "mul", "dst": 1, "src": 5, "src2": 1, "imm": "0xc837b43d", "imm2": "0x027ac950", "rot": 4, "bit": 22, "mask": 16}, + {"i": 161, "op": "or", "dst": 4, "src": 0, "src2": 2, "imm": "0x9c28f8f6", "imm2": "0x3db71fe6", "rot": 11, "bit": 6, "mask": 16}, + {"i": 162, "op": "rotl", "dst": 7, "src": 3, "src2": 6, "imm": "0xb6e56f55", "imm2": "0x5fcbd27c", "rot": 21, "bit": 10, "mask": 1}, + {"i": 163, "op": "sub", "dst": 0, "src": 1, "src2": 4, "imm": "0x8d95ae70", "imm2": "0xd4f10284", "rot": 15, "bit": 12, "mask": 8}, + {"i": 164, "op": "mul", "dst": 1, "src": 0, "src2": 6, "imm": "0xa9bdf99e", "imm2": "0x1f3dceb6", "rot": 21, "bit": 20, "mask": 16}, + {"i": 165, "op": "sub", "dst": 2, "src": 1, "src2": 4, "imm": "0xda245058", "imm2": "0x2038e7f6", "rot": 30, "bit": 11, "mask": 2}, + {"i": 166, "op": "shfl", "dst": 0, "src": 6, "src2": 3, "imm": "0xbbc8eee8", "imm2": "0xa73472fb", "rot": 23, "bit": 4, "mask": 2}, + {"i": 167, "op": "sub", "dst": 0, "src": 4, "src2": 2, "imm": "0x43540d69", "imm2": "0xb3f116ca", "rot": 28, "bit": 2, "mask": 2}, + {"i": 168, "op": "rotr", "dst": 1, "src": 6, "src2": 6, "imm": "0x57cea752", "imm2": "0xc1136e71", "rot": 25, "bit": 3, "mask": 8}, + {"i": 169, "op": "rotr", "dst": 7, "src": 1, "src2": 5, "imm": "0x4550a465", "imm2": "0x14362935", "rot": 25, "bit": 9, "mask": 16}, + {"i": 170, "op": "mad", "dst": 3, "src": 1, "src2": 3, "imm": "0x8c961d80", "imm2": "0x7de2e1db", "rot": 10, "bit": 6, "mask": 4}, + {"i": 171, "op": "shfl", "dst": 2, "src": 0, "src2": 0, "imm": "0xda047fca", "imm2": "0x459e17ab", "rot": 22, "bit": 26, "mask": 4}, + {"i": 172, "op": "sub", "dst": 2, "src": 1, "src2": 2, "imm": "0xbcc341fd", "imm2": "0x17a3b229", "rot": 8, "bit": 25, "mask": 8}, + {"i": 173, "op": "xor", "dst": 7, "src": 6, "src2": 7, "imm": "0x2302b424", "imm2": "0x78780982", "rot": 23, "bit": 7, "mask": 8}, + {"i": 174, "op": "rotr", "dst": 4, "src": 1, "src2": 2, "imm": "0xcad6fb5f", "imm2": "0x1807628e", "rot": 8, "bit": 21, "mask": 4}, + {"i": 175, "op": "shfl", "dst": 4, "src": 7, "src2": 7, "imm": "0xf5057e8e", "imm2": "0xd14d6cc5", "rot": 3, "bit": 12, "mask": 2}, + {"i": 176, "op": "mul", "dst": 6, "src": 2, "src2": 1, "imm": "0xc2926356", "imm2": "0xae74dc32", "rot": 1, "bit": 21, "mask": 8}, + {"i": 177, "op": "shfl", "dst": 0, "src": 2, "src2": 7, "imm": "0x8d94d98e", "imm2": "0x7bd6f4dc", "rot": 27, "bit": 14, "mask": 4}, + {"i": 178, "op": "add", "dst": 6, "src": 4, "src2": 6, "imm": "0x4fa43965", "imm2": "0x509871e6", "rot": 28, "bit": 21, "mask": 4}, + {"i": 179, "op": "xor", "dst": 0, "src": 6, "src2": 6, "imm": "0xe278fe6c", "imm2": "0x75d8a63f", "rot": 21, "bit": 15, "mask": 2}, + {"i": 180, "op": "add", "dst": 5, "src": 7, "src2": 7, "imm": "0xfb3c4bd8", "imm2": "0xb95b8a53", "rot": 22, "bit": 0, "mask": 4}, + {"i": 181, "op": "mul", "dst": 6, "src": 3, "src2": 2, "imm": "0x5a1347ec", "imm2": "0x3ccc5389", "rot": 27, "bit": 26, "mask": 4}, + {"i": 182, "op": "rotl", "dst": 7, "src": 4, "src2": 7, "imm": "0xdc6db2f7", "imm2": "0x7cdff0c6", "rot": 1, "bit": 2, "mask": 4}, + {"i": 183, "op": "add", "dst": 0, "src": 7, "src2": 7, "imm": "0xc448a197", "imm2": "0x7047b7cf", "rot": 24, "bit": 11, "mask": 8}, + {"i": 184, "op": "mul", "dst": 3, "src": 5, "src2": 0, "imm": "0xa5c0e492", "imm2": "0xaf7d8a86", "rot": 11, "bit": 16, "mask": 8}, + {"i": 185, "op": "shfl", "dst": 5, "src": 7, "src2": 1, "imm": "0x5965794b", "imm2": "0x36ddca6b", "rot": 7, "bit": 25, "mask": 1}, + {"i": 186, "op": "mul", "dst": 0, "src": 2, "src2": 5, "imm": "0xdfdbcc0f", "imm2": "0xac7b7091", "rot": 11, "bit": 31, "mask": 8}, + {"i": 187, "op": "mulhi", "dst": 4, "src": 7, "src2": 5, "imm": "0x3d8999e9", "imm2": "0xf1848db8", "rot": 15, "bit": 21, "mask": 8}, + {"i": 188, "op": "mad", "dst": 2, "src": 7, "src2": 2, "imm": "0xda3af48c", "imm2": "0x8e33a97c", "rot": 6, "bit": 30, "mask": 4}, + {"i": 189, "op": "sub", "dst": 7, "src": 2, "src2": 4, "imm": "0xa32da8c1", "imm2": "0xf7486d06", "rot": 25, "bit": 16, "mask": 4}, + {"i": 190, "op": "add", "dst": 2, "src": 5, "src2": 1, "imm": "0x2fceaf49", "imm2": "0x5caccc5d", "rot": 24, "bit": 19, "mask": 2}, + {"i": 191, "op": "shfl", "dst": 1, "src": 2, "src2": 2, "imm": "0x2a71f5e6", "imm2": "0xb9b51e89", "rot": 31, "bit": 25, "mask": 1}, + {"i": 192, "op": "add", "dst": 7, "src": 1, "src2": 7, "imm": "0xba0cee62", "imm2": "0x857bb5fe", "rot": 4, "bit": 20, "mask": 2}, + {"i": 193, "op": "rotl", "dst": 6, "src": 3, "src2": 3, "imm": "0xc7fe35cf", "imm2": "0x515c1201", "rot": 22, "bit": 7, "mask": 16}, + {"i": 194, "op": "xor", "dst": 3, "src": 7, "src2": 4, "imm": "0xe496d1f1", "imm2": "0x11046e3e", "rot": 16, "bit": 9, "mask": 4}, + {"i": 195, "op": "mul", "dst": 7, "src": 0, "src2": 2, "imm": "0xc7d5c4c2", "imm2": "0x077e940b", "rot": 23, "bit": 29, "mask": 4}, + {"i": 196, "op": "xor", "dst": 3, "src": 5, "src2": 3, "imm": "0xbe6db14a", "imm2": "0xf736da5a", "rot": 19, "bit": 6, "mask": 2}, + {"i": 197, "op": "add", "dst": 5, "src": 1, "src2": 5, "imm": "0x8519428c", "imm2": "0xeae84577", "rot": 18, "bit": 29, "mask": 1}, + {"i": 198, "op": "mad", "dst": 7, "src": 4, "src2": 4, "imm": "0xb272a029", "imm2": "0x071f6a5c", "rot": 5, "bit": 28, "mask": 8}, + {"i": 199, "op": "add", "dst": 3, "src": 6, "src2": 3, "imm": "0x468639d3", "imm2": "0x0bfdbfa1", "rot": 20, "bit": 8, "mask": 16}, + {"i": 200, "op": "mad", "dst": 5, "src": 3, "src2": 4, "imm": "0xffba18b7", "imm2": "0xbdcf9e81", "rot": 13, "bit": 31, "mask": 8}, + {"i": 201, "op": "shfl", "dst": 2, "src": 7, "src2": 7, "imm": "0xb7533191", "imm2": "0x8d58cc8c", "rot": 7, "bit": 29, "mask": 4}, + {"i": 202, "op": "add", "dst": 7, "src": 4, "src2": 6, "imm": "0x18ec9a69", "imm2": "0x53ee8e50", "rot": 12, "bit": 23, "mask": 16}, + {"i": 203, "op": "xor", "dst": 6, "src": 0, "src2": 5, "imm": "0x12011ff1", "imm2": "0xfbb57ac6", "rot": 16, "bit": 25, "mask": 16}, + {"i": 204, "op": "add", "dst": 4, "src": 5, "src2": 3, "imm": "0x3e81485b", "imm2": "0xac63376e", "rot": 1, "bit": 4, "mask": 1}, + {"i": 205, "op": "mad", "dst": 7, "src": 1, "src2": 4, "imm": "0xeb9eee5c", "imm2": "0x3f1daecd", "rot": 19, "bit": 29, "mask": 4}, + {"i": 206, "op": "shfl", "dst": 1, "src": 5, "src2": 2, "imm": "0x4fc50d06", "imm2": "0xb5533b31", "rot": 22, "bit": 27, "mask": 16}, + {"i": 207, "op": "mul", "dst": 4, "src": 0, "src2": 5, "imm": "0x202d6001", "imm2": "0x50388f85", "rot": 5, "bit": 18, "mask": 16}, + {"i": 208, "op": "or", "dst": 1, "src": 6, "src2": 7, "imm": "0x8743c0d4", "imm2": "0x9df69539", "rot": 14, "bit": 16, "mask": 1}, + {"i": 209, "op": "add", "dst": 6, "src": 4, "src2": 6, "imm": "0x66ccb75d", "imm2": "0x5b745519", "rot": 28, "bit": 28, "mask": 8}, + {"i": 210, "op": "rotr", "dst": 1, "src": 3, "src2": 1, "imm": "0xe9933132", "imm2": "0x6702fe85", "rot": 16, "bit": 19, "mask": 1}, + {"i": 211, "op": "sub", "dst": 5, "src": 4, "src2": 4, "imm": "0xfe04b942", "imm2": "0x40dd2ce8", "rot": 24, "bit": 8, "mask": 4}, + {"i": 212, "op": "xor", "dst": 4, "src": 7, "src2": 7, "imm": "0x10827878", "imm2": "0xef0de8fc", "rot": 17, "bit": 11, "mask": 8}, + {"i": 213, "op": "add", "dst": 1, "src": 7, "src2": 1, "imm": "0x0ce0553d", "imm2": "0x1c3dccf7", "rot": 26, "bit": 8, "mask": 1}, + {"i": 214, "op": "mulhi", "dst": 0, "src": 1, "src2": 1, "imm": "0xa71d7581", "imm2": "0xe567c74c", "rot": 2, "bit": 29, "mask": 2}, + {"i": 215, "op": "add", "dst": 3, "src": 6, "src2": 2, "imm": "0x96bfec89", "imm2": "0xfadc9205", "rot": 30, "bit": 9, "mask": 16}, + {"i": 216, "op": "rotl", "dst": 0, "src": 2, "src2": 5, "imm": "0x947ca474", "imm2": "0xd2a1b650", "rot": 4, "bit": 1, "mask": 1}, + {"i": 217, "op": "xor", "dst": 6, "src": 4, "src2": 5, "imm": "0x0b7617de", "imm2": "0xc6c0a9a2", "rot": 31, "bit": 15, "mask": 2}, + {"i": 218, "op": "shfl", "dst": 6, "src": 7, "src2": 1, "imm": "0x738f0a8f", "imm2": "0xfc868560", "rot": 2, "bit": 20, "mask": 2}, + {"i": 219, "op": "mad", "dst": 7, "src": 0, "src2": 1, "imm": "0x2960f2bc", "imm2": "0x7e9ef9b7", "rot": 21, "bit": 4, "mask": 16}, + {"i": 220, "op": "mul", "dst": 4, "src": 0, "src2": 2, "imm": "0x6014d800", "imm2": "0xd19ac7d1", "rot": 1, "bit": 13, "mask": 1}, + {"i": 221, "op": "mad", "dst": 2, "src": 6, "src2": 0, "imm": "0x4b558fc2", "imm2": "0x2d48bd3c", "rot": 18, "bit": 31, "mask": 2}, + {"i": 222, "op": "xor", "dst": 5, "src": 4, "src2": 6, "imm": "0x0260f3ab", "imm2": "0x8fce22f1", "rot": 21, "bit": 26, "mask": 16}, + {"i": 223, "op": "mul", "dst": 0, "src": 5, "src2": 0, "imm": "0xe07ab58f", "imm2": "0x5349a41d", "rot": 20, "bit": 3, "mask": 1}, + {"i": 224, "op": "add", "dst": 2, "src": 0, "src2": 5, "imm": "0x61495681", "imm2": "0x66b15c04", "rot": 24, "bit": 9, "mask": 16}, + {"i": 225, "op": "rotr", "dst": 3, "src": 2, "src2": 5, "imm": "0x291268bd", "imm2": "0xf046aa79", "rot": 27, "bit": 15, "mask": 4}, + {"i": 226, "op": "mad", "dst": 2, "src": 3, "src2": 3, "imm": "0xfd6dfa6c", "imm2": "0x2c0db93d", "rot": 29, "bit": 2, "mask": 16}, + {"i": 227, "op": "xor", "dst": 6, "src": 0, "src2": 4, "imm": "0xd4ae1ee2", "imm2": "0xe6a67972", "rot": 26, "bit": 1, "mask": 4}, + {"i": 228, "op": "rotl", "dst": 4, "src": 7, "src2": 3, "imm": "0xf6627a9d", "imm2": "0x99078da3", "rot": 30, "bit": 5, "mask": 16}, + {"i": 229, "op": "add", "dst": 2, "src": 3, "src2": 7, "imm": "0x70d05c34", "imm2": "0x25bc17c2", "rot": 20, "bit": 29, "mask": 4}, + {"i": 230, "op": "rotr", "dst": 1, "src": 5, "src2": 2, "imm": "0xef874bea", "imm2": "0x3bfbfecc", "rot": 17, "bit": 2, "mask": 2}, + {"i": 231, "op": "add", "dst": 1, "src": 4, "src2": 5, "imm": "0x32a28384", "imm2": "0x2d8728f3", "rot": 1, "bit": 8, "mask": 8}, + {"i": 232, "op": "rotl", "dst": 1, "src": 6, "src2": 3, "imm": "0x877c54ab", "imm2": "0x1d5e5014", "rot": 6, "bit": 24, "mask": 2}, + {"i": 233, "op": "add", "dst": 2, "src": 0, "src2": 5, "imm": "0xcf0949f1", "imm2": "0x61e81fe6", "rot": 2, "bit": 21, "mask": 16}, + {"i": 234, "op": "rotl", "dst": 4, "src": 5, "src2": 5, "imm": "0x5017c32c", "imm2": "0x1ab34dfe", "rot": 2, "bit": 1, "mask": 2}, + {"i": 235, "op": "sub", "dst": 2, "src": 6, "src2": 2, "imm": "0xadbdb811", "imm2": "0x2c2d4f2b", "rot": 30, "bit": 29, "mask": 8}, + {"i": 236, "op": "or", "dst": 6, "src": 3, "src2": 4, "imm": "0x700e204f", "imm2": "0x27294e7b", "rot": 13, "bit": 0, "mask": 4}, + {"i": 237, "op": "shfl", "dst": 7, "src": 5, "src2": 4, "imm": "0xc39f71ba", "imm2": "0x9a90263d", "rot": 28, "bit": 9, "mask": 4}, + {"i": 238, "op": "xor", "dst": 1, "src": 3, "src2": 6, "imm": "0x9f84a5b3", "imm2": "0x95691d15", "rot": 14, "bit": 22, "mask": 1}, + {"i": 239, "op": "rotl", "dst": 2, "src": 4, "src2": 0, "imm": "0xa3b79ed5", "imm2": "0x05f568bc", "rot": 1, "bit": 15, "mask": 4}, + {"i": 240, "op": "rotr", "dst": 5, "src": 2, "src2": 7, "imm": "0xaaabbb0d", "imm2": "0x71ce4cd6", "rot": 2, "bit": 26, "mask": 4}, + {"i": 241, "op": "rotl", "dst": 4, "src": 1, "src2": 7, "imm": "0x8146fbd3", "imm2": "0xde65fc31", "rot": 10, "bit": 9, "mask": 4}, + {"i": 242, "op": "add", "dst": 6, "src": 2, "src2": 7, "imm": "0xe204fb50", "imm2": "0x0a67565d", "rot": 5, "bit": 16, "mask": 1}, + {"i": 243, "op": "shfl", "dst": 1, "src": 3, "src2": 7, "imm": "0xcb7a3c1b", "imm2": "0x863f002c", "rot": 18, "bit": 1, "mask": 2}, + {"i": 244, "op": "shfl", "dst": 0, "src": 1, "src2": 3, "imm": "0x92eb030c", "imm2": "0x3cff4c6d", "rot": 1, "bit": 22, "mask": 16}, + {"i": 245, "op": "shfl", "dst": 5, "src": 7, "src2": 6, "imm": "0x046c969c", "imm2": "0xd21d7e76", "rot": 5, "bit": 31, "mask": 2}, + {"i": 246, "op": "xor", "dst": 4, "src": 5, "src2": 1, "imm": "0x2e3015f0", "imm2": "0x1b305a5c", "rot": 22, "bit": 16, "mask": 8}, + {"i": 247, "op": "xor", "dst": 7, "src": 2, "src2": 0, "imm": "0x8238cf71", "imm2": "0xb7874d94", "rot": 9, "bit": 1, "mask": 2}, + {"i": 248, "op": "shfl", "dst": 0, "src": 6, "src2": 7, "imm": "0x437cbc99", "imm2": "0x7fb41fed", "rot": 17, "bit": 1, "mask": 16}, + {"i": 249, "op": "mulhi", "dst": 1, "src": 3, "src2": 2, "imm": "0xeb293d88", "imm2": "0xb92f5968", "rot": 3, "bit": 20, "mask": 2}, + {"i": 250, "op": "add", "dst": 7, "src": 5, "src2": 3, "imm": "0x3d6daffd", "imm2": "0x5a79fd6f", "rot": 6, "bit": 31, "mask": 2}, + {"i": 251, "op": "add", "dst": 6, "src": 0, "src2": 3, "imm": "0x84334aea", "imm2": "0x6beb28c2", "rot": 27, "bit": 28, "mask": 2}, + {"i": 252, "op": "sub", "dst": 7, "src": 1, "src2": 6, "imm": "0xe6177638", "imm2": "0x814d3fc5", "rot": 25, "bit": 2, "mask": 2}, + {"i": 253, "op": "rotr", "dst": 5, "src": 2, "src2": 7, "imm": "0x95560bac", "imm2": "0xbf8b01a9", "rot": 14, "bit": 5, "mask": 16}, + {"i": 254, "op": "mulhi", "dst": 2, "src": 5, "src2": 1, "imm": "0x318956e5", "imm2": "0x7c20b373", "rot": 13, "bit": 30, "mask": 2}, + {"i": 255, "op": "mul", "dst": 6, "src": 3, "src2": 3, "imm": "0xe1998e49", "imm2": "0x59353e5a", "rot": 15, "bit": 2, "mask": 4} + ]}, + "instructions": [ + {"i": 0, "op": "sub", "dst": 6, "src": 3, "src2": 3, "imm": "0x77dfbe60", "imm2": "0x404c8b9c", "rot": 24, "bit": 8, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 1, "op": "or", "dst": 2, "src": 6, "src2": 7, "imm": "0xb2e79058", "imm2": "0xc2485073", "rot": 21, "bit": 23, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 2, "op": "add", "dst": 4, "src": 0, "src2": 6, "imm": "0x1a579b38", "imm2": "0x7731324b", "rot": 6, "bit": 4, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 3, "op": "load", "dst": 3, "src": 6, "src2": 0, "imm": "0x683f5bc5", "imm2": "0xc8a218fe", "rot": 25, "bit": 4, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 4, "op": "shfl", "dst": 0, "src": 6, "src2": 4, "imm": "0x4d7cdcbb", "imm2": "0x1a5bf1a4", "rot": 8, "bit": 28, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 5, "op": "xor", "dst": 4, "src": 1, "src2": 0, "imm": "0xa2b86827", "imm2": "0x94a44439", "rot": 21, "bit": 22, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 6, "op": "mad", "dst": 3, "src": 5, "src2": 3, "imm": "0x58a4ea3f", "imm2": "0x467879fa", "rot": 6, "bit": 31, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 7, "op": "mul", "dst": 1, "src": 6, "src2": 1, "imm": "0x31fcc19c", "imm2": "0x176cb88f", "rot": 1, "bit": 11, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 8, "op": "load", "dst": 2, "src": 0, "src2": 3, "imm": "0x89b12386", "imm2": "0x661ed5e5", "rot": 11, "bit": 13, "mask": 2, "width": 1, "win": 2, "off": 2}, + {"i": 9, "op": "xor", "dst": 0, "src": 4, "src2": 1, "imm": "0x69150105", "imm2": "0x8e1f04b9", "rot": 3, "bit": 30, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 10, "op": "add", "dst": 0, "src": 7, "src2": 5, "imm": "0xee083919", "imm2": "0xb10fcef8", "rot": 9, "bit": 30, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 11, "op": "mulhi", "dst": 0, "src": 2, "src2": 3, "imm": "0x99ac7c83", "imm2": "0x34eb2845", "rot": 18, "bit": 12, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 12, "op": "xor", "dst": 5, "src": 4, "src2": 0, "imm": "0x97cfc887", "imm2": "0x0458f053", "rot": 28, "bit": 24, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 13, "op": "mad", "dst": 7, "src": 3, "src2": 0, "imm": "0x503e87b4", "imm2": "0x2c5d2617", "rot": 3, "bit": 15, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 14, "op": "load", "dst": 3, "src": 4, "src2": 4, "imm": "0xfae98035", "imm2": "0x6c127281", "rot": 19, "bit": 29, "mask": 4, "width": 1, "win": 1, "off": 0}, + {"i": 15, "op": "load", "dst": 2, "src": 5, "src2": 2, "imm": "0x67601ed5", "imm2": "0x3c546ed1", "rot": 28, "bit": 28, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 16, "op": "shfl", "dst": 7, "src": 5, "src2": 6, "imm": "0x7789be79", "imm2": "0x87496b8e", "rot": 22, "bit": 2, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 17, "op": "mul", "dst": 7, "src": 6, "src2": 2, "imm": "0x559099bb", "imm2": "0x6a72e1a6", "rot": 1, "bit": 11, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 18, "op": "mad", "dst": 2, "src": 3, "src2": 1, "imm": "0x4f5bae5e", "imm2": "0x4c8c5762", "rot": 27, "bit": 31, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 19, "op": "rotr", "dst": 5, "src": 2, "src2": 4, "imm": "0x5dc5e6bf", "imm2": "0x36bc207d", "rot": 9, "bit": 4, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 20, "op": "load", "dst": 5, "src": 3, "src2": 7, "imm": "0x4a278885", "imm2": "0xcd1044b0", "rot": 30, "bit": 0, "mask": 4, "width": 1, "win": 1, "off": 1}, + {"i": 21, "op": "shfl", "dst": 6, "src": 4, "src2": 3, "imm": "0x48b94ffc", "imm2": "0xd8e14d80", "rot": 2, "bit": 31, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 22, "op": "mulhi", "dst": 1, "src": 2, "src2": 7, "imm": "0x47bd8492", "imm2": "0x28fa8d0b", "rot": 19, "bit": 26, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 23, "op": "xor", "dst": 6, "src": 5, "src2": 1, "imm": "0x4ed6baf4", "imm2": "0xa8c31f74", "rot": 30, "bit": 2, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 24, "op": "mad", "dst": 5, "src": 2, "src2": 0, "imm": "0x6174747d", "imm2": "0xfc912524", "rot": 18, "bit": 29, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 25, "op": "mad", "dst": 6, "src": 3, "src2": 7, "imm": "0x113a6442", "imm2": "0xbe52ec57", "rot": 15, "bit": 15, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 26, "op": "load", "dst": 6, "src": 5, "src2": 1, "imm": "0x9d3eb1cc", "imm2": "0x375c73bb", "rot": 29, "bit": 7, "mask": 8, "width": 1, "win": 1, "off": 1}, + {"i": 27, "op": "mad", "dst": 1, "src": 5, "src2": 5, "imm": "0x150088c3", "imm2": "0x7f17e789", "rot": 8, "bit": 25, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 28, "op": "load", "dst": 0, "src": 6, "src2": 3, "imm": "0x4b412223", "imm2": "0x9192ceeb", "rot": 16, "bit": 28, "mask": 1, "width": 1, "win": 1, "off": 1}, + {"i": 29, "op": "add", "dst": 7, "src": 4, "src2": 6, "imm": "0xd5e6c37c", "imm2": "0x0b1f02c7", "rot": 2, "bit": 7, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 30, "op": "add", "dst": 7, "src": 0, "src2": 6, "imm": "0x4e45ba51", "imm2": "0x2d6e8bd1", "rot": 7, "bit": 0, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 31, "op": "xor", "dst": 0, "src": 2, "src2": 7, "imm": "0xef601d89", "imm2": "0x805402db", "rot": 7, "bit": 28, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 32, "op": "add", "dst": 6, "src": 2, "src2": 3, "imm": "0x1c91b2a1", "imm2": "0x351422d2", "rot": 1, "bit": 13, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 33, "op": "mulhi", "dst": 4, "src": 5, "src2": 7, "imm": "0x7cf21567", "imm2": "0x1075ee0d", "rot": 26, "bit": 17, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 34, "op": "rotr", "dst": 3, "src": 5, "src2": 6, "imm": "0x60d8f191", "imm2": "0x40523707", "rot": 3, "bit": 10, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 35, "op": "load", "dst": 3, "src": 0, "src2": 5, "imm": "0xb1dc3f6e", "imm2": "0x74440531", "rot": 21, "bit": 10, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 36, "op": "rotl", "dst": 4, "src": 6, "src2": 4, "imm": "0xf0d17569", "imm2": "0xd281fd01", "rot": 13, "bit": 4, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 37, "op": "add", "dst": 6, "src": 1, "src2": 6, "imm": "0x3f2970f7", "imm2": "0x64a28251", "rot": 4, "bit": 7, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 38, "op": "mul", "dst": 2, "src": 0, "src2": 3, "imm": "0x6463770c", "imm2": "0xb85d603f", "rot": 27, "bit": 1, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 39, "op": "add", "dst": 3, "src": 4, "src2": 3, "imm": "0x3b7b3317", "imm2": "0x7378c955", "rot": 28, "bit": 15, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 40, "op": "load", "dst": 0, "src": 7, "src2": 3, "imm": "0xd0fb5982", "imm2": "0x2e4d31a8", "rot": 25, "bit": 27, "mask": 4, "width": 1, "win": 2, "off": 3}, + {"i": 41, "op": "xor", "dst": 6, "src": 5, "src2": 0, "imm": "0xe96a9e6e", "imm2": "0x8d73cc3e", "rot": 17, "bit": 11, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 42, "op": "add", "dst": 3, "src": 0, "src2": 6, "imm": "0x6678b059", "imm2": "0xb31f6a64", "rot": 11, "bit": 2, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 43, "op": "load", "dst": 7, "src": 3, "src2": 2, "imm": "0x65a841c5", "imm2": "0x6aae1403", "rot": 9, "bit": 9, "mask": 1, "width": 1, "win": 1, "off": 0}, + {"i": 44, "op": "shfl", "dst": 3, "src": 7, "src2": 6, "imm": "0x650ba38d", "imm2": "0x07218508", "rot": 12, "bit": 13, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 45, "op": "mulhi", "dst": 1, "src": 5, "src2": 7, "imm": "0x18736788", "imm2": "0x9f820eed", "rot": 13, "bit": 29, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 46, "op": "rotl", "dst": 0, "src": 1, "src2": 5, "imm": "0x48deba75", "imm2": "0xefc508fa", "rot": 6, "bit": 6, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 47, "op": "load", "dst": 1, "src": 7, "src2": 4, "imm": "0x28a04384", "imm2": "0xc67cb0ac", "rot": 5, "bit": 5, "mask": 2, "width": 1, "win": 1, "off": 1}, + {"i": 48, "op": "add", "dst": 4, "src": 0, "src2": 5, "imm": "0x43095946", "imm2": "0x5feccee6", "rot": 22, "bit": 17, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 49, "op": "load", "dst": 2, "src": 0, "src2": 2, "imm": "0x3e78cdd8", "imm2": "0x7c170311", "rot": 10, "bit": 5, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 50, "op": "sub", "dst": 2, "src": 3, "src2": 7, "imm": "0xaff0cdb2", "imm2": "0xde0b7ee0", "rot": 21, "bit": 11, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 51, "op": "add", "dst": 7, "src": 2, "src2": 1, "imm": "0xf45ecdf8", "imm2": "0x26e2b582", "rot": 28, "bit": 24, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 52, "op": "load", "dst": 5, "src": 7, "src2": 7, "imm": "0x67353e69", "imm2": "0xb2d56082", "rot": 27, "bit": 14, "mask": 2, "width": 1, "win": 2, "off": 3}, + {"i": 53, "op": "load", "dst": 6, "src": 3, "src2": 1, "imm": "0x5a58c787", "imm2": "0x53a0467b", "rot": 6, "bit": 16, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 54, "op": "shfl", "dst": 7, "src": 5, "src2": 4, "imm": "0x3c237ad5", "imm2": "0xf2681187", "rot": 14, "bit": 26, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 55, "op": "rotr", "dst": 1, "src": 2, "src2": 3, "imm": "0x1c25c077", "imm2": "0x125de1ff", "rot": 13, "bit": 13, "mask": 2, "width": 1, "win": 0, "off": 0}, + {"i": 56, "op": "shfl", "dst": 0, "src": 5, "src2": 0, "imm": "0x6465afe2", "imm2": "0x23b41d56", "rot": 31, "bit": 28, "mask": 16, "width": 1, "win": 0, "off": 0}, + {"i": 57, "op": "rotl", "dst": 5, "src": 0, "src2": 2, "imm": "0xa427ff6e", "imm2": "0x014a0deb", "rot": 12, "bit": 28, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 58, "op": "add", "dst": 5, "src": 3, "src2": 7, "imm": "0x6b4f35e8", "imm2": "0x473b1718", "rot": 25, "bit": 4, "mask": 8, "width": 1, "win": 0, "off": 0}, + {"i": 59, "op": "rotr", "dst": 4, "src": 1, "src2": 3, "imm": "0x66cc96ef", "imm2": "0xcb8c8e56", "rot": 7, "bit": 6, "mask": 4, "width": 1, "win": 0, "off": 0}, + {"i": 60, "op": "shfl", "dst": 4, "src": 5, "src2": 5, "imm": "0x461bbf9f", "imm2": "0xc1ffd350", "rot": 19, "bit": 2, "mask": 1, "width": 1, "win": 0, "off": 0}, + {"i": 61, "op": "load", "dst": 7, "src": 0, "src2": 1, "imm": "0x4d893571", "imm2": "0x5583e644", "rot": 19, "bit": 19, "mask": 8, "width": 1, "win": 1, "off": 1}, + {"i": 62, "op": "load", "dst": 5, "src": 7, "src2": 3, "imm": "0x7a9049b4", "imm2": "0xbb7ebdf8", "rot": 15, "bit": 11, "mask": 8, "width": 1, "win": 1, "off": 1}, + {"i": 63, "op": "mul", "dst": 4, "src": 1, "src2": 7, "imm": "0xe7ae8a83", "imm2": "0x1ded64e8", "rot": 21, "bit": 1, "mask": 8, "width": 1, "win": 0, "off": 0} + ] +} diff --git a/tools/attack/adv-accept/run-box.sh b/tools/attack/adv-accept/run-box.sh index b65bf64a8..2c6f238f2 100644 --- a/tools/attack/adv-accept/run-box.sh +++ b/tools/attack/adv-accept/run-box.sh @@ -1,14 +1,47 @@ #!/usr/bin/env bash -# Start a long adv-accept run on build box 2 under this lane's rules: nice 10, core band 0-31, a pid -# file beside the log, logs under /srv/builds/igneum-wt-adv-accept/adv/. Run ON box 2, from the built -# crate dir. Kill only by the pid file (kill "$(cat .pid)"), never by name. -# run-box.sh +# Start a long adv-accept run on a build box under this lane's rules (coordinator, 7 October 2026, 19:2x UK): +# nice 10 on every idle core (no core band), and the capacity layer's yield: a 5 s poll of /srv/builds/_locks +# SIGSTOPs the run's own process group while any build-, measure or quiet lock is held and SIGCONTs it when +# they clear (infra/build-server/capacity/run.sh). The run never takes a build slot. Pid files beside the log: +# .pid (the run), .pgid (its process group), .yield.pid (the yield loop). Kill ONLY by pid file: +# kill -- -"$(cat .pgid)"; kill "$(cat .yield.pid)" +# Run ON the box, from anywhere: +# run-box.sh set -euo pipefail -tag="$1"; shift +tag="$1"; bin="$2"; shift 2 dir=/srv/builds/igneum-wt-adv-accept/adv mkdir -p "$dir" -bin=/srv/builds/igneum-wt-adv-accept/tools/attack/adv-accept/target/release/adv-accept log="$dir/$tag.log" -nohup nice -n 10 taskset -c 0-31 "$bin" "$@" > "$log" 2>&1 & -echo $! > "$log.pid" -echo "started pid $(cat "$log.pid"); log $log" +[ -x "$bin" ] || { echo "no binary at $bin" >&2; exit 1; } +# own process group via setsid so STOP/CONT reach every thread and child +setsid nice -n 10 "$bin" "$@" > "$log" 2>&1 < /dev/null & +pid=$! +pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' '); [ -n "$pgid" ] || pgid="$pid" +echo "$pid" > "$log.pid"; echo "$pgid" > "$log.pgid" +locks=/srv/builds/_locks +held() { # 0 when any build slot, measure or quiet lock is held + local f + for f in "$locks"/build-[0-9]* "$locks"/measure "$locks"/quiet; do + [ -e "$f" ] || continue + exec 9>>"$f" + if ! flock -n 9; then exec 9>&-; return 0; fi + exec 9>&- + done + return 1 +} +( + paused=0 + while kill -0 "$pid" 2>/dev/null; do + if held; then + if [ "$paused" = 0 ]; then kill -STOP -- "-$pgid" 2>/dev/null && paused=1; echo "$(date -u +%FT%TZ) pause (a build slot or hold is taken)" >> "$log.yield"; fi + else + if [ "$paused" = 1 ]; then kill -CONT -- "-$pgid" 2>/dev/null; paused=0; echo "$(date -u +%FT%TZ) resume" >> "$log.yield"; fi + fi + sleep 5 + done + [ "$paused" = 1 ] && kill -CONT -- "-$pgid" 2>/dev/null + echo "$(date -u +%FT%TZ) run ended" >> "$log.yield" +) > /dev/null 2>&1 < /dev/null & +disown $! 2>/dev/null || true +echo $! > "$log.yield.pid" +echo "started pid $pid pgid $pgid yield-loop pid $(cat "$log.yield.pid"); log $log" diff --git a/tools/attack/adv-accept/src/live.rs b/tools/attack/adv-accept/src/live.rs new file mode 100644 index 000000000..cac66dc09 --- /dev/null +++ b/tools/attack/adv-accept/src/live.rs @@ -0,0 +1,1745 @@ +//! attack-f8: the uniformity censuses of attack-pass row F8 (`docs/plans/cryptanalysis.md` 4.2 F8) on the real +//! class v4 derivation through the `igneum-pow` library. +//! +//! Three censuses, one binary: +//! +//! * `lines`: the cache-line index `s[0] AND line_mask` (MEMHARD.md 1.6, 2^22 lines) of every one of the 8 reads of +//! every item `t < 2^items_log2` on `days` consecutive day keys: the full 2^22-line histogram per round and +//! pooled, the 2^16-bucket (64-line segment) histogram, the largest bucket in sigma, chi-square, and a uniform +//! SplitMix64 control of the same size. Every item is also derived by the library's `derive_items` and compared +//! word for word (the traced mirror is trusted only while that holds). +//! * `warps`: the warp interpreter mirrored with every load's item index and the item's 8 lines recorded (the +//! day's items derived once into a table, as a GPU holds the dataset): distinct lines and items per hash and per +//! warp over `nonces` nonces of one program, the cross-hash item histogram with the hot-set test, and a uniform +//! control. Sampled warps are hashed by `Epoch::hash_warp` as well and must agree bit for bit. +//! +//! Plant hooks (the known-fail firings): `--plant quarter-lines` masks the line index to a quarter of its range, +//! `--plant half-lines` to a half, `--plant const-item` feeds one constant item at the program's first load site. +//! Under a plant the library comparisons are skipped (the plant is not the derivation) and the log says so. +//! +//! Nothing in `igneum-pow` is modified; every constant and the read sequence are the library's. + +use igneum_pow::bind::{day_bytes, hex, unhex}; +use igneum_pow::generator::{EraParams, Instr, Op, Program, ProgramClass, ITERATIONS, LANES}; +use igneum_pow::memhard::{mixer, round_key_mult, Cache, Layout, MixParams, ITEM_ROUNDS}; +use igneum_pow::seed::{seed_words_from_bytes, SplitMix64}; +use igneum_pow::verify::{load_index, splitmix32, DatasetSource, Epoch}; +use std::io::Write; +use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; + +/// The devnet genesis hash: epoch seed and era seed of the chain's epoch 0 (`igneum-pow/tests/packs.rs`). +const GENESIS_HEX: &str = "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07"; +/// The devnet pack's day index (`bind::day_bytes(20730)`, 2026-10-04). +const DEFAULT_DAY: u64 = 20730; +const DATASET_LOG2: u32 = 28; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Plant { + None, + QuarterLines, + HalfLines, + ConstItem, +} + +impl Plant { + fn parse(s: &str) -> Plant { + match s { + "none" => Plant::None, + "quarter-lines" => Plant::QuarterLines, + "half-lines" => Plant::HalfLines, + "const-item" => Plant::ConstItem, + _ => panic!("unknown plant {s}"), + } + } + fn line_mask(self) -> u32 { + match self { + Plant::QuarterLines => (1u32 << 20) - 1, + Plant::HalfLines => (1u32 << 21) - 1, + _ => u32::MAX, + } + } + fn name(self) -> &'static str { + match self { + Plant::None => "none", + Plant::QuarterLines => "quarter-lines", + Plant::HalfLines => "half-lines", + Plant::ConstItem => "const-item", + } + } +} + +fn utc_now() -> String { + let s = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let (d, t) = (s / 86400, s % 86400); + // civil date from days (Howard Hinnant's algorithm) + let z = d as i64 + 719468; + let era = z.div_euclid(146097); + let doe = z.rem_euclid(146097); + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + let y = yoe + era * 400; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let dd = doy - (153 * mp + 2) / 5 + 1; + let mm = if mp < 10 { mp + 3 } else { mp - 9 }; + let yy = if mm <= 2 { y + 1 } else { y }; + format!("{yy:04}-{mm:02}-{dd:02}T{:02}:{:02}:{:02}Z", t / 3600, (t / 60) % 60, t % 60) +} + +macro_rules! log { + ($($arg:tt)*) => {{ + println!("[{}] {}", utc_now(), format!($($arg)*)); + std::io::stdout().flush().ok(); + }}; +} + +// -------------------------------------------------------------------------------------------------------------- +// The traced item derivation: `memhard::derive_items_mask` instruction for instruction, with the line index of +// every round recorded. Batched like the library so the 8 dependent misses of independent items overlap. +// -------------------------------------------------------------------------------------------------------------- + +fn derive_traced(ts: &[u32], mp: &MixParams, cache: &Cache, out: &mut [[u32; 16]], lines: &mut [[u32; 8]], plant_mask: u32) { + let n = ts.len(); + let m = mp.shape.mixer_mult as usize; + assert_eq!(mp.shape.derive_len, 0, "class v4 has the fixed mixer"); + let line_mask = cache.line_mask(); + for k in 0..n { + let s = &mut out[k]; + let t = ts[k]; + s[..8].copy_from_slice(&mp.key); + for i in 0..8 { + s[8 + i] = t.wrapping_mul(mp.mul[i]).wrapping_add(mp.rc[i]); + } + } + for r in 0..ITEM_ROUNDS { + for j in 0..m { + let rk = round_key_mult(r, j, m); + for s in out[..n].iter_mut() { + mixer(s, rk, mp); + } + } + for k in 0..n { + let s = &mut out[k]; + // MEMHARD.md 1.6: `a = s[0] AND 0x003fffff`, the cache line index (the mask is the cache's at every size) + let a = s[0] & line_mask & plant_mask; + lines[k][r] = a; + let line = cache.line(a); + for i in 0..16 { + s[i] ^= line[i]; + } + } + } + for j in 0..m { + let rk = round_key_mult(ITEM_ROUNDS, j, m); + for s in out[..n].iter_mut() { + mixer(s, rk, mp); + } + } +} + +// -------------------------------------------------------------------------------------------------------------- +// Histogram statistics +// -------------------------------------------------------------------------------------------------------------- + +struct Stats { + bins: usize, + total: u64, + mean: f64, + sigma: f64, + max: u64, + argmax: usize, + min: u64, + z_max: f64, + z_min: f64, + chi2_per_dof: f64, + chi2_z: f64, + /// (fraction f, top-f share S_f) for f = 1/1000, 1/200, 1/100 + top: [(f64, f64); 3], +} + +fn stats_of(counts: impl Iterator + Clone, bins: usize) -> Stats { + let total: u64 = counts.clone().sum(); + let mean = total as f64 / bins as f64; + let sigma = mean.sqrt(); + let mut max = 0u64; + let mut argmax = 0usize; + let mut min = u64::MAX; + let mut chi2 = 0f64; + for (i, c) in counts.clone().enumerate() { + if c > max { + max = c; + argmax = i; + } + if c < min { + min = c; + } + let d = c as f64 - mean; + chi2 += d * d; + } + chi2 /= mean; + let dof = (bins - 1) as f64; + // count of counts for the top-f shares (counts at or above the cap, rare, are sorted on their own) + let cap = 1usize << 16; + let mut coc = vec![0u64; cap]; + let mut big: Vec = Vec::new(); + for c in counts { + if (c as usize) < cap { + coc[c as usize] += 1; + } else { + big.push(c); + } + } + big.sort_unstable_by(|a, b| b.cmp(a)); + let mut top = [(0f64, 0f64); 3]; + for (k, f) in [1.0 / 1000.0, 1.0 / 200.0, 1.0 / 100.0].into_iter().enumerate() { + let want = (f * bins as f64).round() as u64; + let mut left = want; + let mut reads = 0u64; + for &c in &big { + if left == 0 { + break; + } + reads += c; + left -= 1; + } + let mut v = cap - 1; + while left > 0 { + let n = coc[v].min(left); + reads += n * v as u64; + left -= n; + if v == 0 { + break; + } + v -= 1; + } + top[k] = (f, reads as f64 / total.max(1) as f64); + } + Stats { + bins, + total, + mean, + sigma, + max, + argmax, + min, + z_max: (max as f64 - mean) / sigma, + z_min: (min as f64 - mean) / sigma, + chi2_per_dof: chi2 / dof, + chi2_z: (chi2 - dof) / (2.0 * dof).sqrt(), + top, + } +} + +impl Stats { + fn line(&self, label: &str) -> String { + format!( + "{label}: bins {} reads {} mean {:.3} sigma {:.3} max {} (bin {}) z_max {:+.2} min {} z_min {:+.2} chi2/dof {:.5} chi2_z {:+.2} top0.1% {:.5}% top0.5% {:.5}% top1% {:.5}%", + self.bins, + self.total, + self.mean, + self.sigma, + self.max, + self.argmax, + self.z_max, + self.min, + self.z_min, + self.chi2_per_dof, + self.chi2_z, + self.top[0].1 * 100.0, + self.top[1].1 * 100.0, + self.top[2].1 * 100.0 + ) + } +} + +fn snapshot(counts: &[AtomicU32]) -> Vec { + counts.iter().map(|x| x.load(Ordering::Relaxed) as u64).collect() +} + +fn zero(counts: &[AtomicU32]) { + for c in counts { + c.store(0, Ordering::Relaxed); + } +} + +fn atomic_vec(n: usize) -> Vec { + (0..n).map(|_| AtomicU32::new(0)).collect() +} + +/// A uniform control: `n` SplitMix64 indices into `bins` bins, `threads` threads. +fn control(bins: usize, n: u64, seed: u64, threads: usize, hist: &[AtomicU32]) { + let mask = (bins - 1) as u64; + assert!(bins.is_power_of_two()); + std::thread::scope(|sc| { + for th in 0..threads { + let hist = &hist; + sc.spawn(move || { + let mut s = SplitMix64::new(seed ^ (th as u64).wrapping_mul(0x9E3779B97F4A7C15)); + let per = n / threads as u64 + if (th as u64) < n % threads as u64 { 1 } else { 0 }; + for _ in 0..per { + let i = (s.next() & mask) as usize; + hist[i].fetch_add(1, Ordering::Relaxed); + } + }); + } + }); +} + +/// The hot-set test (F8's definition, reused by F9): for the top-f items by count, the share S_f of all reads +/// they receive, against the same share E_f of a uniform control of the same size; the excess X_f = S_f - E_f. +/// The set is a hot set when X_f >= f for any f in {0.1%, 0.5%, 1%}: after the chance excess is removed, the top +/// f of items capture at least one extra proportional share (at least 2f of the reads above chance, which is +/// what a chip's on-die copy of f of the items would have to win to matter). The statistical sensitivity is +/// printed beside it: X_f in units of f. +fn hot_set_test(label: &str, real: &Stats, ctrl: &Stats) -> bool { + let mut flagged = false; + for k in 0..3 { + let (f, s) = real.top[k]; + let e = ctrl.top[k].1; + let x = s - e; + let fire = x >= f; + flagged |= fire; + log!( + "hot-set {label}: f {:.1}% S_f {:.5}% E_f(control) {:.5}% X_f {:+.5}% X_f/f {:+.4} -> {}", + f * 100.0, + s * 100.0, + e * 100.0, + x * 100.0, + x / f, + if fire { "HOT SET" } else { "no hot set" } + ); + } + log!("hot-set {label}: verdict {}", if flagged { "FLAGGED" } else { "clear" }); + flagged +} + +/// The 6-sigma test: the largest bucket within 6 sigma of uniform. +fn six_sigma_test(label: &str, s: &Stats) -> bool { + let fire = s.z_max > 6.0 || s.z_min < -6.0; + log!( + "6-sigma {label}: largest bucket {} at {:+.2} sigma, smallest {} at {:+.2} sigma -> {}", + s.max, + s.z_max, + s.min, + s.z_min, + if fire { "FLAGGED (beyond 6 sigma)" } else { "within 6 sigma" } + ); + fire +} + +// -------------------------------------------------------------------------------------------------------------- +// Census 1: the line index over all items of `days` day keys +// -------------------------------------------------------------------------------------------------------------- + +fn census_lines(day0: u64, days: u64, items_log2: u32, threads: usize, plant: Plant, validate: &str, out_dir: &str) { + log!("census lines: day0 {day0} days {days} items 2^{items_log2} per day threads {threads} plant {} validate {validate}", plant.name()); + let lines_n = 1usize << 22; + let per_round: Vec> = (0..ITEM_ROUNDS).map(|_| atomic_vec(lines_n)).collect(); + let pooled: Vec> = (0..ITEM_ROUNDS).map(|_| atomic_vec(lines_n)).collect(); + let items = 1u64 << items_log2; + let batch = 64u64; + let mut any_fire = false; + let mut mismatches_total = 0u64; + let mut derivations = 0u64; + let t_all = Instant::now(); + for d in day0..day0 + days { + let t0 = Instant::now(); + let ds = Epoch::chain_dataset_day(&day_bytes(d), ProgramClass::V4, 0, DATASET_LOG2); + let mh = ds.memhard().expect("memory-hard"); + assert_eq!(mh.params.shape.mixer_mult, 8); + assert_eq!(mh.params.shape.cache_log2_words, 26); + assert_eq!(mh.cache.line_mask(), (1u32 << 22) - 1); + log!( + "day {d}: day bytes {} key {} rot {:?} cache 2^26 words fnv {:016x} filled in {:.2} s", + hex(&day_bytes(d)), + mh.params.key.iter().map(|w| format!("{w:08x}")).collect::>().join(""), + mh.params.rot, + mh.cache.fnv1a64(), + t0.elapsed().as_secs_f64() + ); + for h in &per_round { + zero(h); + } + let next = AtomicUsize::new(0); + let mism = AtomicUsize::new(0); + let t1 = Instant::now(); + std::thread::scope(|sc| { + for _ in 0..threads { + let (next, mism, per_round, mp, cache) = (&next, &mism, &per_round, &mh.params, &mh.cache); + sc.spawn(move || { + let mut ts = [0u32; 64]; + let mut out = [[0u32; 16]; 64]; + let mut lines = [[0u32; 8]; 64]; + let mut lib = [[0u32; 16]; 64]; + loop { + let b = next.fetch_add(1, Ordering::Relaxed) as u64; + let start = b * batch; + if start >= items { + break; + } + for k in 0..64 { + ts[k] = (start + k as u64) as u32; + } + derive_traced(&ts, mp, cache, &mut out, &mut lines, plant.line_mask()); + for k in 0..64 { + for r in 0..ITEM_ROUNDS { + per_round[r][lines[k][r] as usize].fetch_add(1, Ordering::Relaxed); + } + } + let check = plant == Plant::None && (validate == "all" || (validate == "sample" && b % 64 == 0)); + if check { + igneum_pow::memhard::derive_items(&ts, mp, cache, &mut lib); + for k in 0..64 { + if lib[k] != out[k] { + mism.fetch_add(1, Ordering::Relaxed); + } + } + } + } + }); + } + }); + let el = t1.elapsed().as_secs_f64(); + derivations += items; + let mm = mism.load(Ordering::Relaxed) as u64; + mismatches_total += mm; + log!( + "day {d}: {items} items ({} line reads) derived in {el:.1} s ({:.2} M items/s); library comparison: {} ({} mismatches)", + items * 8, + items as f64 / el / 1e6, + if plant == Plant::None { validate } else { "skipped under the plant" }, + mm + ); + if mm > 0 { + log!("day {d}: THE TRACED MIRROR DISAGREES WITH THE LIBRARY: {mm} items; nothing below is trusted"); + } + // per-day stats: per round, total over rounds, buckets of 64 lines (one segment) + let mut total = vec![0u64; lines_n]; + for r in 0..ITEM_ROUNDS { + let snap = snapshot(&per_round[r]); + let s = stats_of(snap.iter().copied(), lines_n); + log!("{}", s.line(&format!("day {d} round {r} lines"))); + for (i, c) in snap.iter().enumerate() { + total[i] += c; + pooled[r][i].fetch_add(*c as u32, Ordering::Relaxed); + } + } + let s_full = stats_of(total.iter().copied(), lines_n); + log!("{}", s_full.line(&format!("day {d} all rounds lines"))); + let b: Vec = total.chunks(64).map(|c| c.iter().sum()).collect(); + let s_b = stats_of(b.iter().copied(), b.len()); + log!("{}", s_b.line(&format!("day {d} all rounds buckets64"))); + let fire = six_sigma_test(&format!("day {d} buckets64"), &s_b); + any_fire |= fire; + // control of the same size + let ctrl = atomic_vec(lines_n); + control(lines_n, items * 8, 0xF8_0000 + d, threads, &ctrl); + let cs = snapshot(&ctrl); + let c_full = stats_of(cs.iter().copied(), lines_n); + log!("{}", c_full.line(&format!("day {d} CONTROL lines"))); + let cb: Vec = cs.chunks(64).map(|c| c.iter().sum()).collect(); + let c_b = stats_of(cb.iter().copied(), cb.len()); + log!("{}", c_b.line(&format!("day {d} CONTROL buckets64"))); + let hot = hot_set_test(&format!("day {d} lines"), &s_full, &c_full); + any_fire |= hot; + } + // pooled over the days + let mut total = vec![0u64; lines_n]; + for r in 0..ITEM_ROUNDS { + let snap = snapshot(&pooled[r]); + let s = stats_of(snap.iter().copied(), lines_n); + log!("{}", s.line(&format!("POOLED {days} days round {r} lines"))); + for (i, c) in snap.iter().enumerate() { + total[i] += c; + } + } + let s_full = stats_of(total.iter().copied(), lines_n); + log!("{}", s_full.line(&format!("POOLED {days} days all rounds lines"))); + let b: Vec = total.chunks(64).map(|c| c.iter().sum()).collect(); + let s_b = stats_of(b.iter().copied(), b.len()); + log!("{}", s_b.line(&format!("POOLED {days} days all rounds buckets64"))); + let fire = six_sigma_test(&format!("POOLED {days} days buckets64"), &s_b); + let fire_full = six_sigma_test(&format!("POOLED {days} days full 2^22 lines"), &s_full); + let ctrl = atomic_vec(lines_n); + control(lines_n, derivations * 8, 0xF8_1111, threads, &ctrl); + let cs = snapshot(&ctrl); + let c_full = stats_of(cs.iter().copied(), lines_n); + log!("{}", c_full.line(&format!("POOLED CONTROL lines"))); + let cb: Vec = cs.chunks(64).map(|c| c.iter().sum()).collect(); + let c_b = stats_of(cb.iter().copied(), cb.len()); + log!("{}", c_b.line(&format!("POOLED CONTROL buckets64"))); + let hot = hot_set_test(&format!("POOLED {days} days lines"), &s_full, &c_full); + any_fire |= fire | fire_full | hot; + // files + let tag = format!("lines-d{day0}-n{days}-i{items_log2}-{}", plant.name()); + { + let mut f = std::fs::File::create(format!("{out_dir}/{tag}-buckets64.txt")).unwrap(); + writeln!(f, "# bucket(64 lines = one segment) count ; pooled over {days} days from {day0}, items 2^{items_log2} per day, plant {}", plant.name()).unwrap(); + for (i, c) in b.iter().enumerate() { + writeln!(f, "{i} {c}").unwrap(); + } + let mut g = std::fs::File::create(format!("{out_dir}/{tag}-full.u32le")).unwrap(); + let mut bytes = Vec::with_capacity(lines_n * 4); + for c in &total { + bytes.extend_from_slice(&(*c as u32).to_le_bytes()); + } + g.write_all(&bytes).unwrap(); + } + log!( + "census lines DONE: {derivations} item derivations ({} line reads) over {days} days in {:.1} s; mirror mismatches {mismatches_total}; verdict {}", + derivations * 8, + t_all.elapsed().as_secs_f64(), + if any_fire { "FLAGGED" } else { "PASS (no test fired)" } + ); +} + +// -------------------------------------------------------------------------------------------------------------- +// Census 2 and 3: the warp interpreter mirrored with every item index recorded +// -------------------------------------------------------------------------------------------------------------- + +struct Table { + items: Vec<[u32; 16]>, + lines: Vec<[u32; 8]>, +} + +fn build_table(mp: &MixParams, cache: &Cache, threads: usize, plant: Plant, validate: &str) -> (Table, u64) { + let n = 1usize << (DATASET_LOG2 - 4); + let t0 = Instant::now(); + let mut items = vec![[0u32; 16]; n]; + let mut lines = vec![[0u32; 8]; n]; + let mism = AtomicUsize::new(0); + std::thread::scope(|sc| { + let per = n / threads; + for (th, (ic, lc)) in items.chunks_mut(per).zip(lines.chunks_mut(per)).enumerate() { + let mism = &mism; + sc.spawn(move || { + let mut ts = [0u32; 64]; + let mut lib = [[0u32; 16]; 64]; + let base = th * per; + for (bi, (ib, lb)) in ic.chunks_mut(64).zip(lc.chunks_mut(64)).enumerate() { + let start = base + bi * 64; + for k in 0..ib.len() { + ts[k] = (start + k) as u32; + } + derive_traced(&ts[..ib.len()], mp, cache, ib, lb, plant.line_mask()); + let check = plant == Plant::None && (validate == "all" || (validate == "sample" && bi % 64 == 0)); + if check { + igneum_pow::memhard::derive_items(&ts[..ib.len()], mp, cache, &mut lib); + for k in 0..ib.len() { + if lib[k] != ib[k] { + mism.fetch_add(1, Ordering::Relaxed); + } + } + } + } + }); + } + }); + let mm = mism.load(Ordering::Relaxed) as u64; + log!( + "table: {n} items derived with their 8 lines in {:.1} s; library comparison {} ({mm} mismatches)", + t0.elapsed().as_secs_f64(), + if plant == Plant::None { validate } else { "skipped under the plant" } + ); + (Table { items, lines }, mm) +} + +struct ProgramSpec { + label: String, + epoch_seed: Vec, + era: Vec, +} + +fn program_spec(k: u32) -> ProgramSpec { + let genesis = unhex(GENESIS_HEX).unwrap(); + match k { + 1 => ProgramSpec { label: "p1-devnet-epoch0".into(), epoch_seed: genesis.clone(), era: genesis }, + _ => { + let w = |s: String| -> Vec { seed_words_from_bytes(s.as_bytes()).iter().flat_map(|x| x.to_le_bytes()).collect() }; + ProgramSpec { + label: format!("p{k}-attack-f8"), + epoch_seed: w(format!("igneum-attack-f8/program/{k}")), + era: w(format!("igneum-attack-f8/era/{k}")), + } + } + } +} + +/// The mirror of `verify::interpret_warp_init` for class v4 (the ops a v4 program can hold), reading dataset words +/// from the item table and reporting every load's item index. +/// The mirror of `verify::interpret_warp_init` for class v4 (the ops a v4 program can hold), reading dataset words +/// from the item table and reporting every load's item index and, per load position, how many lanes' source +/// register was saturated (0 or all ones) at the load. +struct Mirror<'a> { + program: &'a Program, + era: Option<&'a EraParams>, + layout: Layout, + mask: u32, + log2: u32, + table: &'a Table, + plant_site: Option, +} + +const POS: usize = 128; + +struct Sink { + /// the item index of every load position, per lane + items: Vec<[u32; LANES]>, + /// the masked word index (the address before the layout split) of every load position, per lane + addrs: Vec<[u32; LANES]>, + /// lanes whose source register read 0 or 2^32 - 1 at the load, per position + sat: [u32; POS], +} + +impl Sink { + fn new() -> Sink { + Sink { items: Vec::with_capacity(POS), addrs: Vec::with_capacity(POS), sat: [0; POS] } + } +} + +impl<'a> Mirror<'a> { + #[inline(always)] + fn step(&self, ins: &Instr, r: &mut [[u32; LANES]; 8], sel: &[u32; LANES], sink: &mut Sink, site: &mut usize) { + let d = ins.dst as usize; + let a = ins.src as usize; + match ins.op { + Op::Add => { + let (imm, imm2, bit) = (ins.imm, ins.imm2, ins.bit as u32); + let src = r[a]; + for lane in 0..LANES { + let s = (sel[lane] >> bit) & 1; + let c = if s != 0 { imm2 } else { imm }; + r[d][lane] = r[d][lane].wrapping_add(src[lane]).wrapping_add(c); + } + } + Op::Sub => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] = r[d][lane].wrapping_sub(src[lane]); + } + } + Op::Mul => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] = r[d][lane].wrapping_mul(src[lane]); + } + } + Op::MulHi => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] = ((r[d][lane] as u64 * src[lane] as u64) >> 32) as u32; + } + } + Op::Xor => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] ^= src[lane]; + } + } + Op::Or => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] |= src[lane]; + } + } + Op::Rotl => { + let n = ins.rot; + for lane in 0..LANES { + r[d][lane] = r[d][lane].rotate_left(n); + } + } + Op::Rotr => { + let src = r[a]; + for lane in 0..LANES { + r[d][lane] = r[d][lane].rotate_right(src[lane] & 31); + } + } + Op::Mad => { + let src = r[a]; + let src2 = r[ins.src2 as usize]; + for lane in 0..LANES { + r[d][lane] = src[lane].wrapping_mul(src2[lane]).wrapping_add(r[d][lane]); + } + } + Op::Shfl => { + let src = r[a]; + let m = ins.mask as usize; + for lane in 0..LANES { + r[d][lane] ^= src[lane ^ m]; + } + } + Op::Load => { + assert_eq!(ins.width, 1, "class v4 loads one word"); + let mut ts = [0u32; LANES]; + let mut ws = [0u32; LANES]; + let p = sink.items.len(); + for lane in 0..LANES { + let x = r[a][lane]; + if x == 0 || x == u32::MAX { + sink.sat[p] += 1; + } + let idx = load_index(self.era, ins, x, self.mask, self.log2); + let (t, j) = self.layout.split(idx); + let t = if self.plant_site == Some(*site) { 0x00_1234 } else { t }; + ts[lane] = t; + ws[lane] = idx; + r[d][lane] ^= self.table.items[t as usize][j as usize]; + } + sink.items.push(ts); + sink.addrs.push(ws); + *site += 1; + } + Op::WLoad | Op::Scratch | Op::Hot => panic!("op {:?} is not a class v4 op", ins.op), + } + } + + /// Hashes of the warp at `base_nonce`; the sink carries the item index of every load (128 x 32). + fn warp(&self, base_nonce: u32, sink: &mut Sink) -> [u64; LANES] { + let seed = &self.program.seed; + let mut r = [[0u32; LANES]; 8]; + for lane in 0..LANES { + let nonce = base_nonce.wrapping_add(lane as u32); + for i in 0..8 { + let mut x = nonce ^ seed[i]; + x = x.wrapping_add(0x9e3779b9u32.wrapping_mul(i as u32 + 1)); + x = splitmix32(x); + r[i][lane] = x ^ seed[(i + 1) & 7]; + } + } + sink.items.clear(); + sink.addrs.clear(); + sink.sat = [0; POS]; + for _ in 0..ITERATIONS { + let sel = r[0]; + let mut site = 0usize; + for ins in &self.program.instrs { + self.step(ins, &mut r, &sel, sink, &mut site); + } + for _ in 0..self.program.shadow_reps() { + for ins in &self.program.shadow { + self.step(ins, &mut r, &sel, sink, &mut site); + } + } + } + let mut hashes = [0u64; LANES]; + for lane in 0..LANES { + let lo = r[0][lane] ^ r[1][lane].rotate_left(7) ^ r[2][lane].rotate_left(14) ^ r[3][lane].rotate_left(21); + let hi = r[4][lane] ^ r[5][lane].rotate_left(9) ^ r[6][lane].rotate_left(18) ^ r[7][lane].rotate_left(27); + hashes[lane] = ((hi as u64) << 32) | lo as u64; + } + hashes + } +} + +fn pct(h: &[u64], q: f64) -> usize { + let total: u64 = h.iter().sum(); + let want = (total as f64 * q).ceil().max(1.0) as u64; + let mut acc = 0u64; + for (i, c) in h.iter().enumerate() { + acc += c; + if acc >= want { + return i; + } + } + h.len() - 1 +} + +fn dist_line(label: &str, h: &[u64]) -> String { + let total: u64 = h.iter().sum(); + let min = h.iter().position(|&c| c > 0).unwrap_or(0); + let max = h.iter().rposition(|&c| c > 0).unwrap_or(0); + let mean = h.iter().enumerate().map(|(i, c)| i as f64 * *c as f64).sum::() / total.max(1) as f64; + format!( + "{label}: n {total} min {min} p1 {} median {} p99 {} max {max} mean {mean:.4}", + pct(h, 0.01), + pct(h, 0.5), + pct(h, 0.99) + ) +} + +/// The window of every load site as an item range: `(first item, items)`; the era window is the top `k` bits of +/// the 28-bit word index and every layout position lies below 16, so the window is the same aligned range of items. +fn site_item_windows(program: &Program, mask: u32, log2: u32) -> Vec<(u32, u32)> { + program + .instrs + .iter() + .filter(|i| i.op == Op::Load) + .map(|i| { + let (wm, off) = igneum_pow::verify::window(i, mask, log2); + (off >> 4, (wm >> 4) + 1) + }) + .collect() +} + +/// The expected reads per item under the window layer alone (every site uniform on its own window), as a density per +/// quarter of the item space (windows are the dataset, a half or a quarter, aligned). +fn window_density(windows: &[(u32, u32)], items_n: usize, reads_per_site: f64) -> [f64; 4] { + let q = items_n as u32 / 4; + let mut d = [0f64; 4]; + for &(first, n) in windows { + for (k, dq) in d.iter_mut().enumerate() { + let qs = k as u32 * q; + if qs >= first && qs < first + n { + *dq += reads_per_site / n as f64; + } + } + } + d +} + +/// Statistics of `counts` against a per-quarter expected density: chi-square per dof, the largest and smallest +/// bucket in sigma of their own expectation, buckets of `per` items. +fn stats_vs_density(counts: &[u64], dens: &[f64; 4], per: usize) -> (f64, f64, u64, usize, f64) { + let items_n = counts.len(); + let q = items_n / 4; + let mut chi2 = 0f64; + let mut z_max = f64::MIN; + let mut z_min = f64::MAX; + let mut max = 0u64; + let mut argmax = 0usize; + let mut dof = 0usize; + for (b, c) in counts.chunks(per).enumerate() { + let e = dens[(b * per) / q] * per as f64; + if e <= 0.0 { + continue; + } + let s: u64 = c.iter().sum(); + let z = (s as f64 - e) / e.sqrt(); + chi2 += z * z; + dof += 1; + if z > z_max { + z_max = z; + max = s; + argmax = b; + } + if z < z_min { + z_min = z; + } + } + (chi2 / (dof.max(2) - 1) as f64, z_max, max, argmax, z_min) +} + +/// The windowed control: `n` reads, site `i mod 16`, uniform on the site's window. +fn windowed_control(windows: &[(u32, u32)], n: u64, seed: u64, threads: usize, hist: &[AtomicU32]) { + std::thread::scope(|sc| { + for th in 0..threads { + let hist = &hist; + sc.spawn(move || { + let mut s = SplitMix64::new(seed ^ (th as u64).wrapping_mul(0x9E3779B97F4A7C15)); + let per = n / threads as u64 + if (th as u64) < n % threads as u64 { 1 } else { 0 }; + for i in 0..per { + let (first, cnt) = windows[(i % 16) as usize]; + let t = first + (s.next() as u32 & (cnt - 1)); + hist[t as usize].fetch_add(1, Ordering::Relaxed); + } + }); + } + }); +} + +struct ProgramSummary { + label: String, + id: u64, + attempt: u32, + /// X_f against the windowed control at f = 0.1%, 0.5%, 1% + x: [f64; 3], + hot: bool, + /// windowed chi2/dof of the 64-item buckets and the largest bucket in sigma + chi2_w: f64, + z_w: f64, + /// the largest share of one hi16 bucket (256 items) at any load position + max_bucket_share: f64, + /// the largest saturated-source share at any load position + max_sat_share: f64, + /// acceptance-style 2,048-evaluation metrics: the largest count of one address at one position, the largest + /// saturated-source count at one position + acc_max_addr: u32, + acc_max_sat: u32, + /// S_0.1% over the window-model control and over the flat control + ratio_w: f64, + ratio_flat: f64, + /// top-f shares measured and under the window-model control, f = 0.1% and 1% + s01: f64, + e01: f64, + s10: f64, + e10: f64, + /// the hottest item, its count, and the lossy site whose saturated (or one-bit-off) source maps to it, if any + hottest: u32, + hottest_count: u64, + hottest_source: String, + /// share of each site's reads that land on the top 0.1% of items (the attribution pass; zeros without --diag) + by_site_share: [f64; 16], +} + +#[allow(clippy::too_many_arguments)] +fn run_program( + spec: &ProgramSpec, + ds: DatasetSource, + table: &Table, + table_mism: u64, + day: u64, + nonces: u64, + threads: usize, + plant: Plant, + check_every: u64, + diag: bool, + verbose: bool, + out_dir: &str, +) -> (ProgramSummary, DatasetSource) { + let program = Epoch::chain_program(&spec.epoch_seed, Some(&spec.era), ProgramClass::V4, &spec.label); + assert_eq!(program.generator, 4); + assert_eq!(program.class.mixer_mult, 8); + assert_eq!(program.class.shadow.map(|s| (s.instrs, s.reps)), Some((256, 27))); + let era = program.class.era.expect("class v4 draws the era"); + let n_loads = program.instrs.iter().filter(|i| i.op == Op::Load).count(); + assert_eq!(n_loads, 16); + log!( + "program {}: epoch seed {} era seed {} id {:016x} attempt {} class {} op mix {}; era stride mul {:#010x} rot {} interleave {:?} windows {}", + spec.label, + hex(&spec.epoch_seed), + hex(&spec.era), + program.program_id(), + program.attempt, + program.class.name(), + program.op_mix(), + era.stride_mul, + era.stride_rot, + era.pos, + program.instrs.iter().enumerate().filter(|(_, i)| i.op == Op::Load).map(|(k, i)| format!("{k}:{}:{}", i.win, i.off)).collect::>().join(" ") + ); + let sites: Vec = program.instrs.iter().enumerate().filter(|(_, i)| i.op == Op::Load).map(|(k, _)| k).collect(); + if verbose { + // the static shape of every load site: its source register, the last base-program writer of that register + // before the site (cyclic) and whether that writer injects (add, sub, xor, mad, shfl, load), and how often the + // shadow block writes the register (the shadow runs between iteration i's instruction 63 and iteration i+1's + // instruction 0, outside the acceptance rule) + let instrs = &program.instrs; + for (k, ins) in instrs.iter().enumerate() { + if ins.op != Op::Load { + continue; + } + let src = ins.src; + let mut writer = String::from("none"); + let mut chain = Vec::new(); + for back in 1..instrs.len() { + let j = (k + instrs.len() - back) % instrs.len(); + if instrs[j].dst == src { + if writer == "none" { + writer = format!("{} at {j}", instrs[j].op.name()); + } + chain.push(format!("{}@{j}", instrs[j].op.name())); + if instrs[j].op.injects() { + break; + } + } + } + let mut sh: std::collections::BTreeMap<&str, usize> = std::collections::BTreeMap::new(); + for s in program.shadow.iter().filter(|s| s.dst == src) { + *sh.entry(s.op.name()).or_insert(0) += 1; + } + log!( + "load site instr {k}: src r{src} win {} off {}; last base writer {writer}; writers back to the last injecting one: {}; shadow writes of r{src} per rep: {}", + ins.win, + ins.off, + chain.join(" "), + sh.iter().map(|(o, n)| format!("{o}={n}")).collect::>().join(" ") + ); + } + } + let epoch = Epoch { program, dataset: ds }; + let plant_site = if plant == Plant::ConstItem { Some(0) } else { None }; + let mirror = Mirror { + program: &epoch.program, + era: epoch.program.class.era.as_ref(), + layout: epoch.program.class.layout(), + mask: epoch.dataset.mask, + log2: epoch.dataset.log2_words, + table, + plant_site, + }; + let items_n = 1usize << (DATASET_LOG2 - 4); + let windows = site_item_windows(&epoch.program, epoch.dataset.mask, epoch.dataset.log2_words); + let dens = window_density(&windows, items_n, nonces as f64 * 8.0); + log!( + "window layer: site item windows (first, items) {}; expected reads per item by quarter {:.3} {:.3} {:.3} {:.3} (flat uniform {:.3})", + windows.iter().map(|(f, n)| format!("({:#x},2^{})", f, n.trailing_zeros())).collect::>().join(" "), + dens[0], + dens[1], + dens[2], + dens[3], + nonces as f64 * 128.0 / items_n as f64 + ); + let item_hist = atomic_vec(items_n); + let pos_hi: Vec> = (0..POS).map(|_| atomic_vec(1 << 16)).collect(); + let pos_sat: Vec = atomic_vec(POS); + let warps = nonces / LANES as u64; + let next = AtomicUsize::new(0); + let checked = AtomicUsize::new(0); + let hash_mism = AtomicUsize::new(0); + let max_lines_hash = 8 * 128; + let max_lines_warp = max_lines_hash * LANES; + // the acceptance-style metric on the first 64 warps (2,048 evaluations): per position, the count of every + // masked address and of saturated sources + let acc = std::sync::Mutex::new((vec![std::collections::HashMap::::new(); POS], [0u32; POS])); + let t1 = Instant::now(); + let dists: Vec<(Vec, Vec, Vec, Vec)> = std::thread::scope(|sc| { + let mut hs = Vec::new(); + for _ in 0..threads { + let (next, checked, hash_mism, item_hist, mirror, epoch, pos_hi, pos_sat, acc) = + (&next, &checked, &hash_mism, &item_hist, &mirror, &epoch, &pos_hi, &pos_sat, &acc); + hs.push(sc.spawn(move || { + let mut lines_hash = vec![0u64; max_lines_hash + 1]; + let mut items_hash = vec![0u64; 129]; + let mut lines_warp = vec![0u64; max_lines_warp + 1]; + let mut items_warp = vec![0u64; 128 * LANES + 1]; + let mut sink = Sink::new(); + let mut lane_lines: Vec = Vec::with_capacity(max_lines_hash); + let mut lane_items: Vec = Vec::with_capacity(128); + let mut warp_lines: Vec = Vec::with_capacity(max_lines_warp); + let mut warp_items: Vec = Vec::with_capacity(128 * LANES); + loop { + let w = next.fetch_add(1, Ordering::Relaxed) as u64; + if w >= warps { + break; + } + let base = (w * LANES as u64) as u32; + let hashes = mirror.warp(base, &mut sink); + assert_eq!(sink.items.len(), POS); + if plant == Plant::None && (w < 64 || w % check_every == 0) { + let lib = epoch.hash_warp(base); + checked.fetch_add(1, Ordering::Relaxed); + if lib != hashes { + hash_mism.fetch_add(1, Ordering::Relaxed); + } + } + if w < 64 { + let mut g = acc.lock().unwrap(); + for p in 0..POS { + for lane in 0..LANES { + *g.0[p].entry(sink.addrs[p][lane]).or_insert(0) += 1; + } + g.1[p] += sink.sat[p]; + } + } + warp_lines.clear(); + warp_items.clear(); + for lane in 0..LANES { + lane_lines.clear(); + lane_items.clear(); + for load in &sink.items { + let t = load[lane]; + lane_items.push(t); + lane_lines.extend_from_slice(&mirror.table.lines[t as usize]); + } + warp_items.extend_from_slice(&lane_items); + warp_lines.extend_from_slice(&lane_lines); + lane_items.sort_unstable(); + lane_items.dedup(); + lane_lines.sort_unstable(); + lane_lines.dedup(); + items_hash[lane_items.len()] += 1; + lines_hash[lane_lines.len()] += 1; + } + for (p, load) in sink.items.iter().enumerate() { + for lane in 0..LANES { + let t = load[lane]; + item_hist[t as usize].fetch_add(1, Ordering::Relaxed); + pos_hi[p][(t >> 8) as usize].fetch_add(1, Ordering::Relaxed); + } + pos_sat[p].fetch_add(sink.sat[p], Ordering::Relaxed); + } + warp_items.sort_unstable(); + warp_items.dedup(); + warp_lines.sort_unstable(); + warp_lines.dedup(); + items_warp[warp_items.len()] += 1; + lines_warp[warp_lines.len()] += 1; + } + (lines_hash, items_hash, lines_warp, items_warp) + })); + } + hs.into_iter().map(|h| h.join().unwrap()).collect() + }); + let el = t1.elapsed().as_secs_f64(); + let mut lines_hash = vec![0u64; max_lines_hash + 1]; + let mut items_hash = vec![0u64; 129]; + let mut lines_warp = vec![0u64; max_lines_warp + 1]; + let mut items_warp = vec![0u64; 128 * LANES + 1]; + for (a, b, c, d) in &dists { + for (i, v) in a.iter().enumerate() { + lines_hash[i] += v; + } + for (i, v) in b.iter().enumerate() { + items_hash[i] += v; + } + for (i, v) in c.iter().enumerate() { + lines_warp[i] += v; + } + for (i, v) in d.iter().enumerate() { + items_warp[i] += v; + } + } + let ck = checked.load(Ordering::Relaxed); + let hm = hash_mism.load(Ordering::Relaxed); + log!( + "{} warps ({} nonces) interpreted in {el:.1} s ({:.3} ms per warp per thread); Epoch::hash_warp agreement on {ck} warps: {hm} mismatches{}", + warps, + warps * LANES as u64, + el * 1e3 * threads as f64 / warps as f64, + if plant != Plant::None { " (library comparison skipped under the plant)" } else { "" } + ); + if hm > 0 || table_mism > 0 { + log!("THE MIRROR DISAGREES WITH THE LIBRARY (hashes {hm}, items {table_mism}); nothing below is trusted"); + } + let tag = format!("warps-{}-d{day}-n{nonces}-{}", spec.label, plant.name()); + log!("{}", dist_line(&format!("{} distinct lines per hash", spec.label), &lines_hash)); + log!("{}", dist_line(&format!("{} distinct items per hash", spec.label), &items_hash)); + log!("{}", dist_line(&format!("{} distinct lines per warp", spec.label), &lines_warp)); + log!("{}", dist_line(&format!("{} distinct items per warp", spec.label), &items_warp)); + let exp = |k: f64, l: f64| l * (1.0 - (1.0 - 1.0 / l).powf(k)); + log!( + "uniform expectation: distinct lines per hash {:.3} of 1024 reads, per warp {:.1} of 32768 reads (2^22 lines); distinct items per hash {:.4} of 128, per warp {:.2} of 4096 (2^24 items)", + exp(1024.0, 4194304.0), + exp(32768.0, 4194304.0), + exp(128.0, 16777216.0), + exp(4096.0, 16777216.0) + ); + { + let mut f = std::fs::File::create(format!("{out_dir}/{tag}-distinct.txt")).unwrap(); + for (name, h) in [("lines_hash", &lines_hash), ("items_hash", &items_hash), ("lines_warp", &lines_warp), ("items_warp", &items_warp)] { + writeln!(f, "# distinct {name}: value count").unwrap(); + for (i, c) in h.iter().enumerate().filter(|(_, c)| **c > 0) { + writeln!(f, "{name} {i} {c}").unwrap(); + } + } + } + // per-position diagnostics: the largest hi16 bucket share and the saturated-source share + let mut max_bucket_share = 0f64; + let mut max_bucket_pos = 0usize; + let mut max_sat_share = 0f64; + let mut max_sat_pos = 0usize; + { + let mut f = std::fs::File::create(format!("{out_dir}/{tag}-positions.txt")).unwrap(); + writeln!(f, "# position iteration site instr max_hi16_bucket_share saturated_share").unwrap(); + for p in 0..POS { + let mx = pos_hi[p].iter().map(|x| x.load(Ordering::Relaxed)).max().unwrap_or(0) as f64 / nonces as f64; + let sat = pos_sat[p].load(Ordering::Relaxed) as f64 / nonces as f64; + writeln!(f, "{p} {} {} {} {:.6} {:.6}", p / 16, p % 16, sites[p % 16], mx, sat).unwrap(); + if mx > max_bucket_share { + max_bucket_share = mx; + max_bucket_pos = p; + } + if sat > max_sat_share { + max_sat_share = sat; + max_sat_pos = p; + } + } + } + log!( + "positions: largest hi16-bucket (256 items) share {:.4}% at p{} (iteration {}, site {}, instr {}); windowed expectation {:.4}%; largest saturated-source share {:.4}% at p{} (site {}, instr {})", + max_bucket_share * 100.0, + max_bucket_pos, + max_bucket_pos / 16, + max_bucket_pos % 16, + sites[max_bucket_pos % 16], + 100.0 * 256.0 / (windows[max_bucket_pos % 16].1 as f64), + max_sat_share * 100.0, + max_sat_pos, + max_sat_pos % 16, + sites[max_sat_pos % 16] + ); + // the acceptance-style metric over the first 2,048 evaluations + let (acc_max_addr, acc_max_sat, acc_pos, acc_sat_pos) = { + let g = acc.lock().unwrap(); + let mut ma = 0u32; + let mut mp = 0usize; + let mut ms = 0u32; + let mut msp = 0usize; + for p in 0..POS { + let m = g.0[p].values().copied().max().unwrap_or(0); + if m > ma { + ma = m; + mp = p; + } + if g.1[p] > ms { + ms = g.1[p]; + msp = p; + } + } + (ma, ms, mp, msp) + }; + log!( + "acceptance-style (2,048 evaluations, the rule's sample size): the most repeated address at one position {} of 2048 at p{} (site {}, instr {}); saturated sources at one position {} of 2048 at p{} (site {}, instr {}); uniform expectation: repeats 1 to 2, saturated 0", + acc_max_addr, + acc_pos, + acc_pos % 16, + sites[acc_pos % 16], + acc_max_sat, + acc_sat_pos, + acc_sat_pos % 16, + sites[acc_sat_pos % 16] + ); + // the cross-hash item histogram: flat statistics, the windowed null, the windowed control, the hot-set test + let snap = snapshot(&item_hist); + let s_items = stats_of(snap.iter().copied(), items_n); + log!("{}", s_items.line(&format!("{} item histogram (flat)", spec.label))); + let (chi2_w1, z_w1, max_w1, arg_w1, zmin_w1) = stats_vs_density(&snap, &dens, 1); + let (chi2_w, z_w, max_w, arg_w, zmin_w) = stats_vs_density(&snap, &dens, 64); + log!( + "{} item histogram against the window density: full 2^24 chi2/dof {:.5} largest {} (item {:#x}) at {:+.2} sigma smallest at {:+.2}; buckets64 chi2/dof {:.5} largest {} (bucket {}) at {:+.2} sigma smallest at {:+.2}", + spec.label, + chi2_w1, + max_w1, + arg_w1, + z_w1, + zmin_w1, + chi2_w, + max_w, + arg_w, + z_w, + zmin_w + ); + let total_reads = nonces * 128; + let ctrl = atomic_vec(items_n); + windowed_control(&windows, total_reads, 0xF8_3333 ^ program_hash(&spec.label), threads, &ctrl); + let cs = snapshot(&ctrl); + let c_items = stats_of(cs.iter().copied(), items_n); + let (c_chi2_w, c_z_w, _, _, c_zmin_w) = stats_vs_density(&cs, &dens, 64); + let (c_chi2_w1, c_z_w1, _, _, _) = stats_vs_density(&cs, &dens, 1); + log!("{}", c_items.line(&format!("{} WINDOWED CONTROL item histogram (flat)", spec.label))); + log!( + "{} WINDOWED CONTROL against the window density: full chi2/dof {:.5} largest {:+.2} sigma; buckets64 chi2/dof {:.5} largest {:+.2} smallest {:+.2}", + spec.label, + c_chi2_w1, + c_z_w1, + c_chi2_w, + c_z_w, + c_zmin_w + ); + let f1 = z_w > 6.0 || zmin_w < -6.0; + log!( + "6-sigma {} items buckets64 against the window density: largest bucket {:+.2} sigma, smallest {:+.2} -> {}", + spec.label, + z_w, + zmin_w, + if f1 { "FLAGGED (beyond 6 sigma)" } else { "within 6 sigma" } + ); + let f3 = hot_set_test(&format!("{} items (windowed control)", spec.label), &s_items, &c_items); + let flat = atomic_vec(items_n); + control(items_n, total_reads, 0xF8_4444 ^ program_hash(&spec.label), threads, &flat); + let fs = snapshot(&flat); + let f_items = stats_of(fs.iter().copied(), items_n); + log!("{}", f_items.line(&format!("{} FLAT CONTROL item histogram", spec.label))); + let _ = hot_set_test(&format!("{} items (flat control, the auditor's first view)", spec.label), &s_items, &f_items); + let mut x = [0f64; 3]; + let mut ratio_w = [0f64; 3]; + let mut ratio_flat = [0f64; 3]; + for k in 0..3 { + x[k] = s_items.top[k].1 - c_items.top[k].1; + ratio_w[k] = s_items.top[k].1 / c_items.top[k].1; + ratio_flat[k] = s_items.top[k].1 / f_items.top[k].1; + } + log!( + "ratio {}: top 0.1% / 0.5% / 1% share over the WINDOW-MODEL control {:.4}x / {:.4}x / {:.4}x (gate 1.2x at 0.1%: {}); over the FLAT control {:.4}x / {:.4}x / {:.4}x", + spec.label, + ratio_w[0], + ratio_w[1], + ratio_w[2], + if ratio_w[0] <= 1.2 { "within" } else { "BEYOND" }, + ratio_flat[0], + ratio_flat[1], + ratio_flat[2] + ); + { + let mut g = std::fs::File::create(format!("{out_dir}/{tag}-items.u32le")).unwrap(); + let mut bytes = Vec::with_capacity(items_n * 4); + for c in &snap { + bytes.extend_from_slice(&(*c as u32).to_le_bytes()); + } + g.write_all(&bytes).unwrap(); + } + let mut by_site_share = [0f64; 16]; + if diag { + // attribution: the hottest items (the top 0.1% by count, and the top 8) traced back to the load positions + let want = (items_n as f64 / 1000.0).round() as u64; + let mut coc: std::collections::BTreeMap = std::collections::BTreeMap::new(); + for &c in &snap { + *coc.entry(c).or_insert(0) += 1; + } + let mut left = want; + let mut thr = 0u64; + for (&c, &n) in coc.iter().rev() { + thr = c; + if n >= left { + break; + } + left -= n; + } + let mut hot = vec![0u8; items_n]; + let mut n_hot = 0u64; + let mut hot_reads = 0u64; + for (t, &c) in snap.iter().enumerate() { + if c >= thr { + hot[t] = 1; + n_hot += 1; + hot_reads += c; + } + } + let mut top8: Vec<(u64, usize)> = snap.iter().enumerate().map(|(t, &c)| (c, t)).collect(); + top8.sort_unstable_by(|a, b| b.cmp(a)); + top8.truncate(8); + log!( + "attribution: hot threshold count >= {thr} marks {n_hot} items ({:.4}% of items) holding {hot_reads} reads ({:.4}% of reads); top 8 items {}", + n_hot as f64 * 100.0 / items_n as f64, + hot_reads as f64 * 100.0 / total_reads as f64, + top8.iter().map(|(c, t)| format!("t={t:#08x}:{c}")).collect::>().join(" ") + ); + let per_pos: Vec = (0..POS).map(|_| std::sync::atomic::AtomicU64::new(0)).collect(); + let per_top: Vec> = (0..8).map(|_| (0..POS).map(|_| std::sync::atomic::AtomicU64::new(0)).collect()).collect(); + let next2 = AtomicUsize::new(0); + std::thread::scope(|sc| { + for _ in 0..threads { + let (next2, mirror, hot, top8, per_pos, per_top) = (&next2, &mirror, &hot, &top8, &per_pos, &per_top); + sc.spawn(move || { + let mut sink = Sink::new(); + loop { + let w = next2.fetch_add(1, Ordering::Relaxed) as u64; + if w >= warps { + break; + } + mirror.warp((w * LANES as u64) as u32, &mut sink); + for (p, load) in sink.items.iter().enumerate() { + for lane in 0..LANES { + let t = load[lane] as usize; + if hot[t] != 0 { + per_pos[p].fetch_add(1, Ordering::Relaxed); + } + for (k, (_, tt)) in top8.iter().enumerate() { + if *tt == t { + per_top[k][p].fetch_add(1, Ordering::Relaxed); + } + } + } + } + } + }); + } + }); + let expect = n_hot as f64 / items_n as f64; + let mut rows: Vec<(usize, u64)> = per_pos.iter().enumerate().map(|(p, c)| (p, c.load(Ordering::Relaxed))).collect(); + rows.sort_unstable_by(|a, b| b.1.cmp(&a.1)); + log!( + "attribution: reads on hot items per position (flat expectation {:.4}% of each position's {nonces} reads); the 24 largest: {}", + expect * 100.0, + rows.iter().take(24).map(|(p, c)| format!("p{p}(it{} s{})={:.3}%", p / 16, p % 16, *c as f64 * 100.0 / nonces as f64)).collect::>().join(" ") + ); + let mut by_iter = vec![0u64; ITERATIONS]; + let mut by_site = vec![0u64; 16]; + for (p, c) in &rows { + by_iter[p / 16] += c; + by_site[p % 16] += c; + } + for s in 0..16 { + by_site_share[s] = by_site[s] as f64 / (nonces * 8) as f64; + } + log!( + "attribution: hot reads by iteration {} ; by site {}", + by_iter.iter().enumerate().map(|(i, c)| format!("it{i}={:.3}%", *c as f64 * 100.0 / (nonces * 16) as f64)).collect::>().join(" "), + by_site.iter().enumerate().map(|(i, c)| format!("s{i}={:.3}%", *c as f64 * 100.0 / (nonces * 8) as f64)).collect::>().join(" ") + ); + // per-site contribution table: window, share of the site's reads into the top 0.1% set, the entropy of the + // site's item index over nonces (hi16 buckets of 256 items: a window of 2^(24 - k_off) items is 2^(16 - k_off) + // buckets, so uniform on the window = 16 - k_off bits; the runs of 09:04 UTC printed 16 - 2 k_off by mistake), + // saturated-source share, largest bucket share + let load_instrs: Vec<&Instr> = epoch.program.instrs.iter().filter(|i| i.op == Op::Load).collect(); + for s in 0..16 { + let mut acc = vec![0u64; 1 << 16]; + let mut hot_s = 0u64; + let mut sat_s = 0u64; + for it in 0..ITERATIONS { + let p = it * 16 + s; + for (b, c) in pos_hi[p].iter().enumerate() { + acc[b] += c.load(Ordering::Relaxed) as u64; + } + hot_s += per_pos[p].load(Ordering::Relaxed); + sat_s += pos_sat[p].load(Ordering::Relaxed) as u64; + } + let n_s = (nonces * ITERATIONS as u64) as f64; + let mut h = 0f64; + let mut mx = 0u64; + for &c in &acc { + if c > 0 { + let pr = c as f64 / n_s; + h -= pr * pr.log2(); + } + mx = mx.max(c); + } + let ins = load_instrs[s]; + log!( + "site {s} (instr {}, src r{}, k_off {} offset {}, window 2^{} items): share of its reads into the top 0.1% {:.4}% (flat expectation {:.4}%); index entropy {:.3} bits of {} uniform-on-window; saturated source {:.4}%; largest 256-item bucket {:.4}% (window expectation {:.4}%)", + sites[s], + ins.src, + ins.win, + ins.off, + windows[s].1.trailing_zeros(), + hot_s as f64 * 100.0 / n_s, + expect * 100.0, + h, + 16 - ins.win.min(2) as u32, + sat_s as f64 * 100.0 / n_s, + mx as f64 * 100.0 / n_s, + 100.0 * 256.0 / windows[s].1 as f64 + ); + } + for (k, (c, t)) in top8.iter().enumerate() { + let mut pos: Vec<(usize, u64)> = per_top[k].iter().enumerate().map(|(p, x)| (p, x.load(Ordering::Relaxed))).filter(|(_, x)| *x > 0).collect(); + pos.sort_unstable_by(|a, b| b.1.cmp(&a.1)); + log!( + "attribution: item {t:#08x} ({c} reads) read from positions {}", + pos.iter().take(12).map(|(p, x)| format!("p{p}(it{} s{})x{x}", p / 16, p % 16)).collect::>().join(" ") + ); + } + } + // the hottest item and the lossy site whose saturated source maps to it under the era map (all ones, zero, or + // one bit off either), with that site's source register and its last base-program writer + let (hottest, hottest_count) = snap.iter().enumerate().map(|(t, &c)| (t as u32, c)).max_by_key(|&(_, c)| c).unwrap(); + let hottest_source = { + let instrs = &epoch.program.instrs; + let layout = epoch.program.class.layout(); + let era = epoch.program.class.era.as_ref(); + let mut found = String::from("none"); + for (k, ins) in instrs.iter().enumerate() { + if ins.op != Op::Load { + continue; + } + let img = |x: u32| layout.split(load_index(era, ins, x, epoch.dataset.mask, epoch.dataset.log2_words)).0; + let mut cands: Vec<(u32, &str)> = vec![(u32::MAX, "all-ones"), (0, "zero")]; + for b in 0..32 { + cands.push((u32::MAX ^ (1 << b), "one-zero-bit")); + cands.push((1 << b, "one-one-bit")); + } + if let Some((_, what)) = cands.iter().find(|(x, _)| img(*x) == hottest) { + let mut writer = String::from("none"); + for back in 1..instrs.len() { + let j = (k + instrs.len() - back) % instrs.len(); + if instrs[j].dst == ins.src { + writer = format!("{}@{j}", instrs[j].op.name()); + break; + } + } + found = format!("site-instr{k}:r{}:{what}:last-writer-{writer}", ins.src); + break; + } + } + found + }; + log!( + "hottest item {} ({:#08x}): {} reads ({:.4}% of all); predicted source {}", + spec.label, + hottest, + hottest_count, + hottest_count as f64 * 100.0 / total_reads as f64, + hottest_source + ); + let hot = f3; + log!( + "program {} DONE: {} nonces; 6-sigma (windowed) {}; hot-set {}; verdict {}", + spec.label, + nonces, + if f1 { "FLAGGED" } else { "clear" }, + if hot { "FLAGGED" } else { "clear" }, + if f1 || hot { "FLAGGED" } else { "PASS (no test fired)" } + ); + let summary = ProgramSummary { + label: spec.label.clone(), + id: epoch.program.program_id(), + attempt: epoch.program.attempt, + x, + hot, + chi2_w, + z_w, + max_bucket_share, + max_sat_share, + acc_max_addr, + acc_max_sat, + ratio_w: ratio_w[0], + ratio_flat: ratio_flat[0], + s01: s_items.top[0].1, + e01: c_items.top[0].1, + s10: s_items.top[2].1, + e10: c_items.top[2].1, + hottest, + hottest_count, + hottest_source, + by_site_share, + }; + (summary, epoch.dataset) +} + +fn program_hash(label: &str) -> u64 { + igneum_pow::seed::fnv1a64(label.as_bytes()) +} + +#[allow(clippy::too_many_arguments)] +fn census_warps(programs: &[u32], day: u64, nonces: u64, threads: usize, plant: Plant, validate: &str, check_every: u64, diag: bool, out_dir: &str) { + log!( + "census warps: programs {:?} day {day} nonces {nonces} threads {threads} plant {} validate {validate} check_every {check_every} diag {diag}", + programs, + plant.name() + ); + let t0 = Instant::now(); + let mut ds: DatasetSource = Epoch::chain_dataset_day(&day_bytes(day), ProgramClass::V4, 0, DATASET_LOG2); + assert_eq!(ds.log2_words, DATASET_LOG2); + let (table, table_mism) = { + let mh = ds.memhard().unwrap(); + log!("day {day}: cache filled in {:.2} s, fnv {:016x}", t0.elapsed().as_secs_f64(), mh.cache.fnv1a64()); + build_table(&mh.params, &mh.cache, threads, plant, validate) + }; + let verbose = programs.len() <= 3; + let mut summaries = Vec::new(); + for &k in programs { + let spec = program_spec(k); + let (s, d) = run_program(&spec, ds, &table, table_mism, day, nonces, threads, plant, check_every, diag, verbose, out_dir); + ds = d; + summaries.push(s); + } + if programs.len() > 1 { + // the re-gate format (Counter ASIC lane, 7 October 2026): one line per seed, then PASS or FAIL against 1.2x + let mut over = Vec::new(); + for s in &summaries { + log!( + "SEED {} id {:016x} attempt {} S0.1 {:.5}% null0.1 {:.5}% ratio {:.4}x S1.0 {:.5}% null1.0 {:.5}% hottest {:#08x}:{} source {} by-site {}", + s.label, + s.id, + s.attempt, + s.s01 * 100.0, + s.e01 * 100.0, + s.ratio_w, + s.s10 * 100.0, + s.e10 * 100.0, + s.hottest, + s.hottest_count, + s.hottest_source, + if diag { s.by_site_share.iter().enumerate().map(|(i, x)| format!("s{i}={:.3}%", x * 100.0)).collect::>().join(" ") } else { "(no --diag)".to_string() } + ); + if s.ratio_w > 1.2 { + over.push(format!("{}({:.3}x)", s.label, s.ratio_w)); + } + } + log!( + "CENSUS {}: {} seeds at {} nonces, {} over 1.2x of the window model at the top 0.1%{}{}", + if over.is_empty() { "PASS" } else { "FAIL" }, + summaries.len(), + nonces, + over.len(), + if over.is_empty() { "" } else { ": " }, + over.join(" ") + ); + let mut f = std::fs::File::create(format!("{out_dir}/seed-census-d{day}-n{nonces}-p{}-{}.txt", programs[0], programs[programs.len() - 1])).unwrap(); + writeln!(f, "# label id attempt X_0.1% X_0.5% X_1% hot chi2w_buckets64 z_w max_hi16_bucket_share max_sat_share acc_max_addr_of_2048 acc_max_sat_of_2048 ratio_0.1%_window ratio_0.1%_flat").unwrap(); + let mut n_hot = 0usize; + let mut n_acc_addr = [0usize; 3]; + let mut n_acc_sat = [0usize; 3]; + let mut n_sixsig = 0usize; + let mut n_ratio = 0usize; + for s in &summaries { + n_ratio += (s.ratio_w > 1.2) as usize; + writeln!( + f, + "{} {:016x} {} {:.5} {:.5} {:.5} {} {:.4} {:.2} {:.6} {:.6} {} {} {:.4} {:.4}", + s.label, s.id, s.attempt, s.x[0] * 100.0, s.x[1] * 100.0, s.x[2] * 100.0, s.hot as u8, s.chi2_w, s.z_w, s.max_bucket_share, s.max_sat_share, s.acc_max_addr, s.acc_max_sat, s.ratio_w, s.ratio_flat + ) + .unwrap(); + n_hot += s.hot as usize; + n_sixsig += (s.z_w > 6.0) as usize; + for (i, thr) in [3u32, 11, 21].into_iter().enumerate() { + n_acc_addr[i] += (s.acc_max_addr >= thr) as usize; + n_acc_sat[i] += (s.acc_max_sat >= thr) as usize; + } + } + log!( + "SEED CENSUS: {} programs at {} nonces each: hot set (X_f >= f, windowed control) in {}; top 0.1% beyond 1.2x of the window-model control in {}; windowed buckets64 beyond 6 sigma in {}; acceptance-style most-repeated address at one position >= 3 / 11 / 21 of 2048 in {} / {} / {}; saturated sources at one position >= 3 / 11 / 21 of 2048 in {} / {} / {}", + summaries.len(), + nonces, + n_hot, + n_ratio, + n_sixsig, + n_acc_addr[0], + n_acc_addr[1], + n_acc_addr[2], + n_acc_sat[0], + n_acc_sat[1], + n_acc_sat[2] + ); + let mut by_x: Vec<&ProgramSummary> = summaries.iter().collect(); + by_x.sort_by(|a, b| b.x[0].partial_cmp(&a.x[0]).unwrap()); + for s in by_x.iter().take(10) { + log!( + "SEED CENSUS top: {} id {:016x} attempt {} ratio_0.1% window {:.3}x flat {:.3}x X_0.1% {:+.4}% X_1% {:+.4}% hot {} z_w {:+.1} max bucket share {:.4}% max sat share {:.4}% acc addr {} acc sat {}", + s.label, + s.id, + s.attempt, + s.ratio_w, + s.ratio_flat, + s.x[0] * 100.0, + s.x[2] * 100.0, + s.hot as u8, + s.z_w, + s.max_bucket_share * 100.0, + s.max_sat_share * 100.0, + s.acc_max_addr, + s.acc_max_sat + ); + } + } +} +fn usage() -> ! { + eprintln!( + "attack-f8 lines --day0 20730 --days 16 --items-log2 24 --threads 12 --out [--plant none|quarter-lines|half-lines] [--validate all|sample|none]\n\ + attack-f8 warps --program 1|2|3 | --programs a..b --day 20730 --nonces 1000000 --threads 12 --out [--plant none|const-item] [--validate all|sample|none] [--check-every 997] [--diag 1]\n\ + attack-f8 census --seeds 64 --nonces 16777216 --control window --by-site --threads 12 --out the re-gate: one line per seed, PASS/FAIL against 1.2x\n\ + attack-f8 static --programs 1..67 per program, every load site's write chain back to the last injecting write (rule (a'))\n\ + attack-f8 seeds print the three programs' seeds" + ); + std::process::exit(2); +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() < 2 { + usage(); + } + let get = |k: &str, d: &str| -> String { + let mut i = 2; + while i + 1 < args.len() { + if args[i] == k { + return args[i + 1].clone(); + } + i += 1; + } + d.to_string() + }; + let threads: usize = get("--threads", "12").parse().unwrap(); + let out = get("--out", "."); + let plant = Plant::parse(&get("--plant", "none")); + let validate = get("--validate", "all"); + log!("attack-f8 {} (igneum-pow {}); args {:?}", env!("CARGO_PKG_VERSION"), igneum_pow::generator::GENERATOR_VERSION_V4, &args[1..]); + match args[1].as_str() { + "lines" => census_lines( + get("--day0", &DEFAULT_DAY.to_string()).parse().unwrap(), + get("--days", "16").parse().unwrap(), + get("--items-log2", "24").parse().unwrap(), + threads, + plant, + &validate, + &out, + ), + "warps" => census_warps( + &{ + let ps = get("--programs", ""); + if ps.is_empty() { + vec![get("--program", "1").parse::().unwrap()] + } else { + let (a, b) = ps.split_once("..").expect("--programs a..b"); + (a.parse::().unwrap()..=b.parse::().unwrap()).collect::>() + } + }, + get("--day", &DEFAULT_DAY.to_string()).parse().unwrap(), + get("--nonces", "1000000").parse().unwrap(), + threads, + plant, + &validate, + get("--check-every", "997").parse().unwrap(), + get("--diag", "0") == "1", + &out, + ), + "census" => { + // the re-gate entry: `census --seeds 64 --nonces 16777216 --control window --by-site` runs programs 2..=65 + // (the chain-shaped tag seeds; `--programs a..b` overrides) with the window-model control (the only control + // the hot-set test uses; the flat control is printed beside it) and the by-site attribution + let ps = get("--programs", ""); + let programs: Vec = if ps.is_empty() { + let n: u32 = get("--seeds", "64").parse().unwrap(); + (2..=n + 1).collect() + } else { + let (a, b) = ps.split_once("..").expect("--programs a..b"); + (a.parse::().unwrap()..=b.parse::().unwrap()).collect() + }; + let by_site = args.iter().any(|a| a == "--by-site") || get("--diag", "0") == "1"; + census_warps( + &programs, + get("--day", &DEFAULT_DAY.to_string()).parse().unwrap(), + get("--nonces", "16777216").parse().unwrap(), + threads, + plant, + &validate, + get("--check-every", "997").parse().unwrap(), + by_site, + &out, + ); + } + "static" => { + // per program: for every load site, the ops that write its source register between the register's last + // injecting write (add, sub, xor, mad, shfl, load) and the load, walking back cyclically; the proposed + // rule (a') rejects a program with an `or` or a `mul` in any such chain + let ps = get("--programs", "1..3"); + let (a, b) = ps.split_once("..").expect("--programs a..b"); + println!("# label id attempt sites_with_or sites_with_mul sites_with_mulhi sites_with_rot_only sites_clean rule_a_prime chains"); + for k in a.parse::().unwrap()..=b.parse::().unwrap() { + let spec = program_spec(k); + let program = Epoch::chain_program(&spec.epoch_seed, Some(&spec.era), ProgramClass::V4, &spec.label); + let instrs = &program.instrs; + let (mut n_or, mut n_mul, mut n_mulhi, mut n_rot, mut n_clean) = (0, 0, 0, 0, 0); + let mut chains = Vec::new(); + for (k, ins) in instrs.iter().enumerate() { + if ins.op != Op::Load { + continue; + } + let mut ops = Vec::new(); + for back in 1..instrs.len() { + let j = (k + instrs.len() - back) % instrs.len(); + if instrs[j].dst == ins.src { + if instrs[j].op.injects() { + ops.push(format!("{}@{j}", instrs[j].op.name())); + break; + } + ops.push(format!("{}@{j}", instrs[j].op.name())); + } + } + let has = |o: Op| ops.iter().any(|x| x.starts_with(&format!("{}@", o.name()))); + if has(Op::Or) { + n_or += 1; + } else if has(Op::Mul) { + n_mul += 1; + } else if has(Op::MulHi) { + n_mulhi += 1; + } else if has(Op::Rotl) || has(Op::Rotr) { + n_rot += 1; + } else { + n_clean += 1; + } + // the items the saturated sources map to under the era map at the 2^28-word dataset + let mask = (1u32 << DATASET_LOG2) - 1; + let layout = program.class.layout(); + let img = |x: u32| layout.split(load_index(program.class.era.as_ref(), ins, x, mask, DATASET_LOG2)).0; + chains.push(format!("{k}:r{}:{}:ones={:#08x}:zero={:#08x}", ins.src, ops.join("<"), img(u32::MAX), img(0))); + } + println!( + "{} {:016x} {} {n_or} {n_mul} {n_mulhi} {n_rot} {n_clean} {} {}", + spec.label, + program.program_id(), + program.attempt, + if n_or + n_mul > 0 { "REJECT" } else { "accept" }, + chains.join(" ") + ); + } + } + "seeds" => { + for k in 1..=3 { + let s = program_spec(k); + println!("{} epoch_seed {} era {}", s.label, hex(&s.epoch_seed), hex(&s.era)); + } + } + _ => usage(), + } +} diff --git a/tools/attack/adv-accept/src/main.rs b/tools/attack/adv-accept/src/main.rs index 59e36d342..03411c952 100644 --- a/tools/attack/adv-accept/src/main.rs +++ b/tools/attack/adv-accept/src/main.rs @@ -19,7 +19,7 @@ //! lossily-written register; the rule MUST now reject it (UnfreshLoadSource, or the ratio). //! If the rule does not fire, the harness is not reading the rule and the sweep is void. -use igneum_pow::accept::{check, distinct_ratio_pass, AcceptReport, Reject, ACCEPT_DATASET_LOG2, MIN_DISTINCT_RATIO_V4}; +use igneum_pow::accept::{check, distinct_ratio_pass, Reject, ACCEPT_DATASET_LOG2, MIN_DISTINCT_RATIO_V4}; use igneum_pow::bind::{hex, unhex}; use igneum_pow::generator::{generate_era, Op, Program, ProgramClass, V3_ALLOWED, V4_CLASS}; use igneum_pow::seed::seed_words_from_bytes; @@ -30,10 +30,13 @@ use std::time::Instant; const GENESIS_HEX: &str = "edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07"; -/// 32 epoch/era bytes for sweep index k: the little-endian words of a label-derived seed, as the -/// attack-pass F8 harness builds its chain-shaped seeds. +/// 32 epoch/era bytes for sweep index k: the little-endian words of a label-derived seed, EXACTLY the +/// attack-pass F8 harness's chain-shaped seeds ("igneum-attack-f8/program/k" and ".../era/k"), so the +/// unmodified f8 harness (built here as adv-live) runs the same program as `adv-live warps --program k` +/// for every k this sweep reports. k = 1 is f8's shared devnet epoch 0 and is not in this seed space. fn seed_bytes(tag: &str, k: u64) -> Vec { - seed_words_from_bytes(format!("igneum-adv-accept/{tag}/{k}").as_bytes()) + let tag = if tag == "epoch" { "program" } else { tag }; + seed_words_from_bytes(format!("igneum-attack-f8/{tag}/{k}").as_bytes()) .iter() .flat_map(|x| x.to_le_bytes()) .collect() @@ -101,58 +104,118 @@ struct Row { k: u64, attempt: u32, id: u64, + /// closed-form distinct-item mean per hash from the rule's own report (only with --full; else NaN) distinct_mean: f64, min_ratio: f64, min_ratio_site: usize, - saturated: u32, - bias_max: u32, + /// Q5 structure: output registers whose last base-program write (cyclic) is or/mul/mulhi + lossy_last: u8, + /// Q5 structure: or, mul, mulhi counts in the base program + n_or: u8, + n_mul: u8, + n_mulhi: u8, + /// distinct registers written by the base program (register-set collapse if under 8) + regs_written: u8, last_resort: bool, } +/// Cheap static shape counts over the base program (Q5). +fn structure(p: &Program) -> (u8, u8, u8, u8, u8) { + let n = p.instrs.len(); + let mut lossy_last = 0u8; + for reg in 0..8u8 { + // the last write to reg in program order is the one the fold reads (the shadow runs after 63 too, + // so a shadow write of reg later would supersede it; counted on the base program as the proxy) + let mut last: Option = None; + for i in &p.instrs { + if i.dst == reg { + last = Some(i.op); + } + } + for i in &p.shadow { + if i.dst == reg { + last = Some(i.op); + } + } + if matches!(last, Some(Op::Or) | Some(Op::Mul) | Some(Op::MulHi)) { + lossy_last += 1; + } + } + let count = |o: Op| p.instrs.iter().filter(|i| i.op == o).count() as u8; + let mut w = [false; 8]; + for i in &p.instrs { + w[i.dst as usize] = true; + } + let _ = n; + (lossy_last, count(Op::Or), count(Op::Mul), count(Op::MulHi), w.iter().filter(|&&x| x).count() as u8) +} + fn sweep(args: &[String]) { let get = |k: &str, d: &str| -> String { args.windows(2).find(|w| w[0] == k).map(|w| w[1].clone()).unwrap_or_else(|| d.to_string()) }; + let seed_start: u64 = get("--seed-start", "0").parse().unwrap(); let seeds: u64 = get("--seeds", "1000").parse().unwrap(); let threads: usize = get("--threads", "12").parse().unwrap(); let ratio_units: usize = get("--ratio-units", "256").parse().unwrap(); + let full = args.iter().any(|a| a == "--full"); let out = get("--out", ""); println!( - "adv-accept sweep: {seeds} seeds, {threads} threads, ratio at {ratio_units} units per site (floor {MIN_DISTINCT_RATIO_V4} at 2^20, dataset 2^{ACCEPT_DATASET_LOG2})" + "adv-accept sweep: seeds {seed_start}..{} ({seeds}), {threads} threads, ratio at {ratio_units} units per site (floor {MIN_DISTINCT_RATIO_V4} at 2^20, dataset 2^{ACCEPT_DATASET_LOG2}), full report {full}", + seed_start + seeds ); let t0 = Instant::now(); let next = AtomicUsize::new(0); + let done = AtomicUsize::new(0); + let outfile = if out.is_empty() { None } else { Some(std::sync::Mutex::new(std::fs::File::create(&out).unwrap())) }; + if let Some(f) = &outfile { + writeln!(f.lock().unwrap(), "# seed attempt program_id distinct_mean min_ratio min_ratio_site lossy_last n_or n_mul n_mulhi regs_written last_resort").unwrap(); + } let rows: Vec> = std::thread::scope(|sc| { let mut hs = Vec::new(); for _ in 0..threads { - let next = &next; + let (next, done, outfile) = (&next, &done, &outfile); hs.push(sc.spawn(move || { let mut out = Vec::new(); loop { - let k = next.fetch_add(1, Ordering::Relaxed) as u64; - if k >= seeds { + let i = next.fetch_add(1, Ordering::Relaxed) as u64; + if i >= seeds { break; } + let k = seed_start + i; let epoch = seed_bytes("epoch", k); let era = seed_bytes("era", k); let label = format!("igneum-adv-accept/{k}"); let p = chain_v4(&label, &epoch, &era); - let verdict = check(&p); - let last_resort = verdict.is_err(); - let report = verdict.unwrap_or(AcceptReport::default()); - // the min per-site distinct-index ratio at `ratio_units` (cheap; floor 0 so it never errs) + // the chain path accepted this program (or handed the last resort at attempt 256); the full + // report costs a second 2^20 pass, so it is taken only with --full + let distinct_mean = if full { check(&p).map(|r| r.distinct_mean()).unwrap_or(f64::NAN) } else { f64::NAN }; let (min_ratio, min_site) = distinct_ratio_pass(&p, ratio_units, 0.0).unwrap_or((f64::NAN, 0)); - out.push(Row { + let (lossy_last, n_or, n_mul, n_mulhi, regs_written) = structure(&p); + let r = Row { k, attempt: p.attempt, id: p.program_id(), - distinct_mean: report.distinct_mean(), + distinct_mean, min_ratio, min_ratio_site: min_site, - saturated: report.saturated, - bias_max: report.bias_max, - last_resort, - }); + lossy_last, + n_or, + n_mul, + n_mulhi, + regs_written, + last_resort: p.attempt >= 256, + }; + if let Some(f) = outfile { + let mut g = f.lock().unwrap(); + writeln!(g, "{} {} {:016x} {:.5} {:.5} {} {} {} {} {} {} {}", r.k, r.attempt, r.id, r.distinct_mean, r.min_ratio, r.min_ratio_site, r.lossy_last, r.n_or, r.n_mul, r.n_mulhi, r.regs_written, r.last_resort as u8).unwrap(); + } + out.push(r); + let d = done.fetch_add(1, Ordering::Relaxed) + 1; + if d % 500 == 0 { + let el = t0.elapsed().as_secs_f64(); + println!("progress: {d} of {seeds} seeds in {el:.0} s ({:.2} seeds/s, {:.1} s per seed per thread)", d as f64 / el, el * threads as f64 / d as f64); + } } out })); @@ -165,49 +228,42 @@ fn sweep(args: &[String]) { let mean_attempt = all.iter().map(|r| r.attempt as f64).sum::() / n as f64; let max_attempt = all.iter().map(|r| r.attempt).max().unwrap_or(0); let last_resorts = all.iter().filter(|r| r.last_resort).count(); - let dmean = all.iter().map(|r| r.distinct_mean).sum::() / n as f64; - let dmin = all.iter().map(|r| r.distinct_mean).fold(f64::MAX, f64::min); let rmean = all.iter().map(|r| r.min_ratio).sum::() / n as f64; let rmin = all.iter().map(|r| r.min_ratio).fold(f64::MAX, f64::min); let below_floor = all.iter().filter(|r| r.min_ratio < MIN_DISTINCT_RATIO_V4).count(); + let mut attempt_hist = [0u64; 12]; + for r in &all { + attempt_hist[(r.attempt as usize).min(11)] += 1; + } println!( - "swept {n} seeds in {el:.1} s ({:.1} seeds/s/thread); attempts mean {mean_attempt:.3} max {max_attempt}; last-resort programs {last_resorts}", - n as f64 / el * threads as f64 - ); - println!( - "closed-form distinct-item mean per hash: mean {dmean:.4} min {dmin:.4} (ideal 128, rule floor mean > 120)" + "swept {n} seeds in {el:.1} s ({:.2} seeds/s, {:.1} s per seed per thread); attempts mean {mean_attempt:.3} max {max_attempt}; attempt histogram 0..10,11+ {:?}; last-resort programs {last_resorts}", + n as f64 / el, + el * threads as f64 / n as f64, + attempt_hist ); + if full { + let dmean = all.iter().map(|r| r.distinct_mean).sum::() / n as f64; + let dmin = all.iter().map(|r| r.distinct_mean).fold(f64::MAX, f64::min); + println!("closed-form distinct-item mean per hash: mean {dmean:.4} min {dmin:.4} (ideal 128, rule floor mean > 120)"); + } println!( "per-site distinct-index ratio at {ratio_units} units: mean {rmean:.4} min {rmin:.4}; {below_floor} of {n} below the 0.98 floor at this sample size (the floor is enforced at 2^20)" ); - all.sort_by(|a, b| a.distinct_mean.partial_cmp(&b.distinct_mean).unwrap()); - println!("lowest closed-form distinct-item mean (Q1/Q5 proxy):"); - for r in all.iter().take(10) { - println!( - " seed {} id {:016x} attempt {} distinct_mean {:.4} min_ratio {:.4} (site {}) saturated {} bias_max {}", - r.k, r.id, r.attempt, r.distinct_mean, r.min_ratio, r.min_ratio_site, r.saturated, r.bias_max - ); - } + let lossy = all.iter().filter(|r| r.lossy_last > 0).count(); + let collapse = all.iter().filter(|r| r.regs_written < 8).count(); + let mean_or = all.iter().map(|r| r.n_or as f64).sum::() / n as f64; + println!( + "Q5 structure over accepted programs: {lossy} of {n} with a lossy (or/mul/mulhi) last write to an output register; {collapse} with under 8 registers written; or per program mean {mean_or:.3}" + ); all.sort_by(|a, b| a.min_ratio.partial_cmp(&b.min_ratio).unwrap()); - println!("lowest per-site distinct-index ratio (headroom over the floor, Q5):"); - for r in all.iter().take(10) { + println!("lowest per-site distinct-index ratio (headroom over the floor, Q5; the live hot-set candidates for Q1):"); + for r in all.iter().take(20) { println!( - " seed {} id {:016x} attempt {} min_ratio {:.4} (site {}) distinct_mean {:.4}", - r.k, r.id, r.attempt, r.min_ratio, r.min_ratio_site, r.distinct_mean + " seed {} id {:016x} attempt {} min_ratio {:.4} (site {}) lossy_last {} or {} mul {} mulhi {} regs {}", + r.k, r.id, r.attempt, r.min_ratio, r.min_ratio_site, r.lossy_last, r.n_or, r.n_mul, r.n_mulhi, r.regs_written ); } if !out.is_empty() { - let mut f = std::fs::File::create(&out).unwrap(); - writeln!(f, "# seed attempt program_id distinct_mean min_ratio min_ratio_site saturated bias_max last_resort").unwrap(); - all.sort_by_key(|r| r.k); - for r in &all { - writeln!( - f, - "{} {} {:016x} {:.5} {:.5} {} {} {} {}", - r.k, r.attempt, r.id, r.distinct_mean, r.min_ratio, r.min_ratio_site, r.saturated, r.bias_max, r.last_resort as u8 - ) - .unwrap(); - } println!("wrote {out}"); } println!("sweep DONE");