{"record":{"id":"b0bebe4e39e00f9c","repo":"gchq/CyberChef","slug":"rotor-wiring-must-have-each-letter-exactly-once","errorCode":null,"errorMessage":"Rotor wiring must have each letter exactly once","messagePattern":"Rotor wiring must have each letter exactly once","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/Enigma.mjs","lineNumber":116,"sourceCode":"        }\n        if (!/^[A-Z]$/.test(ringSetting)) {\n            throw new OperationError(\"Rotor ring setting must be exactly one uppercase letter\");\n        }\n        if (!/^[A-Z]$/.test(initialPosition)) {\n            throw new OperationError(\"Rotor initial position must be exactly one uppercase letter\");\n        }\n        this.map = new Array(26);\n        this.revMap = new Array(26);\n        const uniq = {};\n        for (let i=0; i<LETTERS.length; i++) {\n            const a = a2i(LETTERS[i]);\n            const b = a2i(wiring[i]);\n            this.map[a] = b;\n            this.revMap[b] = a;\n            uniq[b] = true;\n        }\n        if (Object.keys(uniq).length !== LETTERS.length) {\n            throw new OperationError(\"Rotor wiring must have each letter exactly once\");\n        }\n        const rs = a2i(ringSetting);\n        this.steps = new Set();\n        for (const x of steps) {\n            this.steps.add(Utils.mod(a2i(x) - rs, 26));\n        }\n        if (this.steps.size !== steps.length) {\n            // This isn't strictly fatal, but it's probably a mistake\n            throw new OperationError(\"Rotor steps must be unique\");\n        }\n        this.pos = Utils.mod(a2i(initialPosition) - rs, 26);\n    }\n\n    /**\n     * Step the rotor forward by one.\n     */\n    step() {\n        this.pos = Utils.mod(this.pos + 1, 26);","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/Enigma.mjs#L98-L134","documentation":"Thrown by the Rotor constructor after building the wiring map: although wiring passed the 26-uppercase-letters format check, the letter set is not a permutation of A-Z (some letters repeat, others are absent). The code collects unique output letters into an object and requires all 26 to appear exactly once, which is what makes the rotor a bijection.","triggerScenarios":"new Rotor(wiring, ...) where wiring is 26 uppercase letters but contains a duplicate (e.g. 'EKMFLGDQVZNTOWYHXUSPAIBRCE' with two E's and no J). The format regex passes but the uniqueness count fails.","commonSituations":"Hand-transcribed wiring with a typo duplicating a letter; copy-paste corruption; deliberately scrambled wiring that isn't a true permutation.","solutions":["Ensure wiring is a permutation of the full alphabet (each letter A-Z exactly once).","Validate new Set(wiring).size === 26 && wiring.length === 26 before constructing.","Use a known-good rotor definition from the ROTORS table."],"exampleFix":"// before\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCE', 'R', 'A', 'A'); // duplicate E, no J\n\n// after\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', 'R', 'A', 'A'); // valid permutation","handlingStrategy":"validation","validationCode":"function isPermutationOfAlphabet(wiring) {\n  return typeof wiring === \"string\" && wiring.length === 26 && new Set(wiring).size === 26 && /^[A-Z]+$/.test(wiring);\n}\nif (!isPermutationOfAlphabet(wiring)) {\n  throw new Error(\"Rotor wiring must be a permutation of A-Z.\");\n}","typeGuard":"function isRotorWiringPermutation(s) {\n  return typeof s === \"string\" && s.length === 26 && new Set(s).size === 26 && /^[A-Z]+$/.test(s);\n}","tryCatchPattern":null,"preventionTips":["Check Set size equals 26 in addition to the length/format regex.","Prefer canonical ROTORS definitions over hand-typed wiring.","Add a unit test asserting every letter A-Z appears once."],"tags":["enigma","cryptography","input-validation","rotor","permutation"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}