{"record":{"id":"ff869106d2550efa","repo":"gchq/CyberChef","slug":"rotor-steps-must-be-unique","errorCode":null,"errorMessage":"Rotor steps must be unique","messagePattern":"Rotor steps must be unique","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"warning","filePath":"src/core/lib/Enigma.mjs","lineNumber":125,"sourceCode":"        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);\n        return this.pos;\n    }\n\n    /**\n     * Transform a character through this rotor forwards.\n     *\n     * @param {number} c - The character.\n     * @returns {number}\n     */","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/Enigma.mjs#L107-L143","documentation":"Thrown by the Rotor constructor when, after normalising step letters against the ring setting, the resulting Set of step positions is smaller than the input steps string length — i.e. two distinct step letters map to the same turnover position. The comment notes this isn't strictly fatal but is treated as a probable mistake.","triggerScenarios":"new Rotor(wiring, steps, ringSetting, ...) where two step letters differ but Utils.mod(a2i(x) - rs, 26) collide. Also fires trivially when steps contains a repeated letter (e.g. 'RR'), since steps.length counts chars but Set dedupes.","commonSituations":"Duplicate letters in the steps string; passing a steps value derived from a malformed ROTORS entry; accidentally concatenating two rotor step strings.","solutions":["Remove duplicate letters from the steps string.","Confirm steps has no repeated characters before constructing (new Set(steps).size === steps.length).","Use step letters directly from the canonical ROTORS definitions."],"exampleFix":"// before\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', 'RR', 'A', 'A'); // duplicate R\n\n// after\nnew Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', 'R', 'A', 'A');","handlingStrategy":"validation","validationCode":"function hasUniqueStepLetters(steps) {\n  return typeof steps === \"string\" && new Set(steps).size === steps.length;\n}\nif (!hasUniqueStepLetters(steps)) {\n  throw new Error(\"Rotor steps contain duplicate letters.\");\n}","typeGuard":"function hasUniqueChars(s) {\n  return typeof s === \"string\" && new Set(s).size === s.length;\n}","tryCatchPattern":null,"preventionTips":["Remove duplicate letters from the steps string.","Derive steps from canonical ROTORS entries.","Validate Set size equals string length before constructing."],"tags":["enigma","cryptography","input-validation","rotor"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}