+ "cat(reflector_c.forward(l) for l in string.ascii_lowercase)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "<a name=\"simplewheel\"></a>\n",
+ "## SimpleWheel\n",
+ "[Top](#top)\n",
+ "\n",
+ "A `SimpleWheel` has different forward and backward maps, and also a position. The position is set with the `set_position` method (and initially in the creator), and the wheel can advance using the `advance` method. \n",
+ "\n",
+ "How the position is used is best explained with an example. The Enigma wheel 1, in the neutral position, transforms `a` to `e` (+4 letters) and `b` to `k` (+10 letters). When the wheel is in position `b` and an `a` in enciphered, it's the _second_ element of the map that's used, so `a` would be advanced 10 letters, to give `j`.\n",
+ "\n",
+ "This means that when using the letter transformation maps, you use the element in the map that's offset by the position of the wheel. When enciphering a `c`, you'd normally use transformation at position 2 in the map; if the wheel is in position 7, you'd instead use the transform at position 2 + 7 = 9 in the map.\n",
+ "\n",
+ "There are various modulus operators to keep the numbers in the requried range, meaning you can wrap around the map and around the wheel.\n",
+ "\n",
+ "Note the use of `__getattribute__` to give a more human-friendly version of the position without making it a method call. That allows you to write `wheel.position` and `wheel.position_l` and get the appropriate answers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 139,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "class SimpleWheel(LetterTransformer):\n",
+ " def __init__(self, transform, position='a', raw_transform=False):\n",
+ " super(SimpleWheel, self).__init__(transform, raw_transform)\n",
+ " self.set_position(position)\n",
+ " \n",
+ " def __getattribute__(self, name):\n",
+ " if name=='position_l':\n",
+ " return unpos(self.position)\n",
+ " else:\n",
+ " return object.__getattribute__(self, name) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Set the wheel to a new position. Note that it expects a letter, not a number."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 140,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def set_position(self, position):\n",
+ " self.position = ord(position) - ord('a')\n",
+ " \n",
+ "setattr(SimpleWheel, 'set_position', set_position) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Advance the wheel one step. Note that advancing beyond position 25 moves back to 0."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 141,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def advance(self):\n",
+ " self.position = (self.position + 1) % 26\n",
+ " return self.position\n",
+ "\n",
+ "setattr(SimpleWheel, 'advance', advance) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Do the encipherment forward and backward. Note how the map element to use is affected by the wheel position, and how the modulus wraps that map element around the wheel if needed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 142,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def forward(self, letter):\n",
+ " if letter in string.ascii_lowercase:\n",
+ " return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+ " else:\n",
+ " return ''\n",
+ "\n",
+ "def backward(self, letter):\n",
+ " if letter in string.ascii_lowercase:\n",
+ " return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))\n",
+ " else:\n",
+ " return ''\n",
+ " \n",
+ "setattr(SimpleWheel, 'forward', forward) \n",
+ "setattr(SimpleWheel, 'backward', backward) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 143,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[('a', 'e'),\n",
+ " ('b', 'k'),\n",
+ " ('c', 'm'),\n",
+ " ('d', 'f'),\n",
+ " ('e', 'l'),\n",
+ " ('f', 'g'),\n",
+ " ('g', 'd'),\n",
+ " ('h', 'q'),\n",
+ " ('i', 'v'),\n",
+ " ('j', 'z'),\n",
+ " ('k', 'n'),\n",
+ " ('l', 't'),\n",
+ " ('m', 'o'),\n",
+ " ('n', 'w'),\n",
+ " ('o', 'y'),\n",
+ " ('p', 'h'),\n",
+ " ('q', 'x'),\n",
+ " ('r', 'u'),\n",
+ " ('s', 's'),\n",
+ " ('t', 'p'),\n",
+ " ('u', 'a'),\n",
+ " ('v', 'i'),\n",
+ " ('w', 'b'),\n",
+ " ('x', 'r'),\n",
+ " ('y', 'c'),\n",
+ " ('z', 'j')]"
+ ]
+ },
+ "execution_count": 143,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))\n",
+ "rotor_1_transform"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 144,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))\n",
+ "wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)\n",
+ "assert(cat(wheel_1.forward(l) for l in string.ascii_lowercase) == 'ekmflgdqvzntowyhxuspaibrcj')\n",
+ "assert(cat(wheel_1.backward(l) for l in string.ascii_lowercase) == 'uwygadfpvzbeckmthxslrinqoj')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 145,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[4,\n",
+ " 10,\n",
+ " 12,\n",
+ " 5,\n",
+ " 11,\n",
+ " 6,\n",
+ " 3,\n",
+ " 16,\n",
+ " 21,\n",
+ " 25,\n",
+ " 13,\n",
+ " 19,\n",
+ " 14,\n",
+ " 22,\n",
+ " 24,\n",
+ " 7,\n",
+ " 23,\n",
+ " 20,\n",
+ " 18,\n",
+ " 15,\n",
+ " 0,\n",
+ " 8,\n",
+ " 1,\n",
+ " 17,\n",
+ " 2,\n",
+ " 9]"
+ ]
+ },
+ "execution_count": 145,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_1.forward_map"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 146,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'j'"
+ ]
+ },
+ "execution_count": 146,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_1.advance()\n",
+ "wheel_1.forward('a')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 147,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('jlekfcpuymsnvxgwtrozhaqbid', 'vxfzceouyadbjlsgwrkqhmpnit')"
+ ]
+ },
+ "execution_count": 147,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat(wheel_1.forward(l) for l in string.ascii_lowercase), cat(wheel_1.backward(l) for l in string.ascii_lowercase)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 148,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'b'"
+ ]
+ },
+ "execution_count": 148,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_1.position_l"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 149,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "wheel_2 = SimpleWheel(wheel_ii_spec)\n",
+ "assert(cat(wheel_2.forward(l) for l in string.ascii_lowercase) == 'ajdksiruxblhwtmcqgznpyfvoe')\n",
+ "assert(cat(wheel_2.backward(l) for l in string.ascii_lowercase) == 'ajpczwrlfbdkotyuqgenhxmivs')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 150,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('ajdksiruxblhwtmcqgznpyfvoe', 'ajpczwrlfbdkotyuqgenhxmivs')"
+ ]
+ },
+ "execution_count": 150,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "cat(wheel_2.forward(l) for l in string.ascii_lowercase), cat(wheel_2.backward(l) for l in string.ascii_lowercase)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 151,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "('bdfhjlcprtxvznyeiwgakmusqo', 'tagbpcsdqeufvnzhyixjwlrkom')"
+ ]
+ },
+ "execution_count": 151,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_3 = SimpleWheel(wheel_iii_spec)\n",
+ "wheel_3.set_position('a')\n",
+ "wheel_3.advance()\n",
+ "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'cegikboqswuymxdhvfzjltrpna')\n",
+ "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'zfaobrcpdteumygxhwivkqjnls')\n",
+ "assert(wheel_3.position == 1)\n",
+ "assert(wheel_3.position_l == 'b')\n",
+ "\n",
+ "for _ in range(24): wheel_3.advance()\n",
+ "assert(wheel_3.position == 25)\n",
+ "assert(wheel_3.position_l == 'z')\n",
+ "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'pcegikmdqsuywaozfjxhblnvtr')\n",
+ "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'nubhcqdterfvgwoaizjykxmslp')\n",
+ "\n",
+ "wheel_3.advance()\n",
+ "assert(wheel_3.position == 0)\n",
+ "assert(wheel_3.position_l == 'a')\n",
+ "assert(cat(wheel_3.forward(l) for l in string.ascii_lowercase) == 'bdfhjlcprtxvznyeiwgakmusqo')\n",
+ "assert(cat(wheel_3.backward(l) for l in string.ascii_lowercase) == 'tagbpcsdqeufvnzhyixjwlrkom')\n",
+ "\n",
+ "cat(wheel_3.forward(l) for l in string.ascii_lowercase), cat(wheel_3.backward(l) for l in string.ascii_lowercase)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "<a name=\"wheel\"></a>\n",
+ "## Wheel\n",
+ "[Top](#top)\n",
+ "\n",
+ "This is the same as a `SimpleWheel`, but with the addition of a ring.\n",
+ "\n",
+ "The ring is moveable around the core of the wheel (with the wiring). This means that moving the ring changes the orientation of the core and wiring for the same setting.\n",
+ "\n",
+ "| Wheel with notch | Notch showing peg to hold it in place |\n",
+ "| ---------------- | ------------------------------------- |\n",
+ "| <img src=\"enigma-notch.jpg\" alt=\"Enigma wheel with notch\" width=300> | <img src=\"dtu_notch_big.jpg\" alt=\"Enigma wheel with notch\" width=300> |\n",
+ "| (From [Crypto museum](http://www.cryptomuseum.com/crypto/enigma/img/300879/035/full.jpg)) | From [Matematik Sider](http://www.matematiksider.dk/enigma/dtu_notch_big.jpg) |\n",
+ "\n",
+ "Though it's not very visible in the right hand image, the extra metal below the ring shows a spring-loaded peg on the wheel core which drops into the ring, with one hole per letter. The ring setting is where the peg drops into the ring.\n",
+ "\n",
+ "The notch setting is used in the full Enigma to control when the wheels step forward.\n",
+ "\n",
+ "Note that the constructor calls the superclass's constructor, then sets the positions properly with a call to `set_position`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 152,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "class Wheel(SimpleWheel):\n",
+ " def __init__(self, transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False):\n",
+ " self.ring_notch_letters = ring_notch_letters\n",
+ " self.ring_setting = ring_setting\n",
+ " super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform)\n",
+ " self.set_position(position)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `position` of the wheel is the orientation of the core. It's the same as the ring if the ring setting is 1. The `position_l` attribute is used to report the position of the ring letter, which is what the Enigma operator would see. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 153,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def __getattribute__(self,name):\n",
+ " if name=='position_l':\n",
+ " return unpos(self.position + self.ring_setting - 1)\n",
+ " else:\n",
+ " return object.__getattribute__(self, name)\n",
+ "\n",
+ "def set_position(self, position):\n",
+ "# self.position = (pos(position) - self.ring_setting + 1) % 26\n",
+ " if isinstance(position, str):\n",
+ " self.position = (pos(position) - self.ring_setting + 1) % 26\n",
+ " else:\n",
+ " self.position = (position - self.ring_setting) % 26\n",
+ "# self.notch_positions = [(pos(position) - pos(p)) % 26 for p in self.ring_notch_letters]\n",
+ " self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26 for p in self.ring_notch_letters]\n",
+ " \n",
+ "setattr(Wheel, '__getattribute__', __getattribute__) \n",
+ "setattr(Wheel, 'set_position', set_position) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Advance the wheel. Again, note the superclass call, followed by the update of the notch positions."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 154,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def advance(self):\n",
+ " super(Wheel, self).advance()\n",
+ " self.notch_positions = [(p + 1) % 26 for p in self.notch_positions]\n",
+ " # self.position_l = unpos(self.position + self.ring_setting - 1)\n",
+ " return self.position\n",
+ " \n",
+ "setattr(Wheel, 'advance', advance) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 155,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', ring_setting=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 156,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(1, [6])"
+ ]
+ },
+ "execution_count": 156,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_3.position, wheel_3.notch_positions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 157,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(25, [2, 15])"
+ ]
+ },
+ "execution_count": 157,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
+ "wheel_6.position, wheel_6.notch_positions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 158,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0 [3, 16]\n",
+ "1 [4, 17]\n",
+ "2 [5, 18]\n",
+ "3 [6, 19]\n",
+ "4 [7, 20]\n",
+ "5 [8, 21]\n",
+ "6 [9, 22]\n",
+ "7 [10, 23]\n",
+ "8 [11, 24]\n",
+ "9 [12, 25]\n",
+ "10 [13, 0]\n",
+ "11 [14, 1]\n",
+ "12 [15, 2]\n",
+ "13 [16, 3]\n",
+ "14 [17, 4]\n",
+ "15 [18, 5]\n",
+ "16 [19, 6]\n",
+ "17 [20, 7]\n",
+ "18 [21, 8]\n",
+ "19 [22, 9]\n",
+ "20 [23, 10]\n",
+ "21 [24, 11]\n",
+ "22 [25, 12]\n",
+ "23 [0, 13]\n",
+ "24 [1, 14]\n",
+ "25 [2, 15]\n",
+ "0 [3, 16]\n"
+ ]
+ }
+ ],
+ "source": [
+ "for _ in range(27):\n",
+ " wheel_6.advance()\n",
+ " print(wheel_6.position, wheel_6.notch_positions)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 159,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "wheel = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', \n",
+ " ring_setting=3)\n",
+ "wheel.set_position(12)\n",
+ "assert(wheel.position == 9)\n",
+ "assert(16 in wheel.notch_positions)\n",
+ "assert(wheel.position_l =='l')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 160,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[16]"
+ ]
+ },
+ "execution_count": 160,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel.notch_positions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 161,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'l'"
+ ]
+ },
+ "execution_count": 161,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel.position_l"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 162,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "wheel_3 = Wheel(wheel_iii_spec, wheel_iii_notches, position='b', ring_setting=1)\n",
+ "assert(wheel_3.position == 1)\n",
+ "assert(wheel_3.notch_positions == [6])\n",
+ "assert(wheel_3.position_l == 'b')\n",
+ "wheel_3.advance()\n",
+ "assert(wheel_3.position == 2)\n",
+ "assert(wheel_3.notch_positions == [7])\n",
+ "assert(wheel_3.position_l == 'c')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 163,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[2, 15]"
+ ]
+ },
+ "execution_count": 163,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
+ "wheel_6.notch_positions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 164,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "wheel_6 = Wheel(wheel_vi_spec, wheel_vi_notches, position='b', ring_setting=3)\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
+ "assert(wheel_6.position == 25)\n",
+ "assert(2 in wheel_6.notch_positions)\n",
+ "assert(15 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'b')\n",
+ "\n",
+ "wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
+ "assert(wheel_6.position == 0)\n",
+ "assert(3 in wheel_6.notch_positions)\n",
+ "assert(16 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'c')\n",
+ "\n",
+ "for _ in range(22): wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'mgxantkzsyqjcufirldvhoewbp')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'dymswobuplgraevzkqifntxcjh')\n",
+ "assert(wheel_6.position == 22)\n",
+ "assert(25 in wheel_6.notch_positions)\n",
+ "assert(12 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'y')\n",
+ "\n",
+ "wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'fwzmsjyrxpibtehqkcugndvaol')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'xlrvnatokfqzduyjphemswbigc')\n",
+ "assert(wheel_6.position == 23)\n",
+ "assert(0 in wheel_6.notch_positions)\n",
+ "assert(13 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'z')\n",
+ "\n",
+ "wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'vylrixqwohasdgpjbtfmcuznke')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'kqumzsnjepyctxiogdlrvahfbw')\n",
+ "assert(wheel_6.position == 24)\n",
+ "assert(1 in wheel_6.notch_positions)\n",
+ "assert(14 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'a')\n",
+ "\n",
+ "wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'xkqhwpvngzrcfoiaselbtymjdu')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'ptlyrmidoxbswhnfckquzgeavj')\n",
+ "assert(wheel_6.position == 25)\n",
+ "assert(2 in wheel_6.notch_positions)\n",
+ "assert(15 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'b')\n",
+ "\n",
+ "wheel_6.advance()\n",
+ "assert(cat(wheel_6.forward(l) for l in string.ascii_lowercase) == 'jpgvoumfyqbenhzrdkasxlictw')\n",
+ "assert(cat(wheel_6.backward(l) for l in string.ascii_lowercase) == 'skxqlhcnwarvgmebjptyfdzuio')\n",
+ "assert(wheel_6.position == 0)\n",
+ "assert(3 in wheel_6.notch_positions)\n",
+ "assert(16 in wheel_6.notch_positions)\n",
+ "assert(wheel_6.position_l == 'c')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 165,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(0, 'c', [3, 16])"
+ ]
+ },
+ "execution_count": 165,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "wheel_6.position, wheel_6.position_l, wheel_6.notch_positions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "<a name=\"enigma\"></a>\n",
+ "## Enigma\n",
+ "[Top](#top)\n",
+ "\n",
+ "This is the full Enigma machine.\n",
+ "\n",
+ "It's a collection of the various components defined above. There are three wheels (left, middle, and right), a plugboard, and a reflector.\n",
+ "\n",
+ "The `__getattribute__` method returns the state of the machine in friendly form, generally by asking the components to return the relevant attributes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 167,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "class Enigma(object):\n",
+ " def __init__(self, reflector_spec,\n",
+ " left_wheel_spec, left_wheel_notches,\n",
+ " middle_wheel_spec, middle_wheel_notches,\n",
+ " right_wheel_spec, right_wheel_notches,\n",
+ " left_ring_setting, middle_ring_setting, right_ring_setting,\n",
+ " plugboard_setting):\n",
+ " self.reflector = Reflector(reflector_spec)\n",
+ " self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches, ring_setting=left_ring_setting)\n",
+ " self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches, ring_setting=middle_ring_setting)\n",
+ " self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches, ring_setting=right_ring_setting)\n",
+ " self.plugboard = Plugboard(plugboard_setting)\n",
+ " \n",
+ " def __getattribute__(self,name):\n",
+ " if name=='wheel_positions':\n",
+ " return self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position \n",
+ " elif name=='wheel_positions_l':\n",
+ " return self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l \n",
+ " elif name=='notch_positions':\n",
+ " return (self.left_wheel.notch_positions, \n",
+ " self.middle_wheel.notch_positions, \n",
+ " self.right_wheel.notch_positions)\n",
+ " else:\n",
+ " return object.__getattribute__(self, name)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Set the wheels to the initial positions. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 168,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position):\n",
+ " self.left_wheel.set_position(left_wheel_position)\n",
+ " self.middle_wheel.set_position(middle_wheel_position)\n",
+ " self.right_wheel.set_position(right_wheel_position)\n",
+ "\n",
+ "setattr(Enigma, 'set_wheels', set_wheels)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`lookup` just follows a path through the machine without changing the positions of any parts. It just follows a signal from the input, thorough all the components, to the reflector, back through all the components, to the output."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 169,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def lookup(self, letter):\n",
+ " a = self.plugboard.forward(letter)\n",
+ " b = self.right_wheel.forward(a)\n",
+ " c = self.middle_wheel.forward(b)\n",
+ " d = self.left_wheel.forward(c)\n",
+ " e = self.reflector.forward(d)\n",
+ " f = self.left_wheel.backward(e)\n",
+ " g = self.middle_wheel.backward(f)\n",
+ " h = self.right_wheel.backward(g)\n",
+ " i = self.plugboard.backward(h)\n",
+ " return i\n",
+ "\n",
+ "setattr(Enigma, 'lookup', lookup)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`advance` moves the wheels on one step. The right wheel always advances. If the notch is in the zero position, the wheel also advances the wheel to the left. \n",
+ "\n",
+ "It follows the 'double stepping' behaviour of the engima machines."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 170,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def advance(self):\n",
+ " advance_middle = False\n",
+ " advance_left = False\n",
+ " if 0 in self.right_wheel.notch_positions:\n",
+ " advance_middle = True\n",
+ " if 0 in self.middle_wheel.notch_positions:\n",
+ " advance_left = True\n",
+ " advance_middle = True\n",
+ " self.right_wheel.advance()\n",
+ " if advance_middle: self.middle_wheel.advance()\n",
+ " if advance_left: self.left_wheel.advance()\n",
+ "\n",
+ "setattr(Enigma, 'advance', advance) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Finally, encipher letters and messages. \n",
+ "\n",
+ "Note that the wheels advance _before_ the letter signal is sent through the machine: in the physical machine, the advancing is done by pressing the key on the keyboard. \n",
+ "\n",
+ "Also note that the messages are cleaned before use, so letters are converted to lower case and non-letters are removed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 220,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "def encipher_letter(self, letter):\n",
+ " self.advance()\n",
+ " return self.lookup(letter)\n",
+ "\n",
+ "def encipher(self, message, debug=False):\n",
+ " enciphered = ''\n",
+ " for letter in clean(message):\n",
+ " enciphered += self.encipher_letter(letter)\n",
+ " if debug:\n",
+ " print('Wheels now', list(self.wheel_positions_l), 'enciphering {} -> {}'.format(letter, self.lookup(letter)))\n",
+ " return enciphered\n",
+ "\n",
+ "setattr(Enigma, 'encipher_letter', encipher_letter)\n",
+ "setattr(Enigma, 'encipher', encipher)\n",
+ "setattr(Enigma, 'decipher', encipher)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "<a name=\"testingenigma\"></a>\n",
+ "## Testing Enigma\n",
+ "[Top](#top)\n",
+ "\n",
+ "Some tests of the Enigma machine."