{"challengeId":"keccak-preimage-rounds","entry":"verifier.mjs","sha256":"8971dbe4a48baa6fb25cd7f813e9d8021cb128a3d29d48066b92ca4a7988c93f","scoreDirection":"maximize","scoreLabel":"rounds inverted","seedPolicy":{"mode":"fixed","testCount":1},"source":"// FROZEN verifier for keccak-preimage-rounds. Self-contained (no imports): a full,\n// standard Keccak-f[1600] permutation in PURE BigInt + bitwise ops, exposed as a\n// round-reduced sponge KeccakRR(nr) with rate 1088 / capacity 512, pad10*1 with the\n// 0x06 SHA-3 domain byte, output truncated to 80 bits. Every lane is masked to 64\n// bits, so the permutation is byte-identical on every conforming engine — fully\n// deterministic, no transcendental Math, no non-integer ** (the determinism gate\n// forbids those). seedPolicy \"fixed\": the TARGET is a single hardcoded 80-bit value.\n//\n// SCORE = nr (the round count the submitted message inverts). The agent submits an\n// artifact { rounds: nr, msg: \"<hex>\" }; we run KeccakRR(nr) on msg, truncate to 80\n// bits, and require it equals the fixed TARGET. A baseline message hits TARGET at\n// nr0=2 rounds; beating it means finding a NEW message whose KeccakRR(3) (then 4,...)\n// against the SAME fixed TARGET — a genuine higher-round preimage search on the\n// round-reduced margin of SHA-3/Keccak. This is OmniBreak's own SHA-3-margin\n// foothold; it is NEVER a break of full 24-round Keccak.\n\nconst MASK64 = (1n << 64n) - 1n;\n\n// Keccak-f[1600] rho rotation offsets, indexed [x][y] (x = column, y = row),\n// the documented 24 nonzero offsets + the fixed point at (0,0)=0.\nconst RHO = [\n  [0n, 36n, 3n, 41n, 18n],\n  [1n, 44n, 10n, 45n, 2n],\n  [62n, 6n, 43n, 15n, 61n],\n  [28n, 55n, 25n, 21n, 56n],\n  [27n, 20n, 39n, 8n, 14n],\n];\n\n// The 24 Keccak-f iota round constants (RC[0..23]).\nconst RC = [\n  0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n,\n  0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,\n  0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an,\n  0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,\n  0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an,\n  0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n,\n];\n\nfunction rotl64(x, n) {\n  if (n === 0n) return x & MASK64;\n  return ((x << n) | (x >> (64n - n))) & MASK64;\n}\n\n// In-place Keccak-f over a 5x5 lane array `s` (s[x][y] is a 64-bit BigInt),\n// running exactly `nr` rounds (standard full permutation is nr = 24). Round\n// constants are taken from RC[0..nr-1] (the standard round schedule, truncated).\nfunction keccakF(s, nr) {\n  for (let round = 0; round < nr; round++) {\n    // theta\n    const C = [0n, 0n, 0n, 0n, 0n];\n    for (let x = 0; x < 5; x++) {\n      C[x] = s[x][0] ^ s[x][1] ^ s[x][2] ^ s[x][3] ^ s[x][4];\n    }\n    const D = [0n, 0n, 0n, 0n, 0n];\n    for (let x = 0; x < 5; x++) {\n      D[x] = C[(x + 4) % 5] ^ rotl64(C[(x + 1) % 5], 1n);\n    }\n    for (let x = 0; x < 5; x++) {\n      for (let y = 0; y < 5; y++) {\n        s[x][y] = s[x][y] ^ D[x];\n      }\n    }\n    // rho + pi: B[y][(2x+3y)%5] = rotl(s[x][y], RHO[x][y])\n    const B = [\n      [0n, 0n, 0n, 0n, 0n],\n      [0n, 0n, 0n, 0n, 0n],\n      [0n, 0n, 0n, 0n, 0n],\n      [0n, 0n, 0n, 0n, 0n],\n      [0n, 0n, 0n, 0n, 0n],\n    ];\n    for (let x = 0; x < 5; x++) {\n      for (let y = 0; y < 5; y++) {\n        B[y][(2 * x + 3 * y) % 5] = rotl64(s[x][y], RHO[x][y]);\n      }\n    }\n    // chi\n    for (let x = 0; x < 5; x++) {\n      for (let y = 0; y < 5; y++) {\n        s[x][y] = (B[x][y] ^ ((~B[(x + 1) % 5][y]) & B[(x + 2) % 5][y])) & MASK64;\n      }\n    }\n    // iota\n    s[0][0] = s[0][0] ^ RC[round];\n  }\n}\n\n// Convert 8 little-endian bytes (offset o) of `buf` into a 64-bit lane.\nfunction loadLane(buf, o) {\n  let v = 0n;\n  for (let i = 7; i >= 0; i--) {\n    v = (v << 8n) | BigInt(buf[o + i] & 0xff);\n  }\n  return v & MASK64;\n}\n\n// Round-reduced Keccak sponge. rate = 1088 bits = 136 bytes, capacity = 512 bits.\n// pad10*1 with the 0x06 SHA-3 domain separation byte. Absorbs `msgBytes`, runs\n// keccakF with `nr` rounds per permutation, and squeezes the first `outBytes`.\nfunction keccakRR(msgBytes, nr, outBytes) {\n  const RATE = 136; // bytes\n  // pad10*1 with domain 0x06: append 0x06, zero-fill, set top bit of last block byte.\n  const len = msgBytes.length;\n  const padLen = RATE - (len % RATE);\n  const padded = new Uint8Array(len + padLen);\n  for (let i = 0; i < len; i++) padded[i] = msgBytes[i] & 0xff;\n  padded[len] = 0x06;\n  padded[padded.length - 1] = padded[padded.length - 1] ^ 0x80;\n\n  // state lanes s[x][y], x = column, y = row; lane index = x + 5*y.\n  const s = [\n    [0n, 0n, 0n, 0n, 0n],\n    [0n, 0n, 0n, 0n, 0n],\n    [0n, 0n, 0n, 0n, 0n],\n    [0n, 0n, 0n, 0n, 0n],\n    [0n, 0n, 0n, 0n, 0n],\n  ];\n\n  for (let off = 0; off < padded.length; off += RATE) {\n    // absorb a rate block: 17 lanes (136 bytes) XORed into state\n    for (let lane = 0; lane < RATE / 8; lane++) {\n      const x = lane % 5;\n      const y = Math.floor(lane / 5);\n      s[x][y] = s[x][y] ^ loadLane(padded, off + lane * 8);\n    }\n    keccakF(s, nr);\n  }\n\n  // squeeze: emit lanes in order until outBytes collected (outBytes <= rate here)\n  const out = new Uint8Array(outBytes);\n  let produced = 0;\n  let lane = 0;\n  while (produced < outBytes) {\n    const x = lane % 5;\n    const y = Math.floor(lane / 5);\n    let v = s[x][y];\n    for (let b = 0; b < 8 && produced < outBytes; b++) {\n      out[produced++] = Number(v & 0xffn);\n      v = v >> 8n;\n    }\n    lane++;\n  }\n  return out;\n}\n\n// ----- the fixed challenge instance -----\n// TARGET = first 80 bits (10 bytes) of KeccakRR(2)(M0), M0 = the baseline message.\n// Hardcoded so the verifier is a frozen, self-contained oracle (no recomputation of\n// the baseline needed to know the target). 80 bits = 20 hex chars.\nconst TARGET = '43bf2e5026c61b59dff0';\n\nfunction gate(name, pass, detail) { return { name, pass, detail }; }\nfunction bad(name, detail) { return { ok: false, score: null, gates: [gate(name, false, detail)], behavior: {}, logs: [] }; }\n\nexport function verify(ctx) {\n  const sol = ctx.solution;\n  if (!sol || typeof sol !== 'object') {\n    return bad('structure', 'expected { rounds: <int 1..24>, msg: \"<hex>\" }');\n  }\n  const rounds = sol.rounds;\n  const msg = sol.msg;\n  if (!Number.isInteger(rounds) || rounds < 1 || rounds > 24) {\n    return bad('structure', 'rounds must be an integer in 1..24');\n  }\n  if (typeof msg !== 'string' || !/^[0-9a-fA-F]*$/.test(msg) || msg.length % 2 !== 0) {\n    return bad('structure', 'msg must be an even-length hex string');\n  }\n  // bound the message so the verifier stays far under timeoutMs (a few rate blocks).\n  if (msg.length > 1024) {\n    return bad('structure', 'msg must be at most 512 bytes (1024 hex chars)');\n  }\n  const bytes = new Uint8Array(msg.length / 2);\n  for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(msg.substr(i * 2, 2), 16);\n\n  const digest = keccakRR(bytes, rounds, 10); // 80 bits = 10 bytes\n  let hex = '';\n  for (const b of digest) hex += b.toString(16).padStart(2, '0');\n\n  if (hex !== TARGET) {\n    return bad('preimage', `KeccakRR(${rounds}) of msg gives ${hex}, which does not equal the fixed 80-bit TARGET ${TARGET}`);\n  }\n\n  return {\n    ok: true,\n    score: rounds,\n    gates: [gate('preimage', true, `KeccakRR(${rounds}) of msg equals the fixed 80-bit TARGET ${TARGET}`)],\n    behavior: { rounds },\n    logs: [`rounds=${rounds}`, `target=${TARGET}`],\n  };\n}\n"}