5 # Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.
7 # 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).
9 # 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).
16 # Some convenience functions
20 def clean(text
): return cat(l
.lower() for l
in text
if l
in string
.ascii_letters
)
23 if letter
in string
.ascii_lowercase
:
24 return ord(letter
) - ord('a')
25 elif letter
in string
.ascii_uppercase
:
26 return ord(letter
) - ord('A')
30 def unpos(number
): return chr(number
% 26 + ord('a'))
33 wheel_i_spec
= 'ekmflgdqvzntowyhxuspaibrcj'
34 wheel_ii_spec
= 'ajdksiruxblhwtmcqgznpyfvoe'
35 wheel_iii_spec
= 'bdfhjlcprtxvznyeiwgakmusqo'
36 wheel_iv_spec
= 'esovpzjayquirhxlnftgkdcmwb'
37 wheel_v_spec
= 'vzbrgityupsdnhlxawmjqofeck'
38 wheel_vi_spec
= 'jpgvoumfyqbenhzrdkasxlictw'
39 wheel_vii_spec
= 'nzjhgrcxmyswboufaivlpekqdt'
40 wheel_viii_spec
= 'fkqhtlxocbjspdzramewniuygv'
41 beta_wheel_spec
= 'leyjvcnixwpbqmdrtakzgfuhos'
42 gamma_wheel_spec
= 'fsokanuerhmbtiycwlqpzxvgjd'
46 wheel_iii_pegs
= ['v']
49 wheel_vi_pegs
= ['z', 'm']
50 wheel_vii_pegs
= ['z', 'm']
51 wheel_viii_pegs
= ['z', 'm']
53 reflector_b_spec
= 'ay br cu dh eq fs gl ip jx kn mo tz vw'
54 reflector_c_spec
= 'af bv cp dj ei go hy kr lz mx nw tq su'
58 class LetterTransformer(object):
59 """A generic substitution cipher, that has different transforms in the
60 forward and backward directions. It requires that the transforms for all
63 >>> lt = LetterTransformer([('z', 'a')] + [(l, string.ascii_lowercase[i+1]) \
64 for i, l in enumerate(string.ascii_lowercase[:-1])], \
67 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0]
69 [25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
71 >>> lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))
73 [25, 24, 23, 22, 2, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
75 [5, 6, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 3, 2, 1, 0]
76 >>> cat(lt.forward(l) for l in string.ascii_lowercase)
77 'zyxwcabdefghijklmnopqrstuv'
78 >>> cat(lt.backward(l) for l in string.ascii_lowercase)
79 'fgehijklmnopqrstuvwxyzdcba'
81 def __init__(self
, specification
, raw_transform
=False):
83 transform
= specification
85 transform
= self
.parse_specification(specification
)
86 self
.validate_transform(transform
)
87 self
.make_transform_map(transform
)
89 def parse_specification(self
, specification
):
90 return list(zip(string
.ascii_lowercase
, clean(specification
)))
91 # return specification
93 def validate_transform(self
, transform
):
94 """A set of pairs, of from-to"""
95 if len(transform
) != 26:
96 raise ValueError("Transform specification has {} pairs, requires 26".
97 format(len(transform
)))
100 raise ValueError("Not all mappings in transform "
102 if len(set([p
[0] for p
in transform
])) != 26:
103 raise ValueError("Transform specification must list 26 origin letters")
104 if len(set([p
[1] for p
in transform
])) != 26:
105 raise ValueError("Transform specification must list 26 destination letters")
107 def make_empty_transform(self
):
108 self
.forward_map
= [0] * 26
109 self
.backward_map
= [0] * 26
111 def make_transform_map(self
, transform
):
112 self
.make_empty_transform()
114 self
.forward_map
[pos(p
[0])] = pos(p
[1])
115 self
.backward_map
[pos(p
[1])] = pos(p
[0])
116 return self
.forward_map
, self
.backward_map
118 def forward(self
, letter
):
119 if letter
in string
.ascii_lowercase
:
120 return unpos(self
.forward_map
[pos(letter
)])
124 def backward(self
, letter
):
125 if letter
in string
.ascii_lowercase
:
126 return unpos(self
.backward_map
[pos(letter
)])
131 class Plugboard(LetterTransformer
):
132 """A plugboard, a type of letter transformer where forward and backward
133 transforms are the same. If a letter isn't explicitly transformed, it is
136 >>> pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper())
138 [20, 6, 2, 3, 24, 15, 1, 11, 13, 25, 10, 7, 12, 8, 18, 5, 17, 16, 14, 23, 0, 21, 22, 19, 4, 9]
139 >>> pb.forward_map == pb.backward_map
141 >>> cat(pb.forward(l) for l in string.ascii_lowercase)
142 'ugcdypblnzkhmisfrqoxavwtej'
143 >>> cat(pb.backward(l) for l in string.ascii_lowercase)
144 'ugcdypblnzkhmisfrqoxavwtej'
146 def parse_specification(self
, specification
):
147 return [tuple(clean(p
)) for p
in specification
.split()]
149 def validate_transform(self
, transform
):
150 """A set of pairs, of from-to"""
153 raise ValueError("Not all mappings in transform"
156 def make_empty_transform(self
):
157 self
.forward_map
= list(range(26))
158 self
.backward_map
= list(range(26))
160 def make_transform_map(self
, transform
):
161 expanded_transform
= transform
+ [tuple(reversed(p
)) for p
in transform
]
162 return super(Plugboard
, self
).make_transform_map(expanded_transform
)
167 class Reflector(Plugboard
):
168 """A reflector is a plugboard that requires 13 transforms.
170 >>> reflector_b = Reflector(reflector_b_spec)
171 >>> reflector_b.forward_map == reflector_b.backward_map
173 >>> reflector_b.forward_map
174 [24, 17, 20, 7, 16, 18, 11, 3, 15, 23, 13, 6, 14, 10, 12, 8, 4, 1, 5, 25, 2, 22, 21, 9, 0, 19]
175 >>> cat(reflector_b.forward(l) for l in string.ascii_lowercase)
176 'yruhqsldpxngokmiebfzcwvjat'
177 >>> cat(reflector_b.backward(l) for l in string.ascii_lowercase)
178 'yruhqsldpxngokmiebfzcwvjat'
180 def validate_transform(self
, transform
):
181 if len(transform
) != 13:
182 raise ValueError("Reflector specification has {} pairs, requires 13".
183 format(len(transform
)))
184 if len(set([p
[0] for p
in transform
] +
185 [p
[1] for p
in transform
])) != 26:
186 raise ValueError("Reflector specification does not contain 26 letters")
188 super(Reflector
, self
).validate_transform(transform
)
189 except ValueError as v
:
190 raise ValueError("Not all mappings in reflector have two elements")
195 class SimpleWheel(LetterTransformer
):
196 """A wheel is a transform that rotates.
198 Looking from the right, letters go in sequence a-b-c clockwise around the
201 The position of the wheel is the number of spaces anticlockwise the wheel
204 Letter inputs and outputs are given relative to the frame holding the wheel,
205 so if the wheel is advanced three places, an input of 'p' will enter the
206 wheel on the position under the wheel's 'q' label.
208 >>> rotor_1_transform = list(zip(string.ascii_lowercase, 'EKMFLGDQVZNTOWYHXUSPAIBRCJ'.lower()))
209 >>> wheel_1 = SimpleWheel(rotor_1_transform, raw_transform=True)
210 >>> cat(wheel_1.forward(l) for l in string.ascii_lowercase)
211 'ekmflgdqvzntowyhxuspaibrcj'
212 >>> cat(wheel_1.backward(l) for l in string.ascii_lowercase)
213 'uwygadfpvzbeckmthxslrinqoj'
216 >>> wheel_2 = SimpleWheel(wheel_ii_spec)
217 >>> cat(wheel_2.forward(l) for l in string.ascii_lowercase)
218 'ajdksiruxblhwtmcqgznpyfvoe'
219 >>> cat(wheel_2.backward(l) for l in string.ascii_lowercase)
220 'ajpczwrlfbdkotyuqgenhxmivs'
222 >>> wheel_3 = SimpleWheel(wheel_iii_spec)
223 >>> wheel_3.set_position('a')
224 >>> wheel_3.advance()
225 >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
226 'cegikboqswuymxdhvfzjltrpna'
227 >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
228 'zfaobrcpdteumygxhwivkqjnls'
231 >>> wheel_3.position_l
234 >>> for _ in range(24): wheel_3.advance()
237 >>> wheel_3.position_l
239 >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
240 'pcegikmdqsuywaozfjxhblnvtr'
241 >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
242 'nubhcqdterfvgwoaizjykxmslp'
244 >>> wheel_3.advance()
247 >>> wheel_3.position_l
249 >>> cat(wheel_3.forward(l) for l in string.ascii_lowercase)
250 'bdfhjlcprtxvznyeiwgakmusqo'
251 >>> cat(wheel_3.backward(l) for l in string.ascii_lowercase)
252 'tagbpcsdqeufvnzhyixjwlrkom'
254 def __init__(self
, transform
, position
='a', raw_transform
=False):
255 super(SimpleWheel
, self
).__init
__(transform
, raw_transform
)
256 self
.set_position(position
)
258 def __getattribute__(self
,name
):
259 if name
=='position_l':
260 return unpos(self
.position
)
262 return object.__getattribute
__(self
, name
)
264 def set_position(self
, position
):
265 self
.position
= ord(position
) - ord('a')
267 def forward(self
, letter
):
268 if letter
in string
.ascii_lowercase
:
269 return unpos((self
.forward_map
[(pos(letter
) + self
.position
) % 26] - self
.position
))
273 def backward(self
, letter
):
274 if letter
in string
.ascii_lowercase
:
275 return unpos((self
.backward_map
[(pos(letter
) + self
.position
) % 26] - self
.position
))
280 self
.position
= (self
.position
+ 1) % 26
281 # return self.position
285 class Wheel(SimpleWheel
):
286 """A wheel with a movable ring.
288 The ring holds the letters and the pegs that turn other wheels. The core
289 holds the wiring that does the transformation.
291 The ring position is how many steps the core is turned relative to the ring.
292 This is one-based, so a ring setting of 1 means the core and ring are
295 The position of the wheel is the position of the core (the transforms)
296 relative to the neutral position.
298 The position_l is the position of the ring, or what would be observed
299 by the user of the Enigma machine.
301 The peg_positions are the number of advances of this wheel before it will
302 advance the next wheel.
304 >>> wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)
307 >>> wheel_3.peg_positions
309 >>> wheel_3.position_l
311 >>> wheel_3.advance()
314 >>> wheel_3.peg_positions
316 >>> wheel_3.position_l
319 >>> wheel_6 = Wheel(wheel_vi_spec, wheel_vi_pegs, position='b', ring_setting=3)
320 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
321 'xkqhwpvngzrcfoiaselbtymjdu'
322 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
323 'ptlyrmidoxbswhnfckquzgeavj'
326 >>> 11 in wheel_6.peg_positions
328 >>> 24 in wheel_6.peg_positions
330 >>> wheel_6.position_l
333 >>> wheel_6.advance()
334 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
335 'jpgvoumfyqbenhzrdkasxlictw'
336 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
337 'skxqlhcnwarvgmebjptyfdzuio'
340 >>> 10 in wheel_6.peg_positions
342 >>> 23 in wheel_6.peg_positions
344 >>> wheel_6.position_l
347 >>> for _ in range(22): wheel_6.advance()
348 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
349 'mgxantkzsyqjcufirldvhoewbp'
350 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
351 'dymswobuplgraevzkqifntxcjh'
354 >>> 1 in wheel_6.peg_positions
356 >>> 14 in wheel_6.peg_positions
358 >>> wheel_6.position_l
361 >>> wheel_6.advance()
362 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
363 'fwzmsjyrxpibtehqkcugndvaol'
364 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
365 'xlrvnatokfqzduyjphemswbigc'
368 >>> 0 in wheel_6.peg_positions
370 >>> 13 in wheel_6.peg_positions
372 >>> wheel_6.position_l
375 >>> wheel_6.advance()
376 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
377 'vylrixqwohasdgpjbtfmcuznke'
378 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
379 'kqumzsnjepyctxiogdlrvahfbw'
382 >>> 25 in wheel_6.peg_positions
384 >>> 12 in wheel_6.peg_positions
386 >>> wheel_6.position_l
389 >>> wheel_6.advance()
390 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
391 'xkqhwpvngzrcfoiaselbtymjdu'
392 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
393 'ptlyrmidoxbswhnfckquzgeavj'
396 >>> 24 in wheel_6.peg_positions
398 >>> 11 in wheel_6.peg_positions
400 >>> wheel_6.position_l
403 >>> wheel_6.advance()
404 >>> cat(wheel_6.forward(l) for l in string.ascii_lowercase)
405 'jpgvoumfyqbenhzrdkasxlictw'
406 >>> cat(wheel_6.backward(l) for l in string.ascii_lowercase)
407 'skxqlhcnwarvgmebjptyfdzuio'
410 >>> 23 in wheel_6.peg_positions
412 >>> 10 in wheel_6.peg_positions
414 >>> wheel_6.position_l
418 def __init__(self
, transform
, ring_peg_letters
, ring_setting
=1, position
='a', raw_transform
=False):
419 self
.ring_peg_letters
= ring_peg_letters
420 self
.ring_setting
= ring_setting
421 super(Wheel
, self
).__init
__(transform
, position
=position
, raw_transform
=raw_transform
)
422 self
.set_position(position
)
424 def __getattribute__(self
,name
):
425 if name
=='position_l':
426 return unpos(self
.position
+ self
.ring_setting
- 1)
428 return object.__getattribute
__(self
, name
)
430 def set_position(self
, position
):
431 self
.position
= (pos(position
) - self
.ring_setting
+ 1) % 26
432 # self.position_l = position
433 self
.peg_positions
= [(pos(p
) - pos(position
)) % 26 for p
in self
.ring_peg_letters
]
436 super(Wheel
, self
).advance()
437 self
.peg_positions
= [(p
- 1) % 26 for p
in self
.peg_positions
]
438 # self.position_l = unpos(self.position + self.ring_setting - 1)
439 # return self.position
444 class Enigma(object):
445 """An Enigma machine.
447 >>> enigma = Enigma(reflector_b_spec, \
448 wheel_i_spec, wheel_i_pegs, \
449 wheel_ii_spec, wheel_ii_pegs, \
450 wheel_iii_spec, wheel_iii_pegs, \
453 >>> enigma.set_wheels('a', 'a', 't')
454 >>> enigma.wheel_positions
456 >>> cat(enigma.wheel_positions_l)
458 >>> enigma.peg_positions
460 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
461 'puvioztjdhxmlyeawsrgbcqknf'
464 >>> enigma.wheel_positions
466 >>> cat(enigma.wheel_positions_l)
468 >>> enigma.peg_positions
470 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
471 'baigpldqcowfyzjehvtsxrkumn'
474 >>> enigma.wheel_positions
476 >>> cat(enigma.wheel_positions_l)
478 >>> enigma.peg_positions
480 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
481 'mnvfydiwgzsoablrxpkutchqej'
484 >>> enigma.wheel_positions
486 >>> cat(enigma.wheel_positions_l)
488 >>> enigma.peg_positions
490 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
491 'ulfopcykswhbzvderqixanjtgm'
494 >>> enigma.wheel_positions
496 >>> cat(enigma.wheel_positions_l)
498 >>> enigma.peg_positions
500 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
501 'qmwftdyovursbzhxaklejicpgn'
504 >>> enigma.wheel_positions
506 >>> cat(enigma.wheel_positions_l)
508 >>> enigma.peg_positions
510 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
511 'oljmzxrvucybdqasngpwihtfke'
516 >>> enigma.set_wheels('a', 'd', 't')
517 >>> enigma.wheel_positions
519 >>> cat(enigma.wheel_positions_l)
521 >>> enigma.peg_positions
523 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
524 'zcbpqxwsjiuonmldethrkygfva'
527 >>> enigma.wheel_positions
529 >>> cat(enigma.wheel_positions_l)
531 >>> enigma.peg_positions
533 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
534 'ehprawjbngotxikcsdqlzyfmvu'
537 >>> enigma.wheel_positions
539 >>> cat(enigma.wheel_positions_l)
541 >>> enigma.peg_positions
543 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
544 'eqzxarpihmnvjkwgbfuyslodtc'
547 >>> enigma.wheel_positions
549 >>> cat(enigma.wheel_positions_l)
551 >>> enigma.peg_positions
553 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
554 'qedcbtpluzmhkongavwfirsyxj'
557 >>> enigma.wheel_positions
559 >>> cat(enigma.wheel_positions_l)
561 >>> enigma.peg_positions
563 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
564 'iwuedhsfazqxytvrkpgncoblmj'
567 >>> enigma.wheel_positions
569 >>> cat(enigma.wheel_positions_l)
571 >>> enigma.peg_positions
573 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
574 'baknstqzrmcxjdvygiefwoulph'
577 >>> enigma.set_wheels('a', 'a', 'a')
578 >>> ct = enigma.encipher('testmessage')
582 >>> enigma.set_wheels('a', 'd', 't')
583 >>> ct = enigma.encipher('testmessage')
588 >>> enigma.set_wheels('b', 'd', 'q')
589 >>> ct = enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
591 'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'
592 >>> enigma.left_wheel.position_l
594 >>> enigma.middle_wheel.position_l
596 >>> enigma.right_wheel.position_l
599 # Setting sheet line 31 from http://www.codesandciphers.org.uk/enigma/enigma3.htm
600 # Enigma simulation settings are
601 # http://enigma.louisedade.co.uk/enigma.html?m3;b;b153;AFTX;AJEU;AU-BG-EY-FP-HL-IN-JZ-OS-QR-TX
602 >>> enigma31 = Enigma(reflector_b_spec, \
603 wheel_i_spec, wheel_i_pegs, \
604 wheel_v_spec, wheel_v_pegs, \
605 wheel_iii_spec, wheel_iii_pegs, \
607 'ua pf rq so ni ey bg hl tx zj')
609 >>> enigma31.set_wheels('j', 'e', 'u')
611 >>> enigma31.advance()
612 >>> enigma31.wheel_positions
614 >>> cat(enigma31.wheel_positions_l)
616 >>> enigma31.peg_positions
618 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
619 'mvqjlyowkdieasgzcunxrbhtfp'
621 >>> enigma31.advance()
622 >>> enigma31.wheel_positions
624 >>> cat(enigma31.wheel_positions_l)
626 >>> enigma31.peg_positions
628 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
629 'sjolzuyvrbwdpxcmtiaqfhknge'
631 >>> enigma31.advance()
632 >>> enigma31.wheel_positions
634 >>> cat(enigma31.wheel_positions_l)
636 >>> enigma31.peg_positions
638 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
639 'qrxedkoywufmlvgsabpzjnicht'
641 >>> enigma31.advance()
642 >>> enigma31.wheel_positions
644 >>> cat(enigma31.wheel_positions_l)
646 >>> enigma31.peg_positions
648 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
649 'hpsukliagqefwvtbjxcodnmrzy'
651 >>> enigma31.advance()
652 >>> enigma31.wheel_positions
654 >>> cat(enigma31.wheel_positions_l)
656 >>> enigma31.peg_positions
658 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
659 'zevnbpyqowrtxdifhkulscjmga'
662 >>> enigma31.set_wheels('i', 'd', 'z')
664 >>> enigma31.advance()
665 >>> enigma31.wheel_positions
667 >>> cat(enigma31.wheel_positions_l)
669 >>> enigma31.peg_positions
671 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
672 'ikhpqrvcambzjondefwyxgsutl'
674 >>> enigma31.advance()
675 >>> enigma31.wheel_positions
677 >>> cat(enigma31.wheel_positions_l)
679 >>> enigma31.peg_positions
681 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
682 'cdabskhgzwfmlqvunyexpojtri'
684 >>> enigma31.advance()
685 >>> enigma31.wheel_positions
687 >>> cat(enigma31.wheel_positions_l)
689 >>> enigma31.peg_positions
691 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
692 'pcbwiqhgemyvjsuaftnroldzkx'
694 >>> enigma31.advance()
695 >>> enigma31.wheel_positions
697 >>> cat(enigma31.wheel_positions_l)
699 >>> enigma31.peg_positions
701 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
702 'xcbfvdnouptmlghjzwykierasq'
704 >>> enigma31.advance()
705 >>> enigma31.wheel_positions
707 >>> cat(enigma31.wheel_positions_l)
709 >>> enigma31.peg_positions
711 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
712 'xfvglbdynuseriwqpmkzjcoaht'
714 >>> enigma31.advance()
715 >>> enigma31.wheel_positions
717 >>> cat(enigma31.wheel_positions_l)
719 >>> enigma31.peg_positions
721 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
722 'tfpqlbouynsewjgcdxkahzmriv'
724 >>> enigma31.advance()
725 >>> enigma31.wheel_positions
727 >>> cat(enigma31.wheel_positions_l)
729 >>> enigma31.peg_positions
731 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
732 'cjaunvlwtbygzexrspqidfhokm'
734 >>> enigma31.advance()
735 >>> enigma31.wheel_positions
737 >>> cat(enigma31.wheel_positions_l)
739 >>> enigma31.peg_positions
741 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
742 'yltxkrqvowebzpingfucshjdam'
744 >>> enigma31.advance()
745 >>> enigma31.wheel_positions
747 >>> cat(enigma31.wheel_positions_l)
749 >>> enigma31.peg_positions
751 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
752 'myktluzrnxceaiqsohpdfwvjbg'
754 >>> enigma31.advance()
755 >>> enigma31.wheel_positions
757 >>> cat(enigma31.wheel_positions_l)
759 >>> enigma31.peg_positions
761 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
762 'pynjrmiugdqxfcvakewzhoslbt'
764 >>> enigma31.advance()
765 >>> enigma31.wheel_positions
767 >>> cat(enigma31.wheel_positions_l)
769 >>> enigma31.peg_positions
771 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
772 'mwvedyplnoxhaijgrqtszcbkfu'
774 >>> enigma31.advance()
775 >>> enigma31.wheel_positions
777 >>> cat(enigma31.wheel_positions_l)
779 >>> enigma31.peg_positions
781 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
782 'qcbrfeutvoxpnmjladzhgiykws'
784 >>> enigma31.advance()
785 >>> enigma31.wheel_positions
787 >>> cat(enigma31.wheel_positions_l)
789 >>> enigma31.peg_positions
791 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
792 'dnoahryetsmukbcvwfjilpqzgx'
794 >>> enigma31.advance()
795 >>> enigma31.wheel_positions
797 >>> cat(enigma31.wheel_positions_l)
799 >>> enigma31.peg_positions
801 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
802 'nidcfehgbqsovalyjzkxwmutpr'
804 >>> enigma31.advance()
805 >>> enigma31.wheel_positions
807 >>> cat(enigma31.wheel_positions_l)
809 >>> enigma31.peg_positions
811 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
812 'joifxdulcarhzpbntkwqgysevm'
814 >>> enigma31.advance()
815 >>> enigma31.wheel_positions
817 >>> cat(enigma31.wheel_positions_l)
819 >>> enigma31.peg_positions
821 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
822 'ptnlsxvozmwdjchayuebrgkfqi'
824 >>> enigma31.advance()
825 >>> enigma31.wheel_positions
827 >>> cat(enigma31.wheel_positions_l)
829 >>> enigma31.peg_positions
831 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
832 'slwopzqnmxybihdeguavrtcjkf'
834 >>> enigma31.advance()
835 >>> enigma31.wheel_positions
837 >>> cat(enigma31.wheel_positions_l)
839 >>> enigma31.peg_positions
841 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
842 'hcbedwlamzogixkytsrqvufnpj'
844 >>> enigma31.advance()
845 >>> enigma31.wheel_positions
847 >>> cat(enigma31.wheel_positions_l)
849 >>> enigma31.peg_positions
851 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
852 'odxbjwzrmelkisavuhnyqpfctg'
854 >>> enigma31.advance()
855 >>> enigma31.wheel_positions
857 >>> cat(enigma31.wheel_positions_l)
859 >>> enigma31.peg_positions
861 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
862 'udgbfeclrwnhxksvtioqapjmzy'
864 >>> enigma31.advance()
865 >>> enigma31.wheel_positions
867 >>> cat(enigma31.wheel_positions_l)
869 >>> enigma31.peg_positions
871 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
872 'nrdczqxmowvshaiufblypkjgte'
874 >>> enigma31.advance()
875 >>> enigma31.wheel_positions
877 >>> cat(enigma31.wheel_positions_l)
879 >>> enigma31.peg_positions
881 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
882 'hkifjdoacebqtzgulyvmpsxwrn'
884 >>> enigma31.advance()
885 >>> enigma31.wheel_positions
887 >>> cat(enigma31.wheel_positions_l)
889 >>> enigma31.peg_positions
891 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
892 'yptzuhofqvnmlkgbixwcejsrad'
894 >>> enigma31.advance()
895 >>> enigma31.wheel_positions
897 >>> cat(enigma31.wheel_positions_l)
899 >>> enigma31.peg_positions
901 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
902 'vkdcwhqfjibzsptngumoraeyxl'
904 >>> enigma31.advance()
905 >>> enigma31.wheel_positions
907 >>> cat(enigma31.wheel_positions_l)
909 >>> enigma31.peg_positions
911 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
912 'wenpbqrouxlkychdfgzvitajms'
915 >>> enigma31.set_wheels('i', 'd', 'z')
916 >>> enigma31.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')
917 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz'
918 >>> enigma31.wheel_positions
920 >>> cat(enigma31.wheel_positions_l)
922 >>> enigma31.peg_positions
924 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
925 'urygzpdmxtwshqvfnbljaokice'
927 >>> enigma31.set_wheels('i', 'd', 'z')
928 >>> enigma31.decipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')
929 'verylongtestmessagewithanextrabitofmessageforgoodmeasure'
931 def __init__(self
, reflector_spec
,
932 left_wheel_spec
, left_wheel_pegs
,
933 middle_wheel_spec
, middle_wheel_pegs
,
934 right_wheel_spec
, right_wheel_pegs
,
935 left_ring_setting
, middle_ring_setting
, right_ring_setting
,
937 self
.reflector
= Reflector(reflector_spec
)
938 self
.left_wheel
= Wheel(left_wheel_spec
, left_wheel_pegs
, ring_setting
=left_ring_setting
)
939 self
.middle_wheel
= Wheel(middle_wheel_spec
, middle_wheel_pegs
, ring_setting
=middle_ring_setting
)
940 self
.right_wheel
= Wheel(right_wheel_spec
, right_wheel_pegs
, ring_setting
=right_ring_setting
)
941 self
.plugboard
= Plugboard(plugboard_setting
)
943 def __getattribute__(self
,name
):
944 if name
=='wheel_positions':
945 return self
.left_wheel
.position
, self
.middle_wheel
.position
, self
.right_wheel
.position
946 elif name
=='wheel_positions_l':
947 return self
.left_wheel
.position_l
, self
.middle_wheel
.position_l
, self
.right_wheel
.position_l
948 elif name
=='peg_positions':
949 return self
.left_wheel
.peg_positions
, self
.middle_wheel
.peg_positions
, self
.right_wheel
.peg_positions
951 return object.__getattribute
__(self
, name
)
953 def set_wheels(self
, left_wheel_position
, middle_wheel_position
, right_wheel_position
):
954 self
.left_wheel
.set_position(left_wheel_position
)
955 self
.middle_wheel
.set_position(middle_wheel_position
)
956 self
.right_wheel
.set_position(right_wheel_position
)
958 def lookup(self
, letter
):
959 a
= self
.plugboard
.forward(letter
)
960 b
= self
.right_wheel
.forward(a
)
961 c
= self
.middle_wheel
.forward(b
)
962 d
= self
.left_wheel
.forward(c
)
963 e
= self
.reflector
.forward(d
)
964 f
= self
.left_wheel
.backward(e
)
965 g
= self
.middle_wheel
.backward(f
)
966 h
= self
.right_wheel
.backward(g
)
967 i
= self
.plugboard
.backward(h
)
971 advance_middle
= False
973 if 0 in self
.right_wheel
.peg_positions
:
974 advance_middle
= True
975 if 0 in self
.middle_wheel
.peg_positions
:
977 advance_middle
= True
978 self
.right_wheel
.advance()
979 if advance_middle
: self
.middle_wheel
.advance()
980 if advance_left
: self
.left_wheel
.advance()
982 def encipher_letter(self
, letter
):
984 return self
.lookup(letter
)
986 def encipher(self
, message
):
988 for letter
in clean(message
):
989 enciphered
+= self
.encipher_letter(letter
)
995 # for i in range(26):
997 # print('enigma.advance()')
998 # print("assert(enigma.wheel_positions == {})".format(enigma.wheel_positions))
999 # print("assert(cat(enigma.wheel_positions_l) == '{}')".format(cat(enigma.wheel_positions_l)))
1000 # print("assert(enigma.peg_positions == {})".format(enigma.peg_positions))
1001 # print("assert(cat(enigma.lookup(l) for l in string.ascii_lowercase) == '{}')".format(cat(enigma.lookup(l) for l in string.ascii_lowercase)))
1005 if __name__
== "__main__":
1007 # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})