Module szyfrow.bombe
A simulator for Bombe machines.
See Enigma
for an implementation of the Enigma to create
messages.
There is a good explanation of how the bombe worked by Graham Ellsbury.
In this implementation, there are banks of wires (what Ellsbury refers to
as "cables"), one bank for each position that appears in the menu. A bank
comprises 26 wires, represented as a dict
of bool
s, depending on whether
that wire is live ("energised") or not.
The menu, derived from the crib, determines how the scramblers connect the
banks. A Connection
represents this.
Expand source code
"""A simulator for Bombe machines.
See `szyfrow.enigma.Enigma` for an implementation of the Enigma to create
messages.
There is a good explanation of [how the bombe worked](http://www.ellsbury.com/enigmabombe.htm)
by Graham Ellsbury.
In this implementation, there are *banks* of wires (what Ellsbury refers to
as "cables"), one bank for each position that appears in the menu. A bank
comprises 26 wires, represented as a `dict` of `bool`s, depending on whether
that wire is live ("energised") or not.
The menu, derived from the crib, determines how the scramblers connect the
banks. A `Connection` represents this.
"""
import string
import collections
import multiprocessing
import itertools
import logging
from szyfrow.enigma import *
__pdoc__ = {}
Signal = collections.namedtuple('Signal', ['bank', 'wire'])
__pdoc__['Signal'] = """Current propogation through the Bombe indicates that
this wire in this bank is live, and the effects need to be proogated further
through the machine.
"""
__pdoc__['Signal.bank'] = """The bank of a signal."""
__pdoc__['Signal.wire'] = """The wire of a signal."""
Connection = collections.namedtuple('Connection', ['banks', 'scrambler'])
__pdoc__['Connection'] = """A connection between banks made by a particular
scrambler (the scrambler state given by its position in the crib).
"""
__pdoc__['Connection.banks'] = """A list of two items, holding the bnaks of
a connection."""
__pdoc__['Connection.scrambler'] = """The bnaks of a connection."""
MenuItem = collections.namedtuple('MenuIem', ['before', 'after', 'number'])
__pdoc__['MenuItem'] = """One item in the menu, derived from the crib.
"""
__pdoc__['MenuItem.before'] = "The letter before the transform (plaintext)."
__pdoc__['MenuItem.after'] = "The letter after the transform (ciphertext)."
__pdoc__['MenuItem.number'] = "The position of this item in the menu."
def make_menu(plaintext, ciphertext):
"""Create a menu from a crib: a given plaintext and ciphertext.
No validation is done to ensure that this is a viable crib (e.g. no
checking for length, no checking that a letter is enciphered to itself).
"""
return [MenuItem(p, c, i+1)
for i, (p, c) in enumerate(zip(plaintext, ciphertext))]
class Scrambler(object):
"""A scrambler is a collection of three `szyfrow.enigma.SimpleWheel`s.
"""
def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec,
wheel1_pos='a', wheel2_pos='a', wheel3_pos='a'):
self.wheel1 = SimpleWheel(wheel1_spec, position=wheel1_pos)
self.wheel2 = SimpleWheel(wheel2_spec, position=wheel2_pos)
self.wheel3 = SimpleWheel(wheel3_spec, position=wheel3_pos)
self.reflector = Reflector(reflector_spec)
__pdoc__['Scrambler.wheel_positions'] = """Return a 3-tuple of the wheel
positions (as numbers)"""
__pdoc__['Scrambler.wheel_positions_l'] = """Return a 3-tuple of the wheel
positions (as letters)"""
def __getattribute__(self, name):
if name=='wheel_positions':
return self.wheel1.position, self.wheel2.position, self.wheel3.position
elif name=='wheel_positions_l':
return self.wheel1.position_l, self.wheel2.position_l, self.wheel3.position_l
else:
return object.__getattribute__(self, name)
def advance(self, wheel1=False, wheel2=False, wheel3=True):
"""Advance some wheels of a scrambler.
"""
if wheel1: self.wheel1.advance()
if wheel2: self.wheel2.advance()
if wheel3: self.wheel3.advance()
def lookup(self, letter):
"""Lookup the decipherment of a letter, given a particular scrambler
orientation.
"""
a = self.wheel3.forward(letter)
b = self.wheel2.forward(a)
c = self.wheel1.forward(b)
d = self.reflector.forward(c)
e = self.wheel1.backward(d)
f = self.wheel2.backward(e)
g = self.wheel3.backward(f)
return g
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos):
"""Set the positions of a scrambler's wheels.
"""
self.wheel1.set_position(wheel1_pos)
self.wheel2.set_position(wheel2_pos)
self.wheel3.set_position(wheel3_pos)
class Bombe(object):
"""An entire Bombe machine.
This specifies the pattern of the wheels and reflectors used. The
scramblers are connected and wired up according the to the specification
given by the menu.
Bombe objects are callable. Calling a Bombe (with the starting scrambler
positions) calls the `test` method and returns the pair of
`start_positions` and the result of `test`.
Bombe objects have attributes `wheel_positions` and `wheel_positions_l`,
which return the results of the scramblers' `Scrambler.wheel_positions`
and `Scrambler.wheel_positions_l`.
"""
def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec,
menu=None, start_signal=None, use_diagonal_board=True,
verify_plugboard=True):
self.connections = []
self.wheel1_spec = wheel1_spec
self.wheel2_spec = wheel2_spec
self.wheel3_spec = wheel3_spec
self.reflector_spec = reflector_spec
if menu:
self.read_menu(menu)
if start_signal:
self.test_start = start_signal
self.use_diagonal_board = use_diagonal_board
self.verify_plugboard = verify_plugboard
__pdoc__['Bombe.wheel_positions'] = """Return a 3-tuple of the wheel
positions (as numbers)"""
__pdoc__['Bomb3.wheel_positions_l'] = """Return a 3-tuple of the wheel
positions (as letters)"""
def __getattribute__(self, name):
if name=='wheel_positions':
return self.connections[0].scrambler.wheel_positions
elif name=='wheel_positions_l':
return self.connections[0].scrambler.wheel_positions_l
else:
return object.__getattribute__(self, name)
def __call__(self, start_positions):
return start_positions, self.test(initial_signal=self.test_start,
start_positions=start_positions,
use_diagonal_board=self.use_diagonal_board,
verify_plugboard=self.verify_plugboard)
def add_connection(self, bank_before, bank_after, scrambler):
"""Create a new connection between banks.
"""
self.connections += [Connection([bank_before, bank_after], scrambler)]
def read_menu(self, menu):
"""Read a menu, creating one scrambler for each element of the menu
and setting up the connections it implies. Also defines the most
common letter in the menu's plaintext as the default letter to start
testing with."""
self.connections = []
for item in menu:
scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec,
self.reflector_spec,
wheel3_pos=unpos(item.number - 1))
self.add_connection(item.before, item.after, scrambler)
most_common_letter = (collections.Counter(m.before for m in menu) +\
collections.Counter(m.after for m in menu)).most_common(1)[0][0]
self.test_start = Signal(most_common_letter, most_common_letter)
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos):
"""Set positions of all scramblers. The first scrambler will be set
to the specified positions. The second scrambler will have its
third wheel advanced one position; the third scramber will have its
third wheel advanced two positios; and so on. Not that the first and
second wheels of the scramblers are never advanced in setup."""
for i, c in enumerate(self.connections):
c.scrambler.set_positions(wheel1_pos, wheel2_pos, unpos(pos(wheel3_pos) + i))
def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True,
verify_plugboard=True):
"""Test a scrambler setting. It creates a signal (held in
`self.pending`) on the `initial_signal` wire then uses
`Bombe.propagate` to propagate the signal across the Bombe.
Returns a Boolean recording if this scrambler setting with
this signal is a "stop" (potential valid scrambler setting).
* If `initial_signal` is `None`, use the default starting signal set in
`Bombe.read_menu`
* If `start_positions` is `None`, use the existing scramber positions."""
self.banks = {label:
dict(zip(string.ascii_lowercase,
[False]*len(string.ascii_lowercase)))
for label in string.ascii_lowercase}
if start_positions:
self.set_positions(*start_positions)
if not initial_signal:
initial_signal = self.test_start
self.pending = [initial_signal]
self.propagate(use_diagonal_board)
live_wire_count = len([self.banks[self.test_start.bank][w]
for w in self.banks[self.test_start.bank]
if self.banks[self.test_start.bank][w]])
if live_wire_count < 26:
if verify_plugboard:
possibles = self.possible_plugboards()
return all(s0.isdisjoint(s1)
for s0 in possibles
for s1 in possibles
if s0 != s1)
else:
return True
else:
return False
def propagate(self, use_diagonal_board):
"""Propagate a signal through the Bombe. Uses `self.pending` as an
agenda for a breadth-first search. Each element on the agenda represents
a particular wire in a bank that is being "energised" (set to `True`).
The first element in the agenda is removed, the wire/bank is set,
then all connected wire/banks are added to the `self.pending`
agenda.
"""
while self.pending:
current = self.pending[0]
# print("processing", current)
self.pending = self.pending[1:]
if not self.banks[current.bank][current.wire]:
self.banks[current.bank][current.wire] = True
if use_diagonal_board:
self.pending += [Signal(current.wire, current.bank)]
for c in self.connections:
if current.bank in c.banks:
other_bank = [b for b in c.banks if b != current.bank][0]
other_wire = c.scrambler.lookup(current.wire)
# print(" adding", other_bank, other_wire, "because", c.banks)
self.pending += [Signal(other_bank, other_wire)]
def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a',
use_diagonal_board=True):
"""Run a Bombe after setup with a menu, by trying all scramber
positions. For each scrambler position, `Bombe.test` is run. If the
test is successful, the scrambler positiions are added to `self.solutions`.
`self.Solutions` is returned.
"""
if not run_start:
run_start = self.test_start
self.solutions = []
self.set_positions(wheel1_pos, wheel2_pos, wheel3_pos)
for run_index in range(26*26*26):
if self.test(initial_signal=run_start, use_diagonal_board=use_diagonal_board):
self.solutions += [self.connections[0].scrambler.wheel_positions_l]
advance3 = True
advance2 = False
advance1 = False
if (run_index + 1) % 26 == 0: advance2 = True
if (run_index + 1) % (26*26) == 0: advance1 = True
for c in self.connections:
c.scrambler.advance(advance1, advance2, advance3)
return self.solutions
def possible_plugboards(self):
"""Given a Bombe after a `Bombe.test` has been performed, determine
what plugboard settings can be derived from the solution.
"""
possibles = set()
for b in self.banks:
active = [w for w in self.banks[b] if self.banks[b][w]]
inactive = [w for w in self.banks[b] if not self.banks[b][w]]
if len(active) == 1:
possibles = possibles.union({frozenset((b, active[0]))})
if len(inactive) == 1:
possibles = possibles.union({frozenset((b, inactive[0]))})
return possibles
def run_multi_bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu,
start_signal=None, use_diagonal_board=True,
verify_plugboard=True):
"""Run a Bombe solution, spreading the load across multiple CPU cores.
Similar to `Bombe.run` in effects, but quicker on a multi-core machine.
"""
allwheels = itertools.product(string.ascii_lowercase, repeat=3)
with multiprocessing.Pool() as pool:
res = pool.map(Bombe(wheel1_spec, wheel2_spec, wheel3_spec,
reflector_spec, menu=menu, start_signal=start_signal,
use_diagonal_board=use_diagonal_board,
verify_plugboard=verify_plugboard),
allwheels)
return [r[0] for r in res if r[1]]
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'
-
Create a menu from a crib: a given plaintext and ciphertext.
No validation is done to ensure that this is a viable crib (e.g. no checking for length, no checking that a letter is enciphered to itself).
Expand source code
def make_menu(plaintext, ciphertext): """Create a menu from a crib: a given plaintext and ciphertext. No validation is done to ensure that this is a viable crib (e.g. no checking for length, no checking that a letter is enciphered to itself). """ return [MenuItem(p, c, i+1) for i, (p, c) in enumerate(zip(plaintext, ciphertext))]
def run_multi_bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu, start_signal=None, use_diagonal_board=True, verify_plugboard=True)
-
Run a Bombe solution, spreading the load across multiple CPU cores. Similar to
Bombe.run()
in effects, but quicker on a multi-core machine.Expand source code
def run_multi_bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu, start_signal=None, use_diagonal_board=True, verify_plugboard=True): """Run a Bombe solution, spreading the load across multiple CPU cores. Similar to `Bombe.run` in effects, but quicker on a multi-core machine. """ allwheels = itertools.product(string.ascii_lowercase, repeat=3) with multiprocessing.Pool() as pool: res = pool.map(Bombe(wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu=menu, start_signal=start_signal, use_diagonal_board=use_diagonal_board, verify_plugboard=verify_plugboard), allwheels) return [r[0] for r in res if r[1]]
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 Bombe (wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu=None, start_signal=None, use_diagonal_board=True, verify_plugboard=True)
-
An entire Bombe machine.
This specifies the pattern of the wheels and reflectors used. The scramblers are connected and wired up according the to the specification given by the menu.
Bombe objects are callable. Calling a Bombe (with the starting scrambler positions) calls the
test
method and returns the pair ofstart_positions
and the result oftest
.Bombe objects have attributes
wheel_positions
andwheel_positions_l
, which return the results of the scramblers'Scrambler.wheel_positions
andScrambler.wheel_positions_l
.Expand source code
class Bombe(object): """An entire Bombe machine. This specifies the pattern of the wheels and reflectors used. The scramblers are connected and wired up according the to the specification given by the menu. Bombe objects are callable. Calling a Bombe (with the starting scrambler positions) calls the `test` method and returns the pair of `start_positions` and the result of `test`. Bombe objects have attributes `wheel_positions` and `wheel_positions_l`, which return the results of the scramblers' `Scrambler.wheel_positions` and `Scrambler.wheel_positions_l`. """ def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, menu=None, start_signal=None, use_diagonal_board=True, verify_plugboard=True): self.connections = [] self.wheel1_spec = wheel1_spec self.wheel2_spec = wheel2_spec self.wheel3_spec = wheel3_spec self.reflector_spec = reflector_spec if menu: self.read_menu(menu) if start_signal: self.test_start = start_signal self.use_diagonal_board = use_diagonal_board self.verify_plugboard = verify_plugboard __pdoc__['Bombe.wheel_positions'] = """Return a 3-tuple of the wheel positions (as numbers)""" __pdoc__['Bomb3.wheel_positions_l'] = """Return a 3-tuple of the wheel positions (as letters)""" def __getattribute__(self, name): if name=='wheel_positions': return self.connections[0].scrambler.wheel_positions elif name=='wheel_positions_l': return self.connections[0].scrambler.wheel_positions_l else: return object.__getattribute__(self, name) def __call__(self, start_positions): return start_positions, self.test(initial_signal=self.test_start, start_positions=start_positions, use_diagonal_board=self.use_diagonal_board, verify_plugboard=self.verify_plugboard) def add_connection(self, bank_before, bank_after, scrambler): """Create a new connection between banks. """ self.connections += [Connection([bank_before, bank_after], scrambler)] def read_menu(self, menu): """Read a menu, creating one scrambler for each element of the menu and setting up the connections it implies. Also defines the most common letter in the menu's plaintext as the default letter to start testing with.""" self.connections = [] for item in menu: scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec, self.reflector_spec, wheel3_pos=unpos(item.number - 1)) self.add_connection(item.before, item.after, scrambler) most_common_letter = (collections.Counter(m.before for m in menu) +\ collections.Counter(m.after for m in menu)).most_common(1)[0][0] self.test_start = Signal(most_common_letter, most_common_letter) def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): """Set positions of all scramblers. The first scrambler will be set to the specified positions. The second scrambler will have its third wheel advanced one position; the third scramber will have its third wheel advanced two positios; and so on. Not that the first and second wheels of the scramblers are never advanced in setup.""" for i, c in enumerate(self.connections): c.scrambler.set_positions(wheel1_pos, wheel2_pos, unpos(pos(wheel3_pos) + i)) def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True, verify_plugboard=True): """Test a scrambler setting. It creates a signal (held in `self.pending`) on the `initial_signal` wire then uses `Bombe.propagate` to propagate the signal across the Bombe. Returns a Boolean recording if this scrambler setting with this signal is a "stop" (potential valid scrambler setting). * If `initial_signal` is `None`, use the default starting signal set in `Bombe.read_menu` * If `start_positions` is `None`, use the existing scramber positions.""" self.banks = {label: dict(zip(string.ascii_lowercase, [False]*len(string.ascii_lowercase))) for label in string.ascii_lowercase} if start_positions: self.set_positions(*start_positions) if not initial_signal: initial_signal = self.test_start self.pending = [initial_signal] self.propagate(use_diagonal_board) live_wire_count = len([self.banks[self.test_start.bank][w] for w in self.banks[self.test_start.bank] if self.banks[self.test_start.bank][w]]) if live_wire_count < 26: if verify_plugboard: possibles = self.possible_plugboards() return all(s0.isdisjoint(s1) for s0 in possibles for s1 in possibles if s0 != s1) else: return True else: return False def propagate(self, use_diagonal_board): """Propagate a signal through the Bombe. Uses `self.pending` as an agenda for a breadth-first search. Each element on the agenda represents a particular wire in a bank that is being "energised" (set to `True`). The first element in the agenda is removed, the wire/bank is set, then all connected wire/banks are added to the `self.pending` agenda. """ while self.pending: current = self.pending[0] # print("processing", current) self.pending = self.pending[1:] if not self.banks[current.bank][current.wire]: self.banks[current.bank][current.wire] = True if use_diagonal_board: self.pending += [Signal(current.wire, current.bank)] for c in self.connections: if current.bank in c.banks: other_bank = [b for b in c.banks if b != current.bank][0] other_wire = c.scrambler.lookup(current.wire) # print(" adding", other_bank, other_wire, "because", c.banks) self.pending += [Signal(other_bank, other_wire)] def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a', use_diagonal_board=True): """Run a Bombe after setup with a menu, by trying all scramber positions. For each scrambler position, `Bombe.test` is run. If the test is successful, the scrambler positiions are added to `self.solutions`. `self.Solutions` is returned. """ if not run_start: run_start = self.test_start self.solutions = [] self.set_positions(wheel1_pos, wheel2_pos, wheel3_pos) for run_index in range(26*26*26): if self.test(initial_signal=run_start, use_diagonal_board=use_diagonal_board): self.solutions += [self.connections[0].scrambler.wheel_positions_l] advance3 = True advance2 = False advance1 = False if (run_index + 1) % 26 == 0: advance2 = True if (run_index + 1) % (26*26) == 0: advance1 = True for c in self.connections: c.scrambler.advance(advance1, advance2, advance3) return self.solutions def possible_plugboards(self): """Given a Bombe after a `Bombe.test` has been performed, determine what plugboard settings can be derived from the solution. """ possibles = set() for b in self.banks: active = [w for w in self.banks[b] if self.banks[b][w]] inactive = [w for w in self.banks[b] if not self.banks[b][w]] if len(active) == 1: possibles = possibles.union({frozenset((b, active[0]))}) if len(inactive) == 1: possibles = possibles.union({frozenset((b, inactive[0]))}) return possibles
Methods
def add_connection(self, bank_before, bank_after, scrambler)
-
Create a new connection between banks.
Expand source code
def add_connection(self, bank_before, bank_after, scrambler): """Create a new connection between banks. """ self.connections += [Connection([bank_before, bank_after], scrambler)]
def possible_plugboards(self)
-
Given a Bombe after a
Bombe.test()
has been performed, determine what plugboard settings can be derived from the solution.Expand source code
def possible_plugboards(self): """Given a Bombe after a `Bombe.test` has been performed, determine what plugboard settings can be derived from the solution. """ possibles = set() for b in self.banks: active = [w for w in self.banks[b] if self.banks[b][w]] inactive = [w for w in self.banks[b] if not self.banks[b][w]] if len(active) == 1: possibles = possibles.union({frozenset((b, active[0]))}) if len(inactive) == 1: possibles = possibles.union({frozenset((b, inactive[0]))}) return possibles
def propagate(self, use_diagonal_board)
-
Propagate a signal through the Bombe. Uses
self.pending
as an agenda for a breadth-first search. Each element on the agenda represents a particular wire in a bank that is being "energised" (set toTrue
). The first element in the agenda is removed, the wire/bank is set, then all connected wire/banks are added to theself.pending
agenda.Expand source code
def propagate(self, use_diagonal_board): """Propagate a signal through the Bombe. Uses `self.pending` as an agenda for a breadth-first search. Each element on the agenda represents a particular wire in a bank that is being "energised" (set to `True`). The first element in the agenda is removed, the wire/bank is set, then all connected wire/banks are added to the `self.pending` agenda. """ while self.pending: current = self.pending[0] # print("processing", current) self.pending = self.pending[1:] if not self.banks[current.bank][current.wire]: self.banks[current.bank][current.wire] = True if use_diagonal_board: self.pending += [Signal(current.wire, current.bank)] for c in self.connections: if current.bank in c.banks: other_bank = [b for b in c.banks if b != current.bank][0] other_wire = c.scrambler.lookup(current.wire) # print(" adding", other_bank, other_wire, "because", c.banks) self.pending += [Signal(other_bank, other_wire)]
-
Read a menu, creating one scrambler for each element of the menu and setting up the connections it implies. Also defines the most common letter in the menu's plaintext as the default letter to start testing with.
Expand source code
def read_menu(self, menu): """Read a menu, creating one scrambler for each element of the menu and setting up the connections it implies. Also defines the most common letter in the menu's plaintext as the default letter to start testing with.""" self.connections = [] for item in menu: scrambler = Scrambler(self.wheel1_spec, self.wheel2_spec, self.wheel3_spec, self.reflector_spec, wheel3_pos=unpos(item.number - 1)) self.add_connection(item.before, item.after, scrambler) most_common_letter = (collections.Counter(m.before for m in menu) +\ collections.Counter(m.after for m in menu)).most_common(1)[0][0] self.test_start = Signal(most_common_letter, most_common_letter)
def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a', use_diagonal_board=True)
-
Run a Bombe after setup with a menu, by trying all scramber positions. For each scrambler position,
Bombe.test()
is run. If the test is successful, the scrambler positiions are added toself.solutions
.self.Solutions
is returned.Expand source code
def run(self, run_start=None, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a', use_diagonal_board=True): """Run a Bombe after setup with a menu, by trying all scramber positions. For each scrambler position, `Bombe.test` is run. If the test is successful, the scrambler positiions are added to `self.solutions`. `self.Solutions` is returned. """ if not run_start: run_start = self.test_start self.solutions = [] self.set_positions(wheel1_pos, wheel2_pos, wheel3_pos) for run_index in range(26*26*26): if self.test(initial_signal=run_start, use_diagonal_board=use_diagonal_board): self.solutions += [self.connections[0].scrambler.wheel_positions_l] advance3 = True advance2 = False advance1 = False if (run_index + 1) % 26 == 0: advance2 = True if (run_index + 1) % (26*26) == 0: advance1 = True for c in self.connections: c.scrambler.advance(advance1, advance2, advance3) return self.solutions
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos)
-
Set positions of all scramblers. The first scrambler will be set to the specified positions. The second scrambler will have its third wheel advanced one position; the third scramber will have its third wheel advanced two positios; and so on. Not that the first and second wheels of the scramblers are never advanced in setup.
Expand source code
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): """Set positions of all scramblers. The first scrambler will be set to the specified positions. The second scrambler will have its third wheel advanced one position; the third scramber will have its third wheel advanced two positios; and so on. Not that the first and second wheels of the scramblers are never advanced in setup.""" for i, c in enumerate(self.connections): c.scrambler.set_positions(wheel1_pos, wheel2_pos, unpos(pos(wheel3_pos) + i))
def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True, verify_plugboard=True)
-
Test a scrambler setting. It creates a signal (held in
self.pending
) on theinitial_signal
wire then usesBombe.propagate()
to propagate the signal across the Bombe.Returns a Boolean recording if this scrambler setting with this signal is a "stop" (potential valid scrambler setting).
- If
initial_signal
isNone
, use the default starting signal set inBombe.read_menu()
- If
start_positions
isNone
, use the existing scramber positions.
Expand source code
def test(self, initial_signal=None, start_positions=None, use_diagonal_board=True, verify_plugboard=True): """Test a scrambler setting. It creates a signal (held in `self.pending`) on the `initial_signal` wire then uses `Bombe.propagate` to propagate the signal across the Bombe. Returns a Boolean recording if this scrambler setting with this signal is a "stop" (potential valid scrambler setting). * If `initial_signal` is `None`, use the default starting signal set in `Bombe.read_menu` * If `start_positions` is `None`, use the existing scramber positions.""" self.banks = {label: dict(zip(string.ascii_lowercase, [False]*len(string.ascii_lowercase))) for label in string.ascii_lowercase} if start_positions: self.set_positions(*start_positions) if not initial_signal: initial_signal = self.test_start self.pending = [initial_signal] self.propagate(use_diagonal_board) live_wire_count = len([self.banks[self.test_start.bank][w] for w in self.banks[self.test_start.bank] if self.banks[self.test_start.bank][w]]) if live_wire_count < 26: if verify_plugboard: possibles = self.possible_plugboards() return all(s0.isdisjoint(s1) for s0 in possibles for s1 in possibles if s0 != s1) else: return True else: return False
- If
class Connection (banks, scrambler)
-
A connection between banks made by a particular scrambler (the scrambler state given by its position in the crib).
Ancestors
- builtins.tuple
Instance variables
var banks
-
A list of two items, holding the bnaks of a connection.
var scrambler
-
The bnaks of a connection.
class MenuItem (before, after, number)
-
MenuIem(before, after, number)
Ancestors
- builtins.tuple
Instance variables
var after
-
Alias for field number 1
var before
-
Alias for field number 0
var number
-
Alias for field number 2
class Scrambler (wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a')
-
A scrambler is a collection of three
SimpleWheel
s.Expand source code
class Scrambler(object): """A scrambler is a collection of three `szyfrow.enigma.SimpleWheel`s. """ def __init__(self, wheel1_spec, wheel2_spec, wheel3_spec, reflector_spec, wheel1_pos='a', wheel2_pos='a', wheel3_pos='a'): self.wheel1 = SimpleWheel(wheel1_spec, position=wheel1_pos) self.wheel2 = SimpleWheel(wheel2_spec, position=wheel2_pos) self.wheel3 = SimpleWheel(wheel3_spec, position=wheel3_pos) self.reflector = Reflector(reflector_spec) __pdoc__['Scrambler.wheel_positions'] = """Return a 3-tuple of the wheel positions (as numbers)""" __pdoc__['Scrambler.wheel_positions_l'] = """Return a 3-tuple of the wheel positions (as letters)""" def __getattribute__(self, name): if name=='wheel_positions': return self.wheel1.position, self.wheel2.position, self.wheel3.position elif name=='wheel_positions_l': return self.wheel1.position_l, self.wheel2.position_l, self.wheel3.position_l else: return object.__getattribute__(self, name) def advance(self, wheel1=False, wheel2=False, wheel3=True): """Advance some wheels of a scrambler. """ if wheel1: self.wheel1.advance() if wheel2: self.wheel2.advance() if wheel3: self.wheel3.advance() def lookup(self, letter): """Lookup the decipherment of a letter, given a particular scrambler orientation. """ a = self.wheel3.forward(letter) b = self.wheel2.forward(a) c = self.wheel1.forward(b) d = self.reflector.forward(c) e = self.wheel1.backward(d) f = self.wheel2.backward(e) g = self.wheel3.backward(f) return g def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): """Set the positions of a scrambler's wheels. """ self.wheel1.set_position(wheel1_pos) self.wheel2.set_position(wheel2_pos) self.wheel3.set_position(wheel3_pos)
Methods
def advance(self, wheel1=False, wheel2=False, wheel3=True)
-
Advance some wheels of a scrambler.
Expand source code
def advance(self, wheel1=False, wheel2=False, wheel3=True): """Advance some wheels of a scrambler. """ if wheel1: self.wheel1.advance() if wheel2: self.wheel2.advance() if wheel3: self.wheel3.advance()
def lookup(self, letter)
-
Lookup the decipherment of a letter, given a particular scrambler orientation.
Expand source code
def lookup(self, letter): """Lookup the decipherment of a letter, given a particular scrambler orientation. """ a = self.wheel3.forward(letter) b = self.wheel2.forward(a) c = self.wheel1.forward(b) d = self.reflector.forward(c) e = self.wheel1.backward(d) f = self.wheel2.backward(e) g = self.wheel3.backward(f) return g
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos)
-
Set the positions of a scrambler's wheels.
Expand source code
def set_positions(self, wheel1_pos, wheel2_pos, wheel3_pos): """Set the positions of a scrambler's wheels. """ self.wheel1.set_position(wheel1_pos) self.wheel2.set_position(wheel2_pos) self.wheel3.set_position(wheel3_pos)
class Signal (bank, wire)
-
Current propogation through the Bombe indicates that this wire in this bank is live, and the effects need to be proogated further through the machine.
Ancestors
- builtins.tuple
Instance variables
var bank
-
The bank of a signal.
var wire
-
The wire of a signal.