{"challengeId":"factor-ladder","entry":"verifier.mjs","sha256":"21b417e522b4cf25a312c512215d653a007211086119a1b369395bafc300a41b","scoreDirection":"maximize","scoreLabel":"bits cracked","seedPolicy":{"mode":"fixed","testCount":1},"source":"// Frozen verifier for factor-ladder. Self-contained (no imports): it interprets\n// the candidate's factorization artifact and CHECKS it with one BigInt multiply\n// per rung. It never executes agent code — build() already ran in the sandbox and\n// handed us plain data. This file is part of the challenge's PROTECTED surface.\n//\n// The ladder below is a list of semiprimes N = p·q of increasing bit-length. Only\n// the products N appear here — the factors live nowhere in this repo, so the only\n// way to score a rung is to actually factor it. Verification is the reverse\n// operation: multiply the two submitted factors and check they reproduce N, and\n// that both are non-trivial (1 < factor < N). For a semiprime that forces the\n// unique prime factorization. There is no hidden input and nothing to overfit, so\n// seedPolicy.mode is \"fixed\".\n\nconst LADDER = [\n  '158299',\n  '1887239',\n  '22274431',\n  '2869029997',\n  '679643626931',\n  '1664253828244663',\n  '18974200096015592173',\n  '6907132642534129823362367',\n  '1623420161345815554131108010501713',\n  '805318827767671345756034772998341755081604853',\n  '1041263386189651892179762797303349287751759431510718622018507',\n  '696498737605860082881106186216908705181149341083869583153488128963343811522643',\n];\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 parseBig(x) {\n  if (typeof x === 'number' && Number.isInteger(x)) return BigInt(x);\n  if (typeof x === 'string' && /^[0-9]+$/.test(x.trim())) return BigInt(x.trim());\n  return null;\n}\n\nexport function verify(ctx) {\n  const sol = ctx.solution;\n  const factors = sol && typeof sol === 'object' ? sol.factors : undefined;\n  if (!factors || typeof factors !== 'object') {\n    return bad('structure', 'expected { factors: { \"<rungIndex>\": [\"p\", \"q\"], ... } }');\n  }\n\n  let bestBits = 0;\n  let count = 0;\n  const cracked = [];\n  for (const key of Object.keys(factors)) {\n    const idx = Number(key);\n    if (!Number.isInteger(idx) || idx < 0 || idx >= LADDER.length) continue;\n    const pair = factors[key];\n    if (!Array.isArray(pair) || pair.length !== 2) continue;\n    const p = parseBig(pair[0]);\n    const q = parseBig(pair[1]);\n    if (p === null || q === null) continue;\n    const N = BigInt(LADDER[idx]);\n    // Non-trivial: both factors strictly between 1 and N, and they reproduce N.\n    if (p <= 1n || q <= 1n || p >= N || q >= N) continue;\n    if (p * q !== N) continue;\n    const bits = N.toString(2).length;\n    count++;\n    cracked.push(`rung ${idx} (${bits}b)`);\n    if (bits > bestBits) bestBits = bits;\n  }\n\n  const gates = [\n    gate('factors-at-least-one', count > 0, count > 0 ? `cracked ${count} rung(s): ${cracked.join(', ')}` : 'no rung was correctly factored'),\n  ];\n  const ok = gates.every((g) => g.pass);\n  return {\n    ok,\n    score: ok ? bestBits : null,\n    gates,\n    behavior: { bits: bestBits, count },\n    logs: [`cracked=${count} largest=${bestBits} bits`],\n  };\n}\n"}