Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
| /** | |
| * Emulation of the Typex machine. | |
| * | |
| * @author s2224834 | |
| * @author The National Museum of Computing - Bombe Rebuild Project | |
| * @copyright Crown Copyright 2019 | |
| * @license Apache-2.0 | |
| */ | |
| import OperationError from "../errors/OperationError.mjs"; | |
| import * as Enigma from "../lib/Enigma.mjs"; | |
| import Utils from "../Utils.mjs"; | |
| /** | |
| * A set of example Typex rotors. No Typex rotor wirings are publicly available, so these are | |
| * randomised. | |
| */ | |
| export const ROTORS = [ | |
| {name: "Example 1", value: "MCYLPQUVRXGSAOWNBJEZDTFKHI<BFHNQUW"}, | |
| {name: "Example 2", value: "KHWENRCBISXJQGOFMAPVYZDLTU<BFHNQUW"}, | |
| {name: "Example 3", value: "BYPDZMGIKQCUSATREHOJNLFWXV<BFHNQUW"}, | |
| {name: "Example 4", value: "ZANJCGDLVHIXOBRPMSWQUKFYET<BFHNQUW"}, | |
| {name: "Example 5", value: "QXBGUTOVFCZPJIHSWERYNDAMLK<BFHNQUW"}, | |
| {name: "Example 6", value: "BDCNWUEIQVFTSXALOGZJYMHKPR<BFHNQUW"}, | |
| {name: "Example 7", value: "WJUKEIABMSGFTQZVCNPHORDXYL<BFHNQUW"}, | |
| {name: "Example 8", value: "TNVCZXDIPFWQKHSJMAOYLEURGB<BFHNQUW"}, | |
| ]; | |
| /** | |
| * An example Typex reflector. Again, randomised. | |
| */ | |
| export const REFLECTORS = [ | |
| {name: "Example", value: "AN BC FG IE KD LU MH OR TS VZ WQ XJ YP"}, | |
| ]; | |
| // Special character handling on Typex keyboard | |
| const KEYBOARD = { | |
| "Q": "1", "W": "2", "E": "3", "R": "4", "T": "5", "Y": "6", "U": "7", "I": "8", "O": "9", "P": "0", | |
| "A": "-", "S": "/", "D": "Z", "F": "%", "G": "X", "H": "£", "K": "(", "L": ")", | |
| "C": "V", "B": "'", "N": ",", "M": "." | |
| }; | |
| const KEYBOARD_REV = {}; | |
| for (const i of Object.keys(KEYBOARD)) { | |
| KEYBOARD_REV[KEYBOARD[i]] = i; | |
| } | |
| /** | |
| * Typex machine. A lot like the Enigma, but five rotors, of which the first two are static. | |
| */ | |
| export class TypexMachine extends Enigma.EnigmaBase { | |
| /** | |
| * TypexMachine constructor. | |
| * | |
| * @param {Object[]} rotors - List of Rotors. | |
| * @param {Object} reflector - A Reflector. | |
| * @param {Plugboard} plugboard - A Plugboard. | |
| */ | |
| constructor(rotors, reflector, plugboard, keyboard) { | |
| super(rotors, reflector, plugboard); | |
| if (rotors.length !== 5) { | |
| throw new OperationError("Typex must have 5 rotors"); | |
| } | |
| this.keyboard = keyboard; | |
| } | |
| /** | |
| * This is the same as the Enigma step function, it's just that the right- | |
| * most two rotors are static. | |
| */ | |
| step() { | |
| const r0 = this.rotors[2]; | |
| const r1 = this.rotors[3]; | |
| r0.step(); | |
| // The second test here is the double-stepping anomaly | |
| if (r0.steps.has(r0.pos) || r1.steps.has(Utils.mod(r1.pos + 1, 26))) { | |
| r1.step(); | |
| if (r1.steps.has(r1.pos)) { | |
| const r2 = this.rotors[4]; | |
| r2.step(); | |
| } | |
| } | |
| } | |
| /** | |
| * Encrypt/decrypt data. This is identical to the Enigma version cryptographically, but we have | |
| * additional handling for the Typex's keyboard (which handles various special characters by | |
| * mapping them to particular letter combinations). | |
| * | |
| * @param {string} input - The data to encrypt/decrypt. | |
| * @return {string} | |
| */ | |
| crypt(input) { | |
| let inputMod = input; | |
| if (this.keyboard === "Encrypt") { | |
| inputMod = ""; | |
| // true = in symbol mode | |
| let mode = false; | |
| for (const x of input) { | |
| if (x === " ") { | |
| inputMod += "X"; | |
| } else if (mode) { | |
| if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) { | |
| inputMod += KEYBOARD_REV[x]; | |
| } else { | |
| mode = false; | |
| inputMod += "V" + x; | |
| } | |
| } else { | |
| if (Object.prototype.hasOwnProperty.call(KEYBOARD_REV, x)) { | |
| mode = true; | |
| inputMod += "Z" + KEYBOARD_REV[x]; | |
| } else { | |
| inputMod += x; | |
| } | |
| } | |
| } | |
| } | |
| const output = super.crypt(inputMod); | |
| let outputMod = output; | |
| if (this.keyboard === "Decrypt") { | |
| outputMod = ""; | |
| let mode = false; | |
| for (const x of output) { | |
| if (x === "X") { | |
| outputMod += " "; | |
| } else if (x === "V") { | |
| mode = false; | |
| } else if (x === "Z") { | |
| mode = true; | |
| } else if (mode) { | |
| outputMod += KEYBOARD[x]; | |
| } else { | |
| outputMod += x; | |
| } | |
| } | |
| } | |
| return outputMod; | |
| } | |
| } | |
| /** | |
| * Typex rotor. Like an Enigma rotor, but no ring setting, and can be reversed. | |
| */ | |
| export class Rotor extends Enigma.Rotor { | |
| /** | |
| * Rotor constructor. | |
| * | |
| * @param {string} wiring - A 26 character string of the wiring order. | |
| * @param {string} steps - A 0..26 character string of stepping points. | |
| * @param {bool} reversed - Whether to reverse the rotor. | |
| * @param {char} ringSetting - Ring setting of the rotor. | |
| * @param {char} initialPosition - The initial position of the rotor. | |
| */ | |
| constructor(wiring, steps, reversed, ringSetting, initialPos) { | |
| let wiringMod = wiring; | |
| if (reversed) { | |
| const outMap = new Array(26); | |
| for (let i=0; i<26; i++) { | |
| // wiring[i] is the original output | |
| // Enigma.LETTERS[i] is the original input | |
| const input = Utils.mod(26 - Enigma.a2i(wiring[i]), 26); | |
| const output = Enigma.i2a(Utils.mod(26 - Enigma.a2i(Enigma.LETTERS[i]), 26)); | |
| outMap[input] = output; | |
| } | |
| wiringMod = outMap.join(""); | |
| } | |
| super(wiringMod, steps, ringSetting, initialPos); | |
| } | |
| } | |
| /** | |
| * Typex input plugboard. Based on a Rotor, because it allows arbitrary maps, not just switches | |
| * like the Enigma plugboard. | |
| * Not to be confused with the reflector plugboard. | |
| * This is also where the Typex's backwards input wiring is implemented - it's a bit of a hack, but | |
| * it means everything else continues to work like in the Enigma. | |
| */ | |
| export class Plugboard extends Enigma.Rotor { | |
| /** | |
| * Typex plugboard constructor. | |
| * | |
| * @param {string} wiring - 26 character string of mappings from A-Z, as per rotors, or "". | |
| */ | |
| constructor(wiring) { | |
| // Typex input wiring is backwards vs Enigma: that is, letters enter the rotors in a | |
| // clockwise order, vs. Enigma's anticlockwise (or vice versa depending on which side | |
| // you're looking at it from). I'm doing the transform here to avoid having to rewrite | |
| // the Engima crypt() method in Typex as well. | |
| // Note that the wiring for the reflector is the same way around as Enigma, so no | |
| // transformation is necessary on that side. | |
| // We're going to achieve this by mapping the plugboard settings through an additional | |
| // transform that mirrors the alphabet before we pass it to the superclass. | |
| if (!/^[A-Z]{26}$/.test(wiring)) { | |
| throw new OperationError("Plugboard wiring must be 26 unique uppercase letters"); | |
| } | |
| const reversed = "AZYXWVUTSRQPONMLKJIHGFEDCB"; | |
| wiring = wiring.replace(/./g, x => { | |
| return reversed[Enigma.a2i(x)]; | |
| }); | |
| try { | |
| super(wiring, "", "A", "A"); | |
| } catch (err) { | |
| throw new OperationError(err.message.replace("Rotor", "Plugboard")); | |
| } | |
| } | |
| /** | |
| * Transform a character through this rotor forwards. | |
| * | |
| * @param {number} c - The character. | |
| * @returns {number} | |
| */ | |
| transform(c) { | |
| return Utils.mod(this.map[Utils.mod(c + this.pos, 26)] - this.pos, 26); | |
| } | |
| /** | |
| * Transform a character through this rotor backwards. | |
| * | |
| * @param {number} c - The character. | |
| * @returns {number} | |
| */ | |
| revTransform(c) { | |
| return Utils.mod(this.revMap[Utils.mod(c + this.pos, 26)] - this.pos, 26); | |
| } | |
| } |