{"id":"7116e6f347bf3540","slug":"enigma-machine","trashed":false,"description":"","likes":414,"publish_level":"public","forks":22,"fork_of":null,"has_importers":false,"update_time":"2019-09-06T18:07:06.938Z","first_public_version":null,"paused_version":null,"publish_time":"2019-08-30T23:05:27.356Z","publish_version":3642,"latest_version":3642,"thumbnail":"3da4f34d49552b93edcca50252404b132575a9dadbc69a0eefbf40bbe64048c8","default_thumbnail":"3da4f34d49552b93edcca50252404b132575a9dadbc69a0eefbf40bbe64048c8","roles":[],"sharing":null,"owner":{"id":"b47c3705cfcd7c1b","avatar_url":"https://avatars.observableusercontent.com/avatar/669bdd774da8a1b9c1b7706ecbc1ea5136b9932c3d5e86b753aa5a098005f674","login":"tmcw","name":"Tom MacWright","bio":"hi!","home_url":"https://macwright.com/","type":"team","tier":"starter_2024"},"creator":{"id":"0bf14b8d9c6eb88a","avatar_url":"https://avatars.observableusercontent.com/avatar/669bdd774da8a1b9c1b7706ecbc1ea5136b9932c3d5e86b753aa5a098005f674","login":"tmcw","name":"Tom MacWright","bio":"hi!","home_url":"https://macwright.com/","tier":"pro"},"authors":[{"id":"0bf14b8d9c6eb88a","avatar_url":"https://avatars.observableusercontent.com/avatar/669bdd774da8a1b9c1b7706ecbc1ea5136b9932c3d5e86b753aa5a098005f674","name":"Tom MacWright","login":"tmcw","bio":"hi!","home_url":"https://macwright.com/","tier":"pro","approved":true,"description":""}],"collections":[],"files":[],"comments":[],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":3642,"title":"Enigma machine","license":null,"copyright":"","nodes":[{"id":9,"value":"chart = {\n  const svg = d3\n    .create(\"svg\")\n    .attr(\"width\", w)\n    .attr(\"height\", w);\n\n  let plugboardstr = \"AB HK\";\n  let speed = 1;\n\n  let labels = html`<input type='checkbox' name='labels' id='labels' />`;\n\n  let reflectorSettings = html`<form>\n<div style='font-weight:bold;margin-top:1em;'>Reflector</div>\n<select name=r1>\n${REFLECTORS.map((r, i) => html`<option value='${r.value}'>${r.name}</option>`)}\n</select>\n</form>`;\n\n  let plugboardSettings = html`<form>\n<div style='font-weight:bold;margin-top:1em;'>Plugboard</div>\n<input value='${plugboardstr}' type=text maxlength=20 style='width:150px;font-family:inherit;box-sizing:border-box;font-weight:bold;font-size:inherit;' />\n</form>`;\n\n  let speedForm = html`<form>\n<div style='font-weight:bold;margin-top:1em;'>Speed</div>\n<input value=1 style='width:150px;' type=range min=1 max=16 step=5 />\n</form>`;\n\n  speedForm.addEventListener(\"input\", e => {\n    speed = e.target.valueAsNumber;\n  });\n\n  let rotorSettings = html`<form>\n<div style='font-weight:bold;margin-top:1em;'>Rotors</div>\n<select name=r1 data-rotor=0>\n${ROTORS.map((r, i) => html`<option value='${r.value}'>${r.name}</option>`)}\n</select>\n<select name=r2 data-rotor=1>\n${ROTORS.map(\n    (r, i) =>\n      html`<option ${i == 1 ? \"selected\" : \"\"} value='${r.value}'>${\n        r.name\n      }</option>`\n  )}\n</select>\n<select name=r3 data-rotor=2>\n${ROTORS.map(\n    (r, i) =>\n      html`<option  ${i == 2 ? \"selected\" : \"\"} value='${r.value}'>${\n        r.name\n      }</option>`\n  )}\n</select>\n</form>`;\n\n  let value = \"Hello world\";\n  let input = html`\n<div style='font-weight:bold;margin-top:1em;'>Input</div>\n<input id=in value='${value}' type=text maxlength=20 style='width:150px;font-family:inherit;box-sizing:border-box;font-weight:bold;font-size:inherit;' />`;\n  input.addEventListener(\"input\", e => {\n    value = e.target.value;\n  });\n  let i = input.querySelector(\"#in\");\n\n  let output = html`\n<div style='font-weight:bold;margin-top:1em;'>Output</div>\n<div id=out style='border:1px solid #000;'>&nbsp;</div>`;\n  let o = output.querySelector(\"#out\");\n\n  let go = html`\n<button style='margin-top:0.5em;width:150px;font-family:inherit;background:#000;color:#fff;padding:2px;box-sizing:border-box;font-weight:bold;font-size:inherit;border:none;'>Encrypt</button>`;\n\n  let ui = html`<div id=ui>\n   <div id=controls>\n     <h1>Enigma machine</h1>\n     <div>\n       ${labels}<label for='labels' style='margin-left:0.2em;'>Labels</label>\n     </div>\n     ${rotorSettings}\n     ${reflectorSettings}\n     ${plugboardSettings}\n     ${speedForm}\n     ${input}\n${go}\n${output}\n     <p id='explanation' style='font-size: 80%;'>\n     This is a simulated Enigma machine. Letters to be encrypted enter at the boundary,\n     move through the wire matrix, and exit.\n     </p>\n   </div>\n   ${svg.node()}\n  </div>\n<style>\n#ui {\n  font-family: IBM Plex Mono;\n  ${mobile ? \"\" : \"display: flex;\"}\n  font-size:15px;\n}\n#ui h1 {\n  font-size: ${mobile ? \"18px\" : \"23px\"};\n  font-family: IBM Plex Mono;\n}\n#ui svg {\n  flex-shrink: 0;\n}\n#ui #controls {\n  padding-right: 1vw;\n  flex: 1 1 auto;\n}\n</style>`;\n\n  yield ui;\n  const g = svg.append(\"g\").attr(\"transform\", `translate(${w / 2}, ${w / 2})`);\n  const topG = svg\n    .append(\"g\")\n    .attr(\"transform\", `translate(${w / 2}, ${w / 2})`);\n  const defs = svg.append(\"defs\");\n\n  // Configurable\n  let reflector = new Reflector(REFLECTORS[0].value);\n  let reflectorLinks = makeReflectorLinks(reflector);\n  let rotorValues = [0, 1, 2].map(r => ROTORS[r].value);\n\n  let plugboard,\n    rotors = [],\n    linkData;\n\n  function setRotors() {\n    plugboard = new Plugboard(plugboardstr);\n    rotors = [];\n    let index = 0;\n    for (let val of rotorValues) {\n      rotors.push(new Rotor(...parseRotorStr(val, 1), \"A\", \"A\"));\n    }\n    rotors.reverse();\n    for (let i = 0; i < rotors.length; i++) rotors[i].index = i;\n    linkData = [plugboard]\n      .concat(rotors)\n      .map((rotor, i) =>\n        rotorLinks(rotor, d3.interpolateNumber(radii[i], radii[i + 1]))\n      );\n  }\n  setRotors();\n\n  plugboardSettings.addEventListener(\"input\", e => {\n    const value = e.target.value;\n    if (validatePlugboard(value)) {\n      e.target.style.backgroundColor = \"white\";\n      plugboardstr = value;\n    } else {\n      e.target.style.backgroundColor = \"red\";\n    }\n    setRotors();\n    updateRotors();\n  });\n\n  const line = d3\n    .line()\n    .x(d => d.x)\n    .y(d => d.y);\n\n  let rotorGroups;\n\n  function updateRotors() {\n    g.selectAll(\"g.rotor\").remove();\n    rotorGroups = g\n      .selectAll(\"g.rotor\")\n      .data(rotors.concat(plugboard), (r, i) => {\n        return i;\n      })\n      .join(enter => {\n        let g = enter\n          .append(\"g\")\n          .attr(\"class\", \"rotor\")\n          .attr(\"opacity\", 0.4);\n        g.selectAll(\"g.link\")\n          .data((d, i) => linkData[i], d => d.source)\n          .join(enter => {\n            let g = enter.append(\"g\").attr(\"class\", \"link\");\n            g.append(\"path\")\n              .attr(\"class\", \"halo\")\n              .attr(\"stroke\", d => \"#fff\")\n              .attr(\"stroke-width\", 3)\n              .attr(\"fill\", \"none\")\n              .attr(\"d\", d => line(d.points));\n            g.append(\"path\")\n              .attr(\"class\", \"link\")\n              .attr(\"stroke\", \"#000\")\n              .attr(\"stroke-width\", 1)\n              .attr(\"fill\", \"none\")\n              .attr(\"d\", d => line(d.points));\n            return g;\n          });\n        return g;\n      });\n  }\n  updateRotors();\n\n  rotorSettings.addEventListener(\"change\", e => {\n    let value = e.target.value;\n    let rotor = e.target.dataset.rotor;\n    rotorValues[rotor] = value;\n    setRotors();\n    updateRotors();\n  });\n\n  reflectorSettings.addEventListener(\"change\", e => {\n    let value = e.target.value;\n    reflector = new Reflector(value);\n    reflectorLinks = makeReflectorLinks(reflector);\n    rLinks\n      .data(reflectorLinks, d => d.source)\n      .join(\n        enter => {},\n        update => {\n          update\n            .select(\"path.halo\")\n            .transition()\n            .delay((d, i) => i * 20)\n            .attr(\"d\", d => d.d);\n          update\n            .select(\"path.line\")\n            .transition()\n            .delay((d, i) => i * 20)\n            .attr(\"d\", d => d.d);\n        }\n      );\n  });\n\n  let rLinks = g\n    .selectAll(\"g.reflector-link\")\n    .data(reflectorLinks, d => d.source)\n    .join(enter => {\n      let g = enter\n        .append(\"g\")\n        .attr(\"class\", \"reflector-link\")\n        .attr(\"opacity\", 0.4);\n      g.append(\"path\")\n        .attr(\"d\", d => d.d)\n        .attr(\"fill\", \"none\")\n        .attr(\"stroke\", \"#fff\")\n        .attr(\"class\", \"halo\")\n        .attr(\"stroke-width\", 2);\n      g.append(\"path\")\n        .attr(\"d\", d => d.d)\n        .attr(\"class\", \"line\")\n        .attr(\"fill\", \"none\")\n        .attr(\"stroke\", \"#000\")\n        .attr(\"stroke-width\", 1);\n      return g;\n    });\n  reflectorSettings.addEventListener(\"change\", e => {\n    let value = e.target.value;\n    reflector = new Reflector(value);\n    reflectorLinks = makeReflectorLinks(reflector);\n    rLinks\n      .data(reflectorLinks, d => d.source)\n      .join(\n        enter => {},\n        update => {\n          update\n            .select(\"path.halo\")\n            .transition()\n            .delay((d, i) => i * 20)\n            .attr(\"d\", d => d.d);\n          update\n            .select(\"path.line\")\n            .transition()\n            .delay((d, i) => i * 20)\n            .attr(\"d\", d => d.d);\n        }\n      );\n  });\n\n  let borderCircles = g\n    .selectAll(\"circle.border\")\n    .data(radii)\n    .join(\"circle\")\n    .attr(\"class\", \"border\")\n    .attr(\"r\", d => d)\n    .attr(\"stroke\", (d, i) => (i == 0 ? \"#000\" : \"#888\"))\n    .attr(\"stroke-width\", 1.5)\n    .attr(\"fill\", \"none\");\n\n  const ids = [0, 1, 2, 3, 4].map(i => DOM.uid(`ring-${i}`));\n\n  let ringLabels = topG\n    .selectAll(\"g.label\")\n    .data(radii)\n    .join(enter => {\n      let g = enter\n        .append(\"g\")\n        .attr(\"class\", \"label\")\n        .attr(\n          \"transform\",\n          (d, i) => `translate(0, ${-(radii[i] + radii[i + 1]) / 2})`\n        );\n      g.append(\"rect\")\n        .attr(\"height\", 25)\n        .attr(\"transform\", \"translate(-10, -14)\")\n        .attr(\"width\", w / 6);\n      g.append(\"text\")\n        .attr(\"fill\", \"#fff\")\n        .style(\"font-size\", \"1.7vw\")\n        .attr(\"alignment-baseline\", \"middle\")\n        .attr(\"font-weight\", \"bold\")\n        .text((d, i) => {\n          return i === 0 ? \"Plugboard\" : i === 4 ? `Reflector` : `Rotor ${i}`;\n        });\n      return g;\n    })\n    .attr(\"opacity\", 0);\n\n  let inputDots = topG\n    .selectAll(\"g.input\")\n    .data(d3.range(0, 26))\n    .join(enter => {\n      let g = enter.append(\"g\").attr(\n        \"transform\",\n        i =>\n          `translate(\n            ${Math.cos(Math.PI * 2 * (i / 26)) * radii[0]},\n            ${Math.sin(Math.PI * 2 * (i / 26)) * radii[0]})`\n      );\n      g.append(\"circle\")\n        .attr(\"class\", \"input\")\n        .attr(\"r\", 10)\n        .attr(\"fill\", \"#fff\")\n        .attr(\"stroke\", \"#000\");\n      g.append(\"text\")\n        .text(d => LETTERS[d])\n        .style(\"font-family\", \"IBM Plex Mono\")\n        .style(\"font-size\", \"2vw\")\n        .attr(\"text-anchor\", \"middle\")\n        .attr(\"alignment-baseline\", \"middle\")\n        .attr(\n          \"transform\",\n          i =>\n            `translate(\n            ${Math.cos(Math.PI * 2 * (i / 26)) * 20},\n            ${Math.sin(Math.PI * 2 * (i / 26)) * 20})`\n        );\n      return g;\n    });\n\n  const dot = topG\n    .append(\"circle\")\n    .attr(\"class\", \"focus\")\n    .attr(\"r\", 7)\n    .attr(\"fill\", \"#da1e28\")\n    .attr(\"opacity\", 0);\n\n  labels.addEventListener(\"change\", e => {\n    if (e.target.checked) {\n      ringLabels.transition().attr(\"opacity\", 1);\n    } else {\n      ringLabels.transition().attr(\"opacity\", 0);\n    }\n  });\n\n  while (true) {\n    await new Promise(resolve =>\n      go.addEventListener(\"click\", e => {\n        o.innerHTML = \"&nbsp;\";\n        go.style.background = \"#aaa\";\n        go.disabled = true;\n        i.disabled = true;\n        resolve();\n      })\n    );\n    const { steps, result } = encrypt(value, rotors, reflector, plugboard);\n    let link = 0;\n    let frame = 0;\n\n    for (let { type, ...step } of steps) {\n      switch (type) {\n        case THROUGH_ROTOR: {\n          rotorGroups\n            .transition()\n            .attr(\"opacity\", (d, i) => (i === step.rotor + 1 ? 1 : 0.2));\n\n          let { points, dest, rotor } = linkData[step.rotor + 1].find(\n            l => l.source === step.source\n          );\n\n          for (let pos of points) {\n            dot.attr(\n              \"transform\",\n              `rotate(${-(360 * (step.pos / 26)).toFixed(5)})\n            translate(${pos.x}, ${pos.y})`\n            );\n            if (++frame % speed === 0) yield ui;\n          }\n          break;\n        }\n        case ROTOR_STEP: {\n          await rotorGroups\n            .filter((d, i) => i - 1 === step.rotor)\n            .transition()\n            .duration(2000 / speed)\n            .attr(\"transform\", (d, i) => {\n              return `rotate(${-(360 * (step.pos / 26)).toFixed(5)})`;\n            })\n            .end();\n          break;\n        }\n        case BACK_THROUGH_ROTOR: {\n          rotorGroups\n            .transition()\n            .attr(\"opacity\", (d, i) => (i === step.rotor + 1 ? 1 : 0.2));\n          let { points, rotor, dest } = linkData[step.rotor + 1].find(\n            l => l.source === step.source\n          );\n\n          for (let pos of points.slice().reverse()) {\n            dot.attr(\n              \"transform\",\n              `rotate(${-(360 * (step.pos / 26)).toFixed(5)})\n            translate(${pos.x}, ${pos.y})`\n            );\n            if (++frame % speed === 0) yield ui;\n          }\n          break;\n        }\n        case THROUGH_REFLECTOR: {\n          rotorGroups.transition().attr(\"opacity\", 0.2);\n          rLinks.transition().attr(\"opacity\", 1);\n          let r = reflectorLinks.find(r => r.target === step.dest);\n          for (let pos of r.points.slice().reverse()) {\n            dot.attr(\"transform\", `translate(${pos.x}, ${pos.y})`);\n            if (++frame % speed === 0) yield ui;\n          }\n          rLinks.transition().attr(\"opacity\", 0.2);\n          break;\n        }\n        case THROUGH_PLUGBOARD: {\n          rotorGroups\n            .transition()\n            .attr(\"opacity\", (d, i) => (i === 0 ? 1 : 0.2));\n          console.log(linkData[0]);\n          let r = linkData[0].find(l => l.source === step.wire);\n\n          dot.attr(\"opacity\", 1);\n          for (let pos of r.points) {\n            dot.attr(\"transform\", `translate(${pos.x}, ${pos.y})`);\n            if (++frame % speed === 0) yield ui;\n          }\n          break;\n        }\n        case BACK_THROUGH_PLUGBOARD: {\n          rotorGroups\n            .transition()\n            .attr(\"opacity\", (d, i) => (i === 0 ? 1 : 0.2));\n          let r = linkData[0][step.wire];\n          for (let pos of r.points.slice().reverse()) {\n            dot.attr(\"transform\", `translate(${pos.x}, ${pos.y})`);\n            if (++frame % speed === 0) yield ui;\n          }\n          o.innerText = o.innerText += step.output;\n          break;\n        }\n      }\n\n      yield ui;\n    }\n    go.style.background = \"#000\";\n    go.disabled = false;\n    i.disabled = false;\n  }\n\n  //yield run();\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":3555,"value":"md`# The Enigma Machine\n\nThe [Enigma Machine](https://en.wikipedia.org/wiki/Enigma_machine) was one of the centerpoints of World War II, and its [cryptanalysis](https://en.wikipedia.org/wiki/Cryptanalysis_of_the_Enigma) was one of the stepping stones from breaking codes as an art to cryptography as a science. The machine encrypted messages sent between parts of the German army – operators would type a key on its keyboard, the machine would scramble that, and a letter would light up on the top.\n\nThis notebook simulates an Enigma Machine and visualizes how it works. The Enigma Machine is an especially neat thing to visualize because it was _electromechanical_. As you used it, it moved. Instead of circuit traces, it had beautiful real wires connecting its pieces.\n\n<figure>\n<img style='border:1px solid #000; box-sizing:border-box;max-width:480px;width:100%;' src=\"https://upload.wikimedia.org/wikipedia/commons/9/99/Enigma_rotor_wiring.png\" alt=\"Enigma rotor wiring.png\"></a>\n\n<figcaption>By <a href=\"https://en.wikipedia.org/wiki/User:RadioFan\" class=\"extiw\" title=\"en:User:RadioFan\">User:RadioFan</a>, <a href=\"https://creativecommons.org/licenses/by-sa/3.0\" title=\"Creative Commons Attribution-Share Alike 3.0\">CC BY-SA 3.0</a>, <a href=\"https://commons.wikimedia.org/w/index.php?curid=30719651\">Link</a></figcaption>\n</figure>\n\n### Intepreting this model\n\nTo understand this model, first consider the design of the machine itself: as someone typed into it, electrical current flowed through _rotors_, which are essentially 1:1 mappings of one letter, represented as a signal from 0-25. Just one stationary rotor wouldn’t give much of a quality encryption, so the rotors also rotated.\n\nThe main pieces were three rotors, a reflector - which was basically a rotor that connects to itself, and a plugboard, which swaps letters for each other on the way in & out. In order, a signal would go through the plugboard, each of the three rotors, the reflector, and then back through the rotors, and then back through the plugboard.\n\nThink of this view of the engima as looking 'through the barrel' of the machine: you're seeing wires snaking around the rotors and then looking at the rotors at the end.\n\n### Using the simulator\n\n- You can encrypt up to 20 characters using this simulator. Just like the original machine, punctuation isn’t supported.\n- The plugboard can be configured with space-separated pairs of characters: for example: \\`AB CD\\` maps A to B (and the reverse), and C to D (and the reverse). Invalid inputs will be ignored.\n- Select your rotors and reflector to taste.\n\n### Genesis\n\nMy interest in the machine was piqued by [The Woman Who Smashed Codes](https://www.npr.org/2017/09/30/548666129/from-dinner-parties-to-spy-rings-the-woman-who-smashed-codes-bursts-with-detail), a wonderful book by Jason Fagone.\n\nI started initially with [Louise Dade’s Enigma Machine simulator](http://enigma.louisedade.co.uk/), but then [GCHQ](https://en.wikipedia.org/wiki/Government_Communications_Headquarters) released their own simulation of the machine in the [CyberChef project](https://github.com/gchq/CyberChef). It’s troubling to use code from GCHQ, one of the organizations of the [Five Eyes](https://en.wikipedia.org/wiki/Five_Eyes) that [surveils the Internet](https://theintercept.com/2015/09/25/gchq-radio-porn-spies-track-web-users-online-identities/). Nevertheless, it’s authoritative, and the breaking of the Enigma Machine is an example of old-fashioned codebreaking that has a certain air of sport.\n\n### Notes & future work\n\n- This is a model of the M3 or Enigma I machine. There also exists a four-rotor version, which I'd like to support eventually. Likewise, there are a few exotic variations of the Enigma which are not modeled.\n- The positions of the rotors can also be configured but that isn’t configurable yet.\n\n### Mini changelog\n\n- 8/31: Made speed configurable, removed 'thin' reflectors because they only apply to M4.\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":116,"value":"radii = Array.from({ length: 5 }, (_, i) =>\n  d3.interpolateNumber(w / 2.4, width / 30)(i / 4)\n)","pinned":false,"mode":"js","data":null,"name":null},{"id":1488,"value":"function rotorLinks(rotor, radius) {\n  let map = rotor instanceof Rotor ? rotor.map : rotor.m;\n\n  let d = map.map((r, t) => {\n    let angle = d3.interpolateNumber(\n      (t / 26) * Math.PI * 2,\n      (r / 26) * Math.PI * 2\n    );\n\n    let points = d3\n      .range(0, 1, 0.01)\n      .concat(1)\n      .map(j => {\n        let r = radius(j);\n        let a = angle(d3.easePolyInOut.exponent(3)(j));\n        return {\n          x: Math.cos(a) * r,\n          y: Math.sin(a) * r\n        };\n      });\n    return {\n      rotor,\n      source: t,\n      dest: r,\n      points\n    };\n  });\n\n  return d;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":534,"value":"makeReflectorLinks = reflector => {\n  return Array.from(reflector.map.entries(), ([s, t]) => {\n    let source = (+s / 26) * Math.PI * 2;\n    let target = (t / 26) * Math.PI * 2;\n    let r = radii[radii.length - 1];\n    let sx0 = r * Math.cos(source);\n    let sy0 = r * Math.sin(source);\n    let tx0 = r * Math.cos(target);\n    let ty0 = r * Math.sin(target);\n    let context = d3.path();\n    context.moveTo(sx0, sy0);\n    context.quadraticCurveTo(0, 0, r * Math.cos(target), r * Math.sin(target));\n\n    let p = d3\n      .create(\"svg:path\")\n      .attr(\"d\", context)\n      .node();\n\n    let len = p.getTotalLength();\n\n    let points = d3\n      .range(0, len, len / 30)\n      .map(at => p.getPointAtLength(at))\n      .map(pt => ({ x: pt.x, y: pt.y }));\n\n    return {\n      source: +s,\n      target: t,\n      points,\n      d: context.toString()\n    };\n  });\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":3470,"value":"function validatePlugboard(pairs) {\n  const map = {};\n  for (let pair of pairs.split(/\\s+/)) {\n    if (!/^[A-Z]{2}$/.test(pair)) {\n      return false;\n    }\n    const a = a2i(pair[0]),\n      b = a2i(pair[1]);\n    if (a === b) {\n      // self-stecker\n      return;\n    }\n    if (Object.prototype.hasOwnProperty.call(map, a)) {\n      return false;\n    }\n    if (Object.prototype.hasOwnProperty.call(map, b)) {\n      return false;\n    }\n    map[a] = b;\n    map[b] = a;\n  }\n  return true;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1776,"value":"function encrypt(input, rotors, reflector, plugboard) {\n  const rotorsRev = [].concat(rotors).reverse();\n  const message = \"\";\n  let result = \"\";\n  const letter = \"\";\n  let steps = [];\n\n  const step = () => {\n    const [r0, r1] = rotors;\n    r0.step();\n    steps.push({\n      type: ROTOR_STEP,\n      rotor: 0,\n      pos: r0.pos\n    });\n    // The second test here is the double-stepping anomaly\n    if (r0.steps.has(r0.pos) || r1.steps.has(mod(r1.pos + 1, 26))) {\n      r1.step();\n      steps.push({\n        type: ROTOR_STEP,\n        rotor: 1,\n        pos: r1.pos\n      });\n      if (r1.steps.has(r1.pos)) {\n        const r2 = rotors[2];\n        r2.step();\n        steps.push({\n          type: ROTOR_STEP,\n          rotor: 2,\n          pos: r2.pos\n        });\n      }\n    }\n  };\n\n  let log = \"\";\n  for (const c of input) {\n    let letter = a2i(c, true);\n    if (letter === -1) {\n      result += c;\n      continue;\n    }\n    step();\n    log += `before(${letter}) `;\n\n    steps.push({\n      type: THROUGH_PLUGBOARD,\n      wire: letter\n    });\n    letter = plugboard.transform(letter);\n\n    for (let i = 0; i < rotors.length; i++) {\n      steps.push({\n        type: THROUGH_ROTOR,\n        rotor: i,\n        pos: rotors[i].pos,\n        source: rotors[i].getWire(letter)\n      });\n      letter = rotors[i].transform(letter);\n    }\n\n    steps.push({\n      type: THROUGH_REFLECTOR,\n      rotor: letter,\n      dest: letter\n    });\n    letter = reflector.transform(letter);\n    for (let i = rotors.length - 1; i >= 0; i--) {\n      letter = rotors[i].revTransform(letter);\n      steps.push({\n        type: BACK_THROUGH_ROTOR,\n        rotor: i,\n        pos: rotors[i].pos,\n        source: rotors[i].getWire(letter)\n      });\n    }\n\n    steps.push({\n      type: BACK_THROUGH_PLUGBOARD,\n      wire: plugboard.transform(letter),\n      output: i2a(plugboard.transform(letter))\n    });\n    letter = plugboard.transform(letter);\n    log += ` = ${i2a(letter)}`;\n    result += i2a(letter);\n  }\n  return { steps, result: result };\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1774,"value":"class Rotor {\n  constructor(wiring, steps, ringSetting, initialPosition, name) {\n    this.name = name;\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 Error(\"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(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 Error(\"Rotor steps must be unique\");\n    }\n    this.pos = mod(a2i(initialPosition) - rs, 26);\n  }\n  step() {\n    this.pos = mod(this.pos + 1, 26);\n    return this.pos;\n  }\n  transform(c) {\n    return mod(this.map[mod(c + this.pos, 26)] - this.pos, 26);\n  }\n  getWire(c) {\n    return mod(c + this.pos, 26);\n  }\n  revTransform(c) {\n    return mod(this.revMap[mod(c + this.pos, 26)] - this.pos, 26);\n  }\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1781,"value":"class PairMapBase {\n  constructor(pairs) {\n    this.pairs = pairs;\n    this.map = new Map();\n    if (pairs === \"\") return;\n    pairs.split(/\\s+/).forEach(pair => {\n      const a = a2i(pair[0]),\n        b = a2i(pair[1]);\n      if (a !== b) {\n        this.map.set(a, b);\n        this.map.set(b, a);\n      }\n    });\n  }\n  transform(c) {\n    return this.map.has(c) ? this.map.get(c) : c;\n  }\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1779,"value":"class Reflector extends PairMapBase {\n  constructor(pairs) {\n    super(pairs);\n    const s = this.map.size;\n    if (s !== 26) {\n      throw new Error(\n        \"Reflector must have exactly 13 pairs covering every letter\"\n      );\n    }\n    const optMap = new Map(Array.from({ length: 26 }, (_, i) => [i, i]));\n    for (const x of this.map.keys()) {\n      optMap.set(x, this.map.get(x));\n    }\n    this.map = optMap;\n  }\n  transform(c) {\n    return this.map.get(c);\n  }\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":1777,"value":"class Plugboard extends PairMapBase {\n  constructor(pairs) {\n    super(pairs, \"Plugboard\");\n\n    let m = Array.from({ length: 26 }, (_, i) => i);\n    let mapped = new Set();\n    let prs = [];\n    let map = [];\n    here: for (let swap of this.map.entries()) {\n      let [a, b] = Array.from(swap);\n      m[a] = b;\n      m[b] = a;\n      for (let s of swap) {\n        if (mapped.has(s)) {\n          continue here;\n        }\n        mapped.add(s);\n      }\n      prs.push(new Set(swap));\n    }\n    this.m = m;\n    this.pairs = prs;\n  }\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":2306,"value":"THROUGH_ROTOR = Symbol(\"THROUGH_ROTOR\")","pinned":false,"mode":"js","data":null,"name":null},{"id":2309,"value":"BACK_THROUGH_ROTOR = Symbol(\"BACK_THROUGH_ROTOR\")","pinned":false,"mode":"js","data":null,"name":null},{"id":2382,"value":"THROUGH_REFLECTOR = Symbol(\"THROUGH_REFLECTOR\")","pinned":false,"mode":"js","data":null,"name":null},{"id":2703,"value":"THROUGH_PLUGBOARD = Symbol(\"THROUGH_PLUGBOARD\")","pinned":false,"mode":"js","data":null,"name":null},{"id":2732,"value":"BACK_THROUGH_PLUGBOARD = Symbol(\"BACK_THROUGH_PLUGBOARD\")","pinned":false,"mode":"js","data":null,"name":null},{"id":2468,"value":"ROTOR_STEP = Symbol(\"ROTOR_STEP\")","pinned":false,"mode":"js","data":null,"name":null},{"id":3495,"value":"w = mobile ? width : width * 0.8","pinned":false,"mode":"js","data":null,"name":null},{"id":3499,"value":"mobile = width < 640","pinned":false,"mode":"js","data":null,"name":null},{"id":1789,"value":"import {checkbox, select} from \"@jashkenas/inputs\"","pinned":false,"mode":"js","data":null,"name":null},{"id":1787,"value":"import {submit} from \"@mbostock/more-deliberate-inputs\"","pinned":false,"mode":"js","data":null,"name":null},{"id":1785,"value":"import {REFLECTORS, ROTORS_FOURTH, ROTORS, LETTERS} from \"@tmcw/enigma-constants\"","pinned":false,"mode":"js","data":null,"name":null},{"id":1784,"value":"import {mod, i2a, a2i, parseRotorStr} from \"@tmcw/enigma-cyberchef-utils\"","pinned":false,"mode":"js","data":null,"name":null},{"id":1956,"value":"html`<link href=\"https://fonts.googleapis.com/css?family=IBM+Plex+Mono&display=swap\" rel=\"stylesheet\">`","pinned":false,"mode":"js","data":null,"name":null},{"id":7,"value":"d3 = require(\"d3\")","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}