Module szyfrow.enigma
A simulator for Enigma machines.
See Bombe
for an implementation of the Bombe to break Enigma
messages.
Specification from Codes and Ciphers page.
Example Enigma machines from Louise Dale (full simulation) and EnigmaCo (good animation of the wheels, but no ring settings).
There's also the nice Enigma simulator for Android by Franklin Heath, available on the Google Play store.
Expand source code
"""A simulator for Enigma machines.
See `szyfrow.bombe.Bombe` for an implementation of the Bombe to break Enigma
messages.
Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.
Example Enigma machines from [Louise Dale](http://enigma.louisedade.co.uk/enigma.html) (full simulation) and [EnigmaCo](http://enigmaco.de/enigma/enigma.html) (good animation of the wheels, but no ring settings).
There's also the nice Enigma simulator for Android by [Franklin Heath](https://franklinheath.co.uk/2012/02/04/our-first-app-published-enigma-simulator/), available on the [Google Play store](https://play.google.com/store/apps/details?id=uk.co.franklinheath.enigmasim&hl=en_GB).
"""
import string
import collections
import multiprocessing
import itertools
from szyfrow.support.utilities import *
# # Some convenience functions
# cat = ''.join
# def clean(text): return cat(l.lower() for l in text if l in string.ascii_letters)
# def pos(letter):
# if letter in string.ascii_lowercase:
# return ord(letter) - ord('a')
# elif letter in string.ascii_uppercase:
# return ord(letter) - ord('A')
# else:
# return ''
# def unpos(number): return chr(number % 26 + ord('a'))
wheel_i_spec = 'ekmflgdqvzntowyhxuspaibrcj'
wheel_ii_spec = 'ajdksiruxblhwtmcqgznpyfvoe'
wheel_iii_spec = 'bdfhjlcprtxvznyeiwgakmusqo'
wheel_iv_spec = 'esovpzjayquirhxlnftgkdcmwb'
wheel_v_spec = 'vzbrgityupsdnhlxawmjqofeck'
wheel_vi_spec = 'jpgvoumfyqbenhzrdkasxlictw'
wheel_vii_spec = 'nzjhgrcxmyswboufaivlpekqdt'
wheel_viii_spec = 'fkqhtlxocbjspdzramewniuygv'
beta_wheel_spec = 'leyjvcnixwpbqmdrtakzgfuhos'
gamma_wheel_spec = 'fsokanuerhmbtiycwlqpzxvgjd'
wheel_i_notches = ['q']
wheel_ii_notches = ['e']
wheel_iii_notches = ['v']
wheel_iv_notches = ['j']
wheel_v_notches = ['z']
wheel_vi_notches = ['z', 'm']
wheel_vii_notches = ['z', 'm']
wheel_viii_notches = ['z', 'm']
reflector_b_spec = 'ay br cu dh eq fs gl ip jx kn mo tz vw'
reflector_c_spec = 'af bv cp dj ei go hy kr lz mx nw tq su'
class LetterTransformer(object):
"""A generic substitution cipher, that has different transforms in the
forward and backward directions. It requires that the transforms for all
letters by provided.
A `transform` is a list of letter pairs, like `[('a', 'b'), ('c', 'd')]`.
That would say that, in the forward direction `a` goes to `b` and
`c` goes to `d`. In the backward direction, `b` goes to `a` and `d` goes
to `c`.
"""
def __init__(self, specification, raw_transform=False):
"""Validate and create a new transformer. The transform is parsed by
`LetterTransformer.parse_specification` unless `raw_transform` is `True`
"""
if raw_transform:
transform = specification
else:
transform = self.parse_specification(specification)
self.validate_transform(transform)
self.make_transform_map(transform)
def parse_specification(self, specification):
"""Turn a `specification` string into a transform, by zipping it
with ASCII lowercase letters to generate the pairs. This assumes that
the `specification` defines the destination of the forward transform.
"""
return list(zip(string.ascii_lowercase, sanitise(specification)))
# return specification
def validate_transform(self, transform):
"""Checks that a transform is valid (every letter is mapped to
exactly one other letter, in both directions).
"""
if len(transform) != 26:
raise ValueError("Transform specification has {} pairs, requires 26".
format(len(transform)))
for p in transform:
if len(p) != 2:
raise ValueError("Not all mappings in transform "
"have two elements")
if len(set([p[0] for p in transform])) != 26:
raise ValueError("Transform specification must list 26 origin letters")
if len(set([p[1] for p in transform])) != 26:
raise ValueError("Transform specification must list 26 destination letters")
def make_empty_transform(self):
"""An empty transform is one that maps every letter to 'a'.
"""
self.forward_map = [0] * 26
self.backward_map = [0] * 26
def make_transform_map(self, transform):
"""Create `forward_map` and `backward_map` from `transform`. The maps
work on letter positions, not letter values. This makes the arithmetic
for wheels much easier.
"""
self.make_empty_transform()
for p in transform:
self.forward_map[pos(p[0])] = pos(p[1])
self.backward_map[pos(p[1])] = pos(p[0])
return self.forward_map, self.backward_map
def forward(self, letter):
"""Apply a map in the forward direction.
"""
if letter in string.ascii_lowercase:
return unpos(self.forward_map[pos(letter)])
else:
return ''
def backward(self, letter):
"""Apply a map in the backward direction.
"""
if letter in string.ascii_lowercase:
return unpos(self.backward_map[pos(letter)])
else:
return ''
class Plugboard(LetterTransformer):
"""A plugboard, a type of letter transformer where forward and backward
transforms are the same. If a letter isn't explicitly transformed, it is
kept as it is.
"""
def parse_specification(self, specification):
"""Convert a specification into a transform. The specification is
given as a list of letter pairs.
"""
return [tuple(sanitise(p)) for p in specification.split()]
def validate_transform(self, transform):
"""A set of pairs, of from-to. Does not require all 26 letters
are in the transform.
"""
for p in transform:
if len(p) != 2:
raise ValueError("Not all mappings in transform"
"have two elements")
def make_empty_transform(self):
"""An empty transform maps every letter to itself.
"""
self.forward_map = list(range(26))
self.backward_map = list(range(26))
def make_transform_map(self, transform):
"""Makes the maps for a plugboard. Ensures that if the pair ('a', 'b')
is in the specification, the pair ('b', 'a') is also present.
"""
expanded_transform = transform + [tuple(reversed(p)) for p in transform]
return super(Plugboard, self).make_transform_map(expanded_transform)
class Reflector(Plugboard):
"""A reflector is a plugboard that requires 13 transforms.
The 'plugboard' superclass ensures that all 13 transforms are also applied
in reverse, making 26 transforms in all.
"""
def validate_transform(self, transform):
if len(transform) != 13:
raise ValueError("Reflector specification has {} pairs, requires 13".
format(len(transform)))
if len(set([p[0] for p in transform] +
[p[1] for p in transform])) != 26:
raise ValueError("Reflector specification does not contain 26 letters")
try:
super(Reflector, self).validate_transform(transform)
except ValueError as v:
raise ValueError("Not all mappings in reflector have two elements")
class SimpleWheel(LetterTransformer):
"""A wheel is a transform that rotates.
Looking from the right, letters go in sequence a-b-c clockwise around the
wheel.
The position of the wheel is the number of spaces anticlockwise the wheel
has turned.
Letter inputs and outputs are given relative to the frame holding the wheel,
so if the wheel is advanced three places, an input of 'p' will enter the
wheel on the position under the wheel's 's' label.
"""
def __init__(self, transform, position='a', raw_transform=False):
super(SimpleWheel, self).__init__(transform, raw_transform)
self.set_position(position)
def __getattribute__(self,name):
if name=='position_l':
return unpos(self.position)
else:
return object.__getattribute__(self, name)
def set_position(self, position):
"""Sets a wheel's position. If the `position` is a string, convert it
to a number and set the position.
"""
if isinstance(position, str):
self.position = pos(position)
else:
self.position = position
def forward(self, letter):
"""Give the transformed letter in the forward direction, accounting
for the position of the wheel.
"""
if letter in string.ascii_lowercase:
return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))
else:
return ''
def backward(self, letter):
"""Give the transformed letter in the backward direction, accounting
for the position of the wheel.
"""
if letter in string.ascii_lowercase:
return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))
else:
return ''
def advance(self):
"""Advance a wheel one position."""
self.position = (self.position + 1) % 26
class Wheel(SimpleWheel):
"""A wheel with a movable ring.
The ring holds the letters and the notches that turn other wheels. The core
holds the wiring that does the transformation.
The ring position is how many steps the core is turned relative to the ring.
This is one-based, so a ring setting of 1 means the core and ring are
aligned.
The position of the wheel is the position of the core (the transforms)
relative to the neutral position.
The position_l is the position of the ring, or what would be observed
by the user of the Enigma machine.
The notch_positions are the number of advances of this wheel before it will
advance the next wheel.
"""
def __init__(self, transform, ring_notch_letters, ring_setting=1,
position='a', raw_transform=False):
self.ring_notch_letters = ring_notch_letters
self.ring_setting = ring_setting
super(Wheel, self).__init__(transform, position=position,
raw_transform=raw_transform)
self.set_position(position)
def __getattribute__(self,name):
if name=='position_l':
return unpos(self.position + self.ring_setting - 1)
else:
return object.__getattribute__(self, name)
def set_position(self, position):
if isinstance(position, str):
self.position = (pos(position) - self.ring_setting + 1) % 26
else:
self.position = (position - self.ring_setting) % 26
# # self.notch_positions = [(pos(p) - pos(position)) % 26 for p in self.ring_notch_letters]
# self.notch_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26 for p in self.ring_notch_letters]
self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26 for p in self.ring_notch_letters]
def advance(self):
"""Advance a wheel's core, then advance the ring position to match.
"""
super(Wheel, self).advance()
self.notch_positions = [(p + 1) % 26 for p in self.notch_positions]
return self.position
class Enigma(object):
"""An Enigma machine.
"""
def __init__(self, reflector_spec,
left_wheel_spec, left_wheel_notches,
middle_wheel_spec, middle_wheel_notches,
right_wheel_spec, right_wheel_notches,
left_ring_setting, middle_ring_setting, right_ring_setting,
plugboard_setting):
self.reflector = Reflector(reflector_spec)
self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches,
ring_setting=left_ring_setting)
self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches,
ring_setting=middle_ring_setting)
self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches,
ring_setting=right_ring_setting)
self.plugboard = Plugboard(plugboard_setting)
def __getattribute__(self,name):
if name=='wheel_positions':
return (self.left_wheel.position,
self.middle_wheel.position,
self.right_wheel.position
)
elif name=='wheel_positions_l':
return (self.left_wheel.position_l,
self.middle_wheel.position_l,
self.right_wheel.position_l
)
elif name=='notch_positions':
return (self.left_wheel.notch_positions,
self.middle_wheel.notch_positions,
self.right_wheel.notch_positions
)
else:
return object.__getattribute__(self, name)
def set_wheels(self, left_wheel_position, middle_wheel_position,
right_wheel_position):
"""Set the Enigma's wheels to the specified positions.
"""
self.left_wheel.set_position(left_wheel_position)
self.middle_wheel.set_position(middle_wheel_position)
self.right_wheel.set_position(right_wheel_position)
def lookup(self, letter):
"""Lookup the enciphering of a letter, without advancing any wheels
"""
a = self.plugboard.forward(letter)
b = self.right_wheel.forward(a)
c = self.middle_wheel.forward(b)
d = self.left_wheel.forward(c)
e = self.reflector.forward(d)
f = self.left_wheel.backward(e)
g = self.middle_wheel.backward(f)
h = self.right_wheel.backward(g)
i = self.plugboard.backward(h)
return i
def advance(self):
"""Advance the Enigma's wheels one step. The right wheel always
advances. The middle and right wheels may advance if the notches
line up correctly.
"""
advance_middle = False
advance_left = False
if 0 in self.right_wheel.notch_positions:
advance_middle = True
if 0 in self.middle_wheel.notch_positions:
advance_left = True
advance_middle = True
self.right_wheel.advance()
if advance_middle: self.middle_wheel.advance()
if advance_left: self.left_wheel.advance()
def encipher_letter(self, letter):
"""Encipher a letter. Advance the Enigma machine, then lookup the
encryption of a letter.
"""
self.advance()
return self.lookup(letter)
def encipher(self, message):
"""Encipher a message."""
enciphered = ''
for letter in sanitise(message):
enciphered += self.encipher_letter(letter)
return enciphered
decipher = encipher
# for i in range(26):
# enigma.advance()
# print('enigma.advance()')
# print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
# print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
# print("assert(enigma.notch_positions == {})".format(enigma.notch_positions))
# print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
# print()
if __name__ == "__main__":
import doctest
# doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})
doctest.testmod()
Functions
def cat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
def lcat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
def wcat(iterable, /)
-
Concatenate any number of strings.
The string whose method is called is inserted in between each given string. The result is returned as a new string.
Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
Classes
class Enigma (reflector_spec, left_wheel_spec, left_wheel_notches, middle_wheel_spec, middle_wheel_notches, right_wheel_spec, right_wheel_notches, left_ring_setting, middle_ring_setting, right_ring_setting, plugboard_setting)
-
An Enigma machine.
Expand source code
class Enigma(object): """An Enigma machine. """ def __init__(self, reflector_spec, left_wheel_spec, left_wheel_notches, middle_wheel_spec, middle_wheel_notches, right_wheel_spec, right_wheel_notches, left_ring_setting, middle_ring_setting, right_ring_setting, plugboard_setting): self.reflector = Reflector(reflector_spec) self.left_wheel = Wheel(left_wheel_spec, left_wheel_notches, ring_setting=left_ring_setting) self.middle_wheel = Wheel(middle_wheel_spec, middle_wheel_notches, ring_setting=middle_ring_setting) self.right_wheel = Wheel(right_wheel_spec, right_wheel_notches, ring_setting=right_ring_setting) self.plugboard = Plugboard(plugboard_setting) def __getattribute__(self,name): if name=='wheel_positions': return (self.left_wheel.position, self.middle_wheel.position, self.right_wheel.position ) elif name=='wheel_positions_l': return (self.left_wheel.position_l, self.middle_wheel.position_l, self.right_wheel.position_l ) elif name=='notch_positions': return (self.left_wheel.notch_positions, self.middle_wheel.notch_positions, self.right_wheel.notch_positions ) else: return object.__getattribute__(self, name) def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position): """Set the Enigma's wheels to the specified positions. """ self.left_wheel.set_position(left_wheel_position) self.middle_wheel.set_position(middle_wheel_position) self.right_wheel.set_position(right_wheel_position) def lookup(self, letter): """Lookup the enciphering of a letter, without advancing any wheels """ a = self.plugboard.forward(letter) b = self.right_wheel.forward(a) c = self.middle_wheel.forward(b) d = self.left_wheel.forward(c) e = self.reflector.forward(d) f = self.left_wheel.backward(e) g = self.middle_wheel.backward(f) h = self.right_wheel.backward(g) i = self.plugboard.backward(h) return i def advance(self): """Advance the Enigma's wheels one step. The right wheel always advances. The middle and right wheels may advance if the notches line up correctly. """ advance_middle = False advance_left = False if 0 in self.right_wheel.notch_positions: advance_middle = True if 0 in self.middle_wheel.notch_positions: advance_left = True advance_middle = True self.right_wheel.advance() if advance_middle: self.middle_wheel.advance() if advance_left: self.left_wheel.advance() def encipher_letter(self, letter): """Encipher a letter. Advance the Enigma machine, then lookup the encryption of a letter. """ self.advance() return self.lookup(letter) def encipher(self, message): """Encipher a message.""" enciphered = '' for letter in sanitise(message): enciphered += self.encipher_letter(letter) return enciphered decipher = encipher
Methods
def advance(self)
-
Advance the Enigma's wheels one step. The right wheel always advances. The middle and right wheels may advance if the notches line up correctly.
Expand source code
def advance(self): """Advance the Enigma's wheels one step. The right wheel always advances. The middle and right wheels may advance if the notches line up correctly. """ advance_middle = False advance_left = False if 0 in self.right_wheel.notch_positions: advance_middle = True if 0 in self.middle_wheel.notch_positions: advance_left = True advance_middle = True self.right_wheel.advance() if advance_middle: self.middle_wheel.advance() if advance_left: self.left_wheel.advance()
def decipher(self, message)
-
Encipher a message.
Expand source code
def encipher(self, message): """Encipher a message.""" enciphered = '' for letter in sanitise(message): enciphered += self.encipher_letter(letter) return enciphered
def encipher(self, message)
-
Encipher a message.
Expand source code
def encipher(self, message): """Encipher a message.""" enciphered = '' for letter in sanitise(message): enciphered += self.encipher_letter(letter) return enciphered
def encipher_letter(self, letter)
-
Encipher a letter. Advance the Enigma machine, then lookup the encryption of a letter.
Expand source code
def encipher_letter(self, letter): """Encipher a letter. Advance the Enigma machine, then lookup the encryption of a letter. """ self.advance() return self.lookup(letter)
def lookup(self, letter)
-
Lookup the enciphering of a letter, without advancing any wheels
Expand source code
def lookup(self, letter): """Lookup the enciphering of a letter, without advancing any wheels """ a = self.plugboard.forward(letter) b = self.right_wheel.forward(a) c = self.middle_wheel.forward(b) d = self.left_wheel.forward(c) e = self.reflector.forward(d) f = self.left_wheel.backward(e) g = self.middle_wheel.backward(f) h = self.right_wheel.backward(g) i = self.plugboard.backward(h) return i
def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position)
-
Set the Enigma's wheels to the specified positions.
Expand source code
def set_wheels(self, left_wheel_position, middle_wheel_position, right_wheel_position): """Set the Enigma's wheels to the specified positions. """ self.left_wheel.set_position(left_wheel_position) self.middle_wheel.set_position(middle_wheel_position) self.right_wheel.set_position(right_wheel_position)
class LetterTransformer (specification, raw_transform=False)
-
A generic substitution cipher, that has different transforms in the forward and backward directions. It requires that the transforms for all letters by provided.
A
transform
is a list of letter pairs, like[('a', 'b'), ('c', 'd')]
. That would say that, in the forward directiona
goes tob
andc
goes tod
. In the backward direction,b
goes toa
andd
goes toc
.Validate and create a new transformer. The transform is parsed by
LetterTransformer.parse_specification()
unlessraw_transform
isTrue
Expand source code
class LetterTransformer(object): """A generic substitution cipher, that has different transforms in the forward and backward directions. It requires that the transforms for all letters by provided. A `transform` is a list of letter pairs, like `[('a', 'b'), ('c', 'd')]`. That would say that, in the forward direction `a` goes to `b` and `c` goes to `d`. In the backward direction, `b` goes to `a` and `d` goes to `c`. """ def __init__(self, specification, raw_transform=False): """Validate and create a new transformer. The transform is parsed by `LetterTransformer.parse_specification` unless `raw_transform` is `True` """ if raw_transform: transform = specification else: transform = self.parse_specification(specification) self.validate_transform(transform) self.make_transform_map(transform) def parse_specification(self, specification): """Turn a `specification` string into a transform, by zipping it with ASCII lowercase letters to generate the pairs. This assumes that the `specification` defines the destination of the forward transform. """ return list(zip(string.ascii_lowercase, sanitise(specification))) # return specification def validate_transform(self, transform): """Checks that a transform is valid (every letter is mapped to exactly one other letter, in both directions). """ if len(transform) != 26: raise ValueError("Transform specification has {} pairs, requires 26". format(len(transform))) for p in transform: if len(p) != 2: raise ValueError("Not all mappings in transform " "have two elements") if len(set([p[0] for p in transform])) != 26: raise ValueError("Transform specification must list 26 origin letters") if len(set([p[1] for p in transform])) != 26: raise ValueError("Transform specification must list 26 destination letters") def make_empty_transform(self): """An empty transform is one that maps every letter to 'a'. """ self.forward_map = [0] * 26 self.backward_map = [0] * 26 def make_transform_map(self, transform): """Create `forward_map` and `backward_map` from `transform`. The maps work on letter positions, not letter values. This makes the arithmetic for wheels much easier. """ self.make_empty_transform() for p in transform: self.forward_map[pos(p[0])] = pos(p[1]) self.backward_map[pos(p[1])] = pos(p[0]) return self.forward_map, self.backward_map def forward(self, letter): """Apply a map in the forward direction. """ if letter in string.ascii_lowercase: return unpos(self.forward_map[pos(letter)]) else: return '' def backward(self, letter): """Apply a map in the backward direction. """ if letter in string.ascii_lowercase: return unpos(self.backward_map[pos(letter)]) else: return ''
Subclasses
Methods
def backward(self, letter)
-
Apply a map in the backward direction.
Expand source code
def backward(self, letter): """Apply a map in the backward direction. """ if letter in string.ascii_lowercase: return unpos(self.backward_map[pos(letter)]) else: return ''
def forward(self, letter)
-
Apply a map in the forward direction.
Expand source code
def forward(self, letter): """Apply a map in the forward direction. """ if letter in string.ascii_lowercase: return unpos(self.forward_map[pos(letter)]) else: return ''
def make_empty_transform(self)
-
An empty transform is one that maps every letter to 'a'.
Expand source code
def make_empty_transform(self): """An empty transform is one that maps every letter to 'a'. """ self.forward_map = [0] * 26 self.backward_map = [0] * 26
def make_transform_map(self, transform)
-
Create
forward_map
andbackward_map
fromtransform
. The maps work on letter positions, not letter values. This makes the arithmetic for wheels much easier.Expand source code
def make_transform_map(self, transform): """Create `forward_map` and `backward_map` from `transform`. The maps work on letter positions, not letter values. This makes the arithmetic for wheels much easier. """ self.make_empty_transform() for p in transform: self.forward_map[pos(p[0])] = pos(p[1]) self.backward_map[pos(p[1])] = pos(p[0]) return self.forward_map, self.backward_map
def parse_specification(self, specification)
-
Turn a
specification
string into a transform, by zipping it with ASCII lowercase letters to generate the pairs. This assumes that thespecification
defines the destination of the forward transform.Expand source code
def parse_specification(self, specification): """Turn a `specification` string into a transform, by zipping it with ASCII lowercase letters to generate the pairs. This assumes that the `specification` defines the destination of the forward transform. """ return list(zip(string.ascii_lowercase, sanitise(specification))) # return specification
def validate_transform(self, transform)
-
Checks that a transform is valid (every letter is mapped to exactly one other letter, in both directions).
Expand source code
def validate_transform(self, transform): """Checks that a transform is valid (every letter is mapped to exactly one other letter, in both directions). """ if len(transform) != 26: raise ValueError("Transform specification has {} pairs, requires 26". format(len(transform))) for p in transform: if len(p) != 2: raise ValueError("Not all mappings in transform " "have two elements") if len(set([p[0] for p in transform])) != 26: raise ValueError("Transform specification must list 26 origin letters") if len(set([p[1] for p in transform])) != 26: raise ValueError("Transform specification must list 26 destination letters")
class Plugboard (specification, raw_transform=False)
-
A plugboard, a type of letter transformer where forward and backward transforms are the same. If a letter isn't explicitly transformed, it is kept as it is.
Validate and create a new transformer. The transform is parsed by
LetterTransformer.parse_specification()
unlessraw_transform
isTrue
Expand source code
class Plugboard(LetterTransformer): """A plugboard, a type of letter transformer where forward and backward transforms are the same. If a letter isn't explicitly transformed, it is kept as it is. """ def parse_specification(self, specification): """Convert a specification into a transform. The specification is given as a list of letter pairs. """ return [tuple(sanitise(p)) for p in specification.split()] def validate_transform(self, transform): """A set of pairs, of from-to. Does not require all 26 letters are in the transform. """ for p in transform: if len(p) != 2: raise ValueError("Not all mappings in transform" "have two elements") def make_empty_transform(self): """An empty transform maps every letter to itself. """ self.forward_map = list(range(26)) self.backward_map = list(range(26)) def make_transform_map(self, transform): """Makes the maps for a plugboard. Ensures that if the pair ('a', 'b') is in the specification, the pair ('b', 'a') is also present. """ expanded_transform = transform + [tuple(reversed(p)) for p in transform] return super(Plugboard, self).make_transform_map(expanded_transform)
Ancestors
Subclasses
Methods
def make_empty_transform(self)
-
An empty transform maps every letter to itself.
Expand source code
def make_empty_transform(self): """An empty transform maps every letter to itself. """ self.forward_map = list(range(26)) self.backward_map = list(range(26))
def make_transform_map(self, transform)
-
Makes the maps for a plugboard. Ensures that if the pair ('a', 'b') is in the specification, the pair ('b', 'a') is also present.
Expand source code
def make_transform_map(self, transform): """Makes the maps for a plugboard. Ensures that if the pair ('a', 'b') is in the specification, the pair ('b', 'a') is also present. """ expanded_transform = transform + [tuple(reversed(p)) for p in transform] return super(Plugboard, self).make_transform_map(expanded_transform)
def parse_specification(self, specification)
-
Convert a specification into a transform. The specification is given as a list of letter pairs.
Expand source code
def parse_specification(self, specification): """Convert a specification into a transform. The specification is given as a list of letter pairs. """ return [tuple(sanitise(p)) for p in specification.split()]
def validate_transform(self, transform)
-
A set of pairs, of from-to. Does not require all 26 letters are in the transform.
Expand source code
def validate_transform(self, transform): """A set of pairs, of from-to. Does not require all 26 letters are in the transform. """ for p in transform: if len(p) != 2: raise ValueError("Not all mappings in transform" "have two elements")
Inherited members
class Reflector (specification, raw_transform=False)
-
A reflector is a plugboard that requires 13 transforms. The 'plugboard' superclass ensures that all 13 transforms are also applied in reverse, making 26 transforms in all.
Validate and create a new transformer. The transform is parsed by
LetterTransformer.parse_specification()
unlessraw_transform
isTrue
Expand source code
class Reflector(Plugboard): """A reflector is a plugboard that requires 13 transforms. The 'plugboard' superclass ensures that all 13 transforms are also applied in reverse, making 26 transforms in all. """ def validate_transform(self, transform): if len(transform) != 13: raise ValueError("Reflector specification has {} pairs, requires 13". format(len(transform))) if len(set([p[0] for p in transform] + [p[1] for p in transform])) != 26: raise ValueError("Reflector specification does not contain 26 letters") try: super(Reflector, self).validate_transform(transform) except ValueError as v: raise ValueError("Not all mappings in reflector have two elements")
Ancestors
Inherited members
class SimpleWheel (transform, position='a', raw_transform=False)
-
A wheel is a transform that rotates.
Looking from the right, letters go in sequence a-b-c clockwise around the wheel.
The position of the wheel is the number of spaces anticlockwise the wheel has turned.
Letter inputs and outputs are given relative to the frame holding the wheel, so if the wheel is advanced three places, an input of 'p' will enter the wheel on the position under the wheel's 's' label.
Validate and create a new transformer. The transform is parsed by
LetterTransformer.parse_specification()
unlessraw_transform
isTrue
Expand source code
class SimpleWheel(LetterTransformer): """A wheel is a transform that rotates. Looking from the right, letters go in sequence a-b-c clockwise around the wheel. The position of the wheel is the number of spaces anticlockwise the wheel has turned. Letter inputs and outputs are given relative to the frame holding the wheel, so if the wheel is advanced three places, an input of 'p' will enter the wheel on the position under the wheel's 's' label. """ def __init__(self, transform, position='a', raw_transform=False): super(SimpleWheel, self).__init__(transform, raw_transform) self.set_position(position) def __getattribute__(self,name): if name=='position_l': return unpos(self.position) else: return object.__getattribute__(self, name) def set_position(self, position): """Sets a wheel's position. If the `position` is a string, convert it to a number and set the position. """ if isinstance(position, str): self.position = pos(position) else: self.position = position def forward(self, letter): """Give the transformed letter in the forward direction, accounting for the position of the wheel. """ if letter in string.ascii_lowercase: return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position)) else: return '' def backward(self, letter): """Give the transformed letter in the backward direction, accounting for the position of the wheel. """ if letter in string.ascii_lowercase: return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position)) else: return '' def advance(self): """Advance a wheel one position.""" self.position = (self.position + 1) % 26
Ancestors
Subclasses
Methods
def advance(self)
-
Advance a wheel one position.
Expand source code
def advance(self): """Advance a wheel one position.""" self.position = (self.position + 1) % 26
def backward(self, letter)
-
Give the transformed letter in the backward direction, accounting for the position of the wheel.
Expand source code
def backward(self, letter): """Give the transformed letter in the backward direction, accounting for the position of the wheel. """ if letter in string.ascii_lowercase: return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position)) else: return ''
def forward(self, letter)
-
Give the transformed letter in the forward direction, accounting for the position of the wheel.
Expand source code
def forward(self, letter): """Give the transformed letter in the forward direction, accounting for the position of the wheel. """ if letter in string.ascii_lowercase: return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position)) else: return ''
def set_position(self, position)
-
Sets a wheel's position. If the
position
is a string, convert it to a number and set the position.Expand source code
def set_position(self, position): """Sets a wheel's position. If the `position` is a string, convert it to a number and set the position. """ if isinstance(position, str): self.position = pos(position) else: self.position = position
Inherited members
class Wheel (transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False)
-
A wheel with a movable ring.
The ring holds the letters and the notches that turn other wheels. The core holds the wiring that does the transformation.
The ring position is how many steps the core is turned relative to the ring. This is one-based, so a ring setting of 1 means the core and ring are aligned.
The position of the wheel is the position of the core (the transforms) relative to the neutral position.
The position_l is the position of the ring, or what would be observed by the user of the Enigma machine.
The notch_positions are the number of advances of this wheel before it will advance the next wheel.
Validate and create a new transformer. The transform is parsed by
LetterTransformer.parse_specification()
unlessraw_transform
isTrue
Expand source code
class Wheel(SimpleWheel): """A wheel with a movable ring. The ring holds the letters and the notches that turn other wheels. The core holds the wiring that does the transformation. The ring position is how many steps the core is turned relative to the ring. This is one-based, so a ring setting of 1 means the core and ring are aligned. The position of the wheel is the position of the core (the transforms) relative to the neutral position. The position_l is the position of the ring, or what would be observed by the user of the Enigma machine. The notch_positions are the number of advances of this wheel before it will advance the next wheel. """ def __init__(self, transform, ring_notch_letters, ring_setting=1, position='a', raw_transform=False): self.ring_notch_letters = ring_notch_letters self.ring_setting = ring_setting super(Wheel, self).__init__(transform, position=position, raw_transform=raw_transform) self.set_position(position) def __getattribute__(self,name): if name=='position_l': return unpos(self.position + self.ring_setting - 1) else: return object.__getattribute__(self, name) def set_position(self, position): if isinstance(position, str): self.position = (pos(position) - self.ring_setting + 1) % 26 else: self.position = (position - self.ring_setting) % 26 # # self.notch_positions = [(pos(p) - pos(position)) % 26 for p in self.ring_notch_letters] # self.notch_positions = [(pos(p) - (self.position + self.ring_setting - 1)) % 26 for p in self.ring_notch_letters] self.notch_positions = [(self.position + self.ring_setting - 1 - pos(p)) % 26 for p in self.ring_notch_letters] def advance(self): """Advance a wheel's core, then advance the ring position to match. """ super(Wheel, self).advance() self.notch_positions = [(p + 1) % 26 for p in self.notch_positions] return self.position
Ancestors
Methods
def advance(self)
-
Advance a wheel's core, then advance the ring position to match.
Expand source code
def advance(self): """Advance a wheel's core, then advance the ring position to match. """ super(Wheel, self).advance() self.notch_positions = [(p + 1) % 26 for p in self.notch_positions] return self.position
Inherited members