Rearranged files, added import paths
[cipher-tools.git] / enigma.py
diff --git a/enigma.py b/enigma.py
deleted file mode 100644 (file)
index 89758f5..0000000
--- a/enigma.py
+++ /dev/null
@@ -1,353 +0,0 @@
-
-# coding: utf-8
-
-##################################
-# # Enigma machine
-##################################
-# 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
-import logging
-
-logger = logging.getLogger('engima')
-logger.setLevel(logging.WARNING)
-# logger.setLevel(logging.INFO)
-# logger.setLevel(logging.DEBUG)
-
-# create the logging file handler
-fh = logging.FileHandler("enigma.log")
-formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-fh.setFormatter(formatter)
-
-# add handler to logger object
-logger.addHandler(fh)
-
-
-# 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.
-    """
-    def __init__(self, specification, raw_transform=False):
-        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):
-        return list(zip(string.ascii_lowercase, clean(specification)))
-        # return specification
-    
-    def validate_transform(self, transform):
-        """A set of pairs, of from-to"""
-        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):
-        self.forward_map = [0] * 26
-        self.backward_map = [0] * 26
-            
-    def make_transform_map(self, transform):
-        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):
-        if letter in string.ascii_lowercase:
-            return unpos(self.forward_map[pos(letter)])
-        else:
-            return ''
-                
-    def backward(self, letter):
-        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):
-        return [tuple(clean(p)) for p in specification.split()]
-    
-    def validate_transform(self, transform):
-        """A set of pairs, of from-to"""
-        for p in transform:
-            if len(p) != 2:
-                raise ValueError("Not all mappings in transform"
-                    "have two elements")
-    
-    def make_empty_transform(self):
-        self.forward_map = list(range(26))
-        self.backward_map = list(range(26))
-        
-    def make_transform_map(self, transform):
-        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.
-    """
-    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 'q' 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):
-        if isinstance(position, str):
-            # self.position = ord(position) - ord('a')
-            self.position = pos(position)
-        else:
-            self.position = position
-    
-    def forward(self, letter):
-        if letter in string.ascii_lowercase:
-            return unpos((self.forward_map[(pos(letter) + self.position) % 26] - self.position))
-        else:
-            return ''
-                
-    def backward(self, letter):
-        if letter in string.ascii_lowercase:
-            return unpos((self.backward_map[(pos(letter) + self.position) % 26] - self.position))
-        else:
-            return ''
-        
-    def advance(self):
-        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):
-        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):
-        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):
-        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_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):
-        self.advance()
-        return self.lookup(letter)
-    
-    def encipher(self, message):
-        enciphered = ''
-        for letter in clean(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()
-