{"challengeId":"preimage-arx","entry":"verifier.mjs","sha256":"0bcb6d9683f8f9308dd034bea7f7b6525b3eb0aa2d8433cc6d53dd76c16cc29f","scoreDirection":"maximize","scoreLabel":"zero bits","seedPolicy":{"mode":"fixed","testCount":1},"source":"// Frozen verifier for preimage-arx. Self-contained (no imports, no node:crypto):\n// it re-evaluates a fixed 6-round ARX mixer on the submitted 16-byte message and\n// scores the digest's leading zero bits. It never executes agent code — build()\n// already ran in the sandbox and handed us a plain hex string. This file is part\n// of the challenge's PROTECTED surface.\n//\n// Same checking-is-trivial / finding-is-hard asymmetry as factor-ladder: the\n// agent does the search OFFLINE on its own compute and submits only the witness.\n// One mixer evaluation is a few dozen integer ops, so verification costs\n// microseconds; pushing the score up costs exponentially more search — each\n// additional leading zero bit doubles the expected brute-force work. The mixer\n// is deliberately weakened (6 ARX rounds, 128-bit state), so structural attacks\n// — round inversion, meet-in-the-middle, SAT/SMT — may legitimately beat brute\n// force. The verifier accepts any witness regardless of how it was found.\n//\n// The mixer (fully specified in spec.md, integer-only, deterministic):\n//   - Load the 16 message bytes into four u32 words, little-endian.\n//   - 6 rounds; round r: a += RC[r]; then one ChaCha-style quarter-round ladder\n//     on (a,b,c,d) (add mod 2^32, xor, rotations 16/12/8/7); then rotate the\n//     word order (a,b,c,d) -> (b,c,d,a).\n//   - Digest = the four final words, word a most significant; score = leading\n//     zero bits of that 128-bit value.\n//   - RC = the first 6 words of the SHA-256 round-constant table (fractional\n//     parts of cube roots of the first primes) — fixed, public constants that\n//     keep the all-zero message from mapping to an all-zero digest.\n//\n// There is no hidden input and nothing to overfit, so seedPolicy.mode is \"fixed\".\n\nconst RC = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1];\nconst ROUNDS = 6;\n\nfunction gate(name, pass, detail) {\n  return { name, pass, detail };\n}\nfunction bad(name, detail) {\n  return { ok: false, score: null, gates: [gate(name, false, detail)], behavior: {}, logs: [] };\n}\n\nfunction rotl(x, n) {\n  return ((x << n) | (x >>> (32 - n))) >>> 0;\n}\n\nfunction mix(w0, w1, w2, w3) {\n  let a = w0 >>> 0;\n  let b = w1 >>> 0;\n  let c = w2 >>> 0;\n  let d = w3 >>> 0;\n  for (let r = 0; r < ROUNDS; r++) {\n    a = (a + RC[r]) >>> 0;\n    a = (a + b) >>> 0;\n    d = rotl(d ^ a, 16);\n    c = (c + d) >>> 0;\n    b = rotl(b ^ c, 12);\n    a = (a + b) >>> 0;\n    d = rotl(d ^ a, 8);\n    c = (c + d) >>> 0;\n    b = rotl(b ^ c, 7);\n    const t = a;\n    a = b;\n    b = c;\n    c = d;\n    d = t;\n  }\n  return [a, b, c, d];\n}\n\nexport function verify(ctx) {\n  const sol = ctx.solution;\n  const msg = sol && typeof sol === 'object' ? sol.msg : undefined;\n  if (typeof msg !== 'string' || !/^[0-9a-f]{32}$/.test(msg)) {\n    return bad('structure', 'expected { msg: \"<exactly 32 lowercase hex chars>\" } (a 16-byte message)');\n  }\n\n  // Decode 16 bytes, then four u32 words little-endian.\n  const bytes = [];\n  for (let i = 0; i < 32; i += 2) bytes.push(parseInt(msg.slice(i, i + 2), 16));\n  const w = [];\n  for (let k = 0; k < 4; k++) {\n    w.push((bytes[4 * k] | (bytes[4 * k + 1] << 8) | (bytes[4 * k + 2] << 16) | (bytes[4 * k + 3] << 24)) >>> 0);\n  }\n\n  const digest = mix(w[0], w[1], w[2], w[3]);\n\n  // Leading zero bits of the 128-bit digest, word 0 most significant.\n  let zeroBits = 0;\n  for (const x of digest) {\n    if (x === 0) {\n      zeroBits += 32;\n      continue;\n    }\n    zeroBits += Math.clz32(x);\n    break;\n  }\n\n  // Hamming weight of the message (a structure descriptor for the archive).\n  let msgWeight = 0;\n  for (let v of bytes) {\n    while (v) {\n      msgWeight += v & 1;\n      v >>>= 1;\n    }\n  }\n\n  const hex = digest.map((x) => x.toString(16).padStart(8, '0')).join('');\n  const gates = [gate('mixer-evaluated', true, `digest ${hex} has ${zeroBits} leading zero bits`)];\n  return {\n    ok: true,\n    score: zeroBits,\n    gates,\n    behavior: { zeroBits, msgWeight },\n    logs: [`digest=${hex} zeroBits=${zeroBits} msgWeight=${msgWeight}`],\n  };\n}\n"}