1150763d35eabc8be0e4fa5cf1c4f5cc3ed26b6f
[cipher-training.git] / enigma.py
1
2 # coding: utf-8
3
4 # # Enigma machine
5 # Specification from [Codes and Ciphers](http://www.codesandciphers.org.uk/enigma/rotorspec.htm) page.
6 #
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).
8 #
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).
10
11
12
13 import string
14 import collections
15
16 # Some convenience functions
17
18 cat = ''.join
19
20 def clean(text): return cat(l.lower() for l in text if l in string.ascii_letters)
21
22 def pos(letter):
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')
27 else:
28 return ''
29
30 def unpos(number): return chr(number % 26 + ord('a'))
31
32
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'
43
44 wheel_i_pegs = ['q']
45 wheel_ii_pegs = ['e']
46 wheel_iii_pegs = ['v']
47 wheel_iv_pegs = ['j']
48 wheel_v_pegs = ['z']
49 wheel_vi_pegs = ['z', 'm']
50 wheel_vii_pegs = ['z', 'm']
51 wheel_viii_pegs = ['z', 'm']
52
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'
55
56
57
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
61 letters by provided.
62
63 >>> lt = LetterTransformer([('z', 'a')] + [(l, string.ascii_lowercase[i+1]) \
64 for i, l in enumerate(string.ascii_lowercase[:-1])], \
65 raw_transform = True)
66 >>> lt.forward_map
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]
68 >>> lt.backward_map
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]
70
71 >>> lt = LetterTransformer(cat(collections.OrderedDict.fromkeys('zyxwc' + string.ascii_lowercase)))
72 >>> lt.forward_map
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]
74 >>> lt.backward_map
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'
80 """
81 def __init__(self, specification, raw_transform=False):
82 if raw_transform:
83 transform = specification
84 else:
85 transform = self.parse_specification(specification)
86 self.validate_transform(transform)
87 self.make_transform_map(transform)
88
89 def parse_specification(self, specification):
90 return list(zip(string.ascii_lowercase, clean(specification)))
91 # return specification
92
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)))
98 for p in transform:
99 if len(p) != 2:
100 raise ValueError("Not all mappings in transform "
101 "have two elements")
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")
106
107 def make_empty_transform(self):
108 self.forward_map = [0] * 26
109 self.backward_map = [0] * 26
110
111 def make_transform_map(self, transform):
112 self.make_empty_transform()
113 for p in 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
117
118 def forward(self, letter):
119 if letter in string.ascii_lowercase:
120 return unpos(self.forward_map[pos(letter)])
121 else:
122 return ''
123
124 def backward(self, letter):
125 if letter in string.ascii_lowercase:
126 return unpos(self.backward_map[pos(letter)])
127 else:
128 return ''
129
130
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
134 kept as it is.
135
136 >>> pb = Plugboard('ua pf rq so ni ey bg hl tx zj'.upper())
137 >>> pb.forward_map
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
140 True
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'
145 """
146 def parse_specification(self, specification):
147 return [tuple(clean(p)) for p in specification.split()]
148
149 def validate_transform(self, transform):
150 """A set of pairs, of from-to"""
151 for p in transform:
152 if len(p) != 2:
153 raise ValueError("Not all mappings in transform"
154 "have two elements")
155
156 def make_empty_transform(self):
157 self.forward_map = list(range(26))
158 self.backward_map = list(range(26))
159
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)
163
164
165
166
167 class Reflector(Plugboard):
168 """A reflector is a plugboard that requires 13 transforms.
169
170 >>> reflector_b = Reflector(reflector_b_spec)
171 >>> reflector_b.forward_map == reflector_b.backward_map
172 True
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'
179 """
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")
187 try:
188 super(Reflector, self).validate_transform(transform)
189 except ValueError as v:
190 raise ValueError("Not all mappings in reflector have two elements")
191
192
193
194
195 class SimpleWheel(LetterTransformer):
196 """A wheel is a transform that rotates.
197
198 Looking from the right, letters go in sequence a-b-c clockwise around the
199 wheel.
200
201 The position of the wheel is the number of spaces anticlockwise the wheel
202 has turned.
203
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.
207
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'
214
215
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'
221
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'
229 >>> wheel_3.position
230 1
231 >>> wheel_3.position_l
232 'b'
233
234 >>> for _ in range(24): wheel_3.advance()
235 >>> wheel_3.position
236 25
237 >>> wheel_3.position_l
238 'z'
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'
243
244 >>> wheel_3.advance()
245 >>> wheel_3.position
246 0
247 >>> wheel_3.position_l
248 'a'
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'
253 """
254 def __init__(self, transform, position='a', raw_transform=False):
255 super(SimpleWheel, self).__init__(transform, raw_transform)
256 self.set_position(position)
257
258 def __getattribute__(self,name):
259 if name=='position_l':
260 return unpos(self.position)
261 else:
262 return object.__getattribute__(self, name)
263
264 def set_position(self, position):
265 self.position = ord(position) - ord('a')
266
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))
270 else:
271 return ''
272
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))
276 else:
277 return ''
278
279 def advance(self):
280 self.position = (self.position + 1) % 26
281 # return self.position
282
283
284
285 class Wheel(SimpleWheel):
286 """A wheel with a movable ring.
287
288 The ring holds the letters and the pegs that turn other wheels. The core
289 holds the wiring that does the transformation.
290
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
293 aligned.
294
295 The position of the wheel is the position of the core (the transforms)
296 relative to the neutral position.
297
298 The position_l is the position of the ring, or what would be observed
299 by the user of the Enigma machine.
300
301 The peg_positions are the number of advances of this wheel before it will
302 advance the next wheel.
303
304 >>> wheel_3 = Wheel(wheel_iii_spec, wheel_iii_pegs, position='b', ring_setting=1)
305 >>> wheel_3.position
306 1
307 >>> wheel_3.peg_positions
308 [20]
309 >>> wheel_3.position_l
310 'b'
311 >>> wheel_3.advance()
312 >>> wheel_3.position
313 2
314 >>> wheel_3.peg_positions
315 [19]
316 >>> wheel_3.position_l
317 'c'
318
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'
324 >>> wheel_6.position
325 25
326 >>> 11 in wheel_6.peg_positions
327 True
328 >>> 24 in wheel_6.peg_positions
329 True
330 >>> wheel_6.position_l
331 'b'
332
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'
338 >>> wheel_6.position
339 0
340 >>> 10 in wheel_6.peg_positions
341 True
342 >>> 23 in wheel_6.peg_positions
343 True
344 >>> wheel_6.position_l
345 'c'
346
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'
352 >>> wheel_6.position
353 22
354 >>> 1 in wheel_6.peg_positions
355 True
356 >>> 14 in wheel_6.peg_positions
357 True
358 >>> wheel_6.position_l
359 'y'
360
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'
366 >>> wheel_6.position
367 23
368 >>> 0 in wheel_6.peg_positions
369 True
370 >>> 13 in wheel_6.peg_positions
371 True
372 >>> wheel_6.position_l
373 'z'
374
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'
380 >>> wheel_6.position
381 24
382 >>> 25 in wheel_6.peg_positions
383 True
384 >>> 12 in wheel_6.peg_positions
385 True
386 >>> wheel_6.position_l
387 'a'
388
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'
394 >>> wheel_6.position
395 25
396 >>> 24 in wheel_6.peg_positions
397 True
398 >>> 11 in wheel_6.peg_positions
399 True
400 >>> wheel_6.position_l
401 'b'
402
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'
408 >>> wheel_6.position
409 0
410 >>> 23 in wheel_6.peg_positions
411 True
412 >>> 10 in wheel_6.peg_positions
413 True
414 >>> wheel_6.position_l
415 'c'
416
417 """
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)
423
424 def __getattribute__(self,name):
425 if name=='position_l':
426 return unpos(self.position + self.ring_setting - 1)
427 else:
428 return object.__getattribute__(self, name)
429
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]
434
435 def advance(self):
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
440
441
442
443
444 class Enigma(object):
445 """An Enigma machine.
446
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, \
451 1, 1, 1, \
452 '')
453 >>> enigma.set_wheels('a', 'a', 't')
454 >>> enigma.wheel_positions
455 (0, 0, 19)
456 >>> cat(enigma.wheel_positions_l)
457 'aat'
458 >>> enigma.peg_positions
459 ([16], [4], [2])
460 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
461 'puvioztjdhxmlyeawsrgbcqknf'
462
463 >>> enigma.advance()
464 >>> enigma.wheel_positions
465 (0, 0, 20)
466 >>> cat(enigma.wheel_positions_l)
467 'aau'
468 >>> enigma.peg_positions
469 ([16], [4], [1])
470 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
471 'baigpldqcowfyzjehvtsxrkumn'
472
473 >>> enigma.advance()
474 >>> enigma.wheel_positions
475 (0, 0, 21)
476 >>> cat(enigma.wheel_positions_l)
477 'aav'
478 >>> enigma.peg_positions
479 ([16], [4], [0])
480 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
481 'mnvfydiwgzsoablrxpkutchqej'
482
483 >>> enigma.advance()
484 >>> enigma.wheel_positions
485 (0, 1, 22)
486 >>> cat(enigma.wheel_positions_l)
487 'abw'
488 >>> enigma.peg_positions
489 ([16], [3], [25])
490 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
491 'ulfopcykswhbzvderqixanjtgm'
492
493 >>> enigma.advance()
494 >>> enigma.wheel_positions
495 (0, 1, 23)
496 >>> cat(enigma.wheel_positions_l)
497 'abx'
498 >>> enigma.peg_positions
499 ([16], [3], [24])
500 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
501 'qmwftdyovursbzhxaklejicpgn'
502
503 >>> enigma.advance()
504 >>> enigma.wheel_positions
505 (0, 1, 24)
506 >>> cat(enigma.wheel_positions_l)
507 'aby'
508 >>> enigma.peg_positions
509 ([16], [3], [23])
510 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
511 'oljmzxrvucybdqasngpwihtfke'
512
513
514
515
516 >>> enigma.set_wheels('a', 'd', 't')
517 >>> enigma.wheel_positions
518 (0, 3, 19)
519 >>> cat(enigma.wheel_positions_l)
520 'adt'
521 >>> enigma.peg_positions
522 ([16], [1], [2])
523 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
524 'zcbpqxwsjiuonmldethrkygfva'
525
526 >>> enigma.advance()
527 >>> enigma.wheel_positions
528 (0, 3, 20)
529 >>> cat(enigma.wheel_positions_l)
530 'adu'
531 >>> enigma.peg_positions
532 ([16], [1], [1])
533 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
534 'ehprawjbngotxikcsdqlzyfmvu'
535
536 >>> enigma.advance()
537 >>> enigma.wheel_positions
538 (0, 3, 21)
539 >>> cat(enigma.wheel_positions_l)
540 'adv'
541 >>> enigma.peg_positions
542 ([16], [1], [0])
543 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
544 'eqzxarpihmnvjkwgbfuyslodtc'
545
546 >>> enigma.advance()
547 >>> enigma.wheel_positions
548 (0, 4, 22)
549 >>> cat(enigma.wheel_positions_l)
550 'aew'
551 >>> enigma.peg_positions
552 ([16], [0], [25])
553 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
554 'qedcbtpluzmhkongavwfirsyxj'
555
556 >>> enigma.advance()
557 >>> enigma.wheel_positions
558 (1, 5, 23)
559 >>> cat(enigma.wheel_positions_l)
560 'bfx'
561 >>> enigma.peg_positions
562 ([15], [25], [24])
563 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
564 'iwuedhsfazqxytvrkpgncoblmj'
565
566 >>> enigma.advance()
567 >>> enigma.wheel_positions
568 (1, 5, 24)
569 >>> cat(enigma.wheel_positions_l)
570 'bfy'
571 >>> enigma.peg_positions
572 ([15], [25], [23])
573 >>> cat(enigma.lookup(l) for l in string.ascii_lowercase)
574 'baknstqzrmcxjdvygiefwoulph'
575
576
577 >>> enigma.set_wheels('a', 'a', 'a')
578 >>> ct = enigma.encipher('testmessage')
579 >>> ct
580 'olpfhnvflyn'
581
582 >>> enigma.set_wheels('a', 'd', 't')
583 >>> ct = enigma.encipher('testmessage')
584 >>> ct
585 'lawnjgpwjik'
586
587
588 >>> enigma.set_wheels('b', 'd', 'q')
589 >>> ct = enigma.encipher('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
590 >>> ct
591 'kvmmwrlqlqsqpeugjrcxzwpfyiyybwloewrouvkpoztceuwtfjzqwpbqldttsr'
592 >>> enigma.left_wheel.position_l
593 'c'
594 >>> enigma.middle_wheel.position_l
595 'h'
596 >>> enigma.right_wheel.position_l
597 'a'
598
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, \
606 6, 20, 24, \
607 'ua pf rq so ni ey bg hl tx zj')
608
609 >>> enigma31.set_wheels('j', 'e', 'u')
610
611 >>> enigma31.advance()
612 >>> enigma31.wheel_positions
613 (4, 11, 24)
614 >>> cat(enigma31.wheel_positions_l)
615 'jev'
616 >>> enigma31.peg_positions
617 ([7], [21], [0])
618 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
619 'mvqjlyowkdieasgzcunxrbhtfp'
620
621 >>> enigma31.advance()
622 >>> enigma31.wheel_positions
623 (4, 12, 25)
624 >>> cat(enigma31.wheel_positions_l)
625 'jfw'
626 >>> enigma31.peg_positions
627 ([7], [20], [25])
628 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
629 'sjolzuyvrbwdpxcmtiaqfhknge'
630
631 >>> enigma31.advance()
632 >>> enigma31.wheel_positions
633 (4, 12, 0)
634 >>> cat(enigma31.wheel_positions_l)
635 'jfx'
636 >>> enigma31.peg_positions
637 ([7], [20], [24])
638 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
639 'qrxedkoywufmlvgsabpzjnicht'
640
641 >>> enigma31.advance()
642 >>> enigma31.wheel_positions
643 (4, 12, 1)
644 >>> cat(enigma31.wheel_positions_l)
645 'jfy'
646 >>> enigma31.peg_positions
647 ([7], [20], [23])
648 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
649 'hpsukliagqefwvtbjxcodnmrzy'
650
651 >>> enigma31.advance()
652 >>> enigma31.wheel_positions
653 (4, 12, 2)
654 >>> cat(enigma31.wheel_positions_l)
655 'jfz'
656 >>> enigma31.peg_positions
657 ([7], [20], [22])
658 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
659 'zevnbpyqowrtxdifhkulscjmga'
660
661
662 >>> enigma31.set_wheels('i', 'd', 'z')
663
664 >>> enigma31.advance()
665 >>> enigma31.wheel_positions
666 (3, 10, 3)
667 >>> cat(enigma31.wheel_positions_l)
668 'ida'
669 >>> enigma31.peg_positions
670 ([8], [22], [21])
671 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
672 'ikhpqrvcambzjondefwyxgsutl'
673
674 >>> enigma31.advance()
675 >>> enigma31.wheel_positions
676 (3, 10, 4)
677 >>> cat(enigma31.wheel_positions_l)
678 'idb'
679 >>> enigma31.peg_positions
680 ([8], [22], [20])
681 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
682 'cdabskhgzwfmlqvunyexpojtri'
683
684 >>> enigma31.advance()
685 >>> enigma31.wheel_positions
686 (3, 10, 5)
687 >>> cat(enigma31.wheel_positions_l)
688 'idc'
689 >>> enigma31.peg_positions
690 ([8], [22], [19])
691 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
692 'pcbwiqhgemyvjsuaftnroldzkx'
693
694 >>> enigma31.advance()
695 >>> enigma31.wheel_positions
696 (3, 10, 6)
697 >>> cat(enigma31.wheel_positions_l)
698 'idd'
699 >>> enigma31.peg_positions
700 ([8], [22], [18])
701 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
702 'xcbfvdnouptmlghjzwykierasq'
703
704 >>> enigma31.advance()
705 >>> enigma31.wheel_positions
706 (3, 10, 7)
707 >>> cat(enigma31.wheel_positions_l)
708 'ide'
709 >>> enigma31.peg_positions
710 ([8], [22], [17])
711 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
712 'xfvglbdynuseriwqpmkzjcoaht'
713
714 >>> enigma31.advance()
715 >>> enigma31.wheel_positions
716 (3, 10, 8)
717 >>> cat(enigma31.wheel_positions_l)
718 'idf'
719 >>> enigma31.peg_positions
720 ([8], [22], [16])
721 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
722 'tfpqlbouynsewjgcdxkahzmriv'
723
724 >>> enigma31.advance()
725 >>> enigma31.wheel_positions
726 (3, 10, 9)
727 >>> cat(enigma31.wheel_positions_l)
728 'idg'
729 >>> enigma31.peg_positions
730 ([8], [22], [15])
731 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
732 'cjaunvlwtbygzexrspqidfhokm'
733
734 >>> enigma31.advance()
735 >>> enigma31.wheel_positions
736 (3, 10, 10)
737 >>> cat(enigma31.wheel_positions_l)
738 'idh'
739 >>> enigma31.peg_positions
740 ([8], [22], [14])
741 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
742 'yltxkrqvowebzpingfucshjdam'
743
744 >>> enigma31.advance()
745 >>> enigma31.wheel_positions
746 (3, 10, 11)
747 >>> cat(enigma31.wheel_positions_l)
748 'idi'
749 >>> enigma31.peg_positions
750 ([8], [22], [13])
751 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
752 'myktluzrnxceaiqsohpdfwvjbg'
753
754 >>> enigma31.advance()
755 >>> enigma31.wheel_positions
756 (3, 10, 12)
757 >>> cat(enigma31.wheel_positions_l)
758 'idj'
759 >>> enigma31.peg_positions
760 ([8], [22], [12])
761 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
762 'pynjrmiugdqxfcvakewzhoslbt'
763
764 >>> enigma31.advance()
765 >>> enigma31.wheel_positions
766 (3, 10, 13)
767 >>> cat(enigma31.wheel_positions_l)
768 'idk'
769 >>> enigma31.peg_positions
770 ([8], [22], [11])
771 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
772 'mwvedyplnoxhaijgrqtszcbkfu'
773
774 >>> enigma31.advance()
775 >>> enigma31.wheel_positions
776 (3, 10, 14)
777 >>> cat(enigma31.wheel_positions_l)
778 'idl'
779 >>> enigma31.peg_positions
780 ([8], [22], [10])
781 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
782 'qcbrfeutvoxpnmjladzhgiykws'
783
784 >>> enigma31.advance()
785 >>> enigma31.wheel_positions
786 (3, 10, 15)
787 >>> cat(enigma31.wheel_positions_l)
788 'idm'
789 >>> enigma31.peg_positions
790 ([8], [22], [9])
791 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
792 'dnoahryetsmukbcvwfjilpqzgx'
793
794 >>> enigma31.advance()
795 >>> enigma31.wheel_positions
796 (3, 10, 16)
797 >>> cat(enigma31.wheel_positions_l)
798 'idn'
799 >>> enigma31.peg_positions
800 ([8], [22], [8])
801 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
802 'nidcfehgbqsovalyjzkxwmutpr'
803
804 >>> enigma31.advance()
805 >>> enigma31.wheel_positions
806 (3, 10, 17)
807 >>> cat(enigma31.wheel_positions_l)
808 'ido'
809 >>> enigma31.peg_positions
810 ([8], [22], [7])
811 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
812 'joifxdulcarhzpbntkwqgysevm'
813
814 >>> enigma31.advance()
815 >>> enigma31.wheel_positions
816 (3, 10, 18)
817 >>> cat(enigma31.wheel_positions_l)
818 'idp'
819 >>> enigma31.peg_positions
820 ([8], [22], [6])
821 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
822 'ptnlsxvozmwdjchayuebrgkfqi'
823
824 >>> enigma31.advance()
825 >>> enigma31.wheel_positions
826 (3, 10, 19)
827 >>> cat(enigma31.wheel_positions_l)
828 'idq'
829 >>> enigma31.peg_positions
830 ([8], [22], [5])
831 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
832 'slwopzqnmxybihdeguavrtcjkf'
833
834 >>> enigma31.advance()
835 >>> enigma31.wheel_positions
836 (3, 10, 20)
837 >>> cat(enigma31.wheel_positions_l)
838 'idr'
839 >>> enigma31.peg_positions
840 ([8], [22], [4])
841 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
842 'hcbedwlamzogixkytsrqvufnpj'
843
844 >>> enigma31.advance()
845 >>> enigma31.wheel_positions
846 (3, 10, 21)
847 >>> cat(enigma31.wheel_positions_l)
848 'ids'
849 >>> enigma31.peg_positions
850 ([8], [22], [3])
851 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
852 'odxbjwzrmelkisavuhnyqpfctg'
853
854 >>> enigma31.advance()
855 >>> enigma31.wheel_positions
856 (3, 10, 22)
857 >>> cat(enigma31.wheel_positions_l)
858 'idt'
859 >>> enigma31.peg_positions
860 ([8], [22], [2])
861 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
862 'udgbfeclrwnhxksvtioqapjmzy'
863
864 >>> enigma31.advance()
865 >>> enigma31.wheel_positions
866 (3, 10, 23)
867 >>> cat(enigma31.wheel_positions_l)
868 'idu'
869 >>> enigma31.peg_positions
870 ([8], [22], [1])
871 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
872 'nrdczqxmowvshaiufblypkjgte'
873
874 >>> enigma31.advance()
875 >>> enigma31.wheel_positions
876 (3, 10, 24)
877 >>> cat(enigma31.wheel_positions_l)
878 'idv'
879 >>> enigma31.peg_positions
880 ([8], [22], [0])
881 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
882 'hkifjdoacebqtzgulyvmpsxwrn'
883
884 >>> enigma31.advance()
885 >>> enigma31.wheel_positions
886 (3, 11, 25)
887 >>> cat(enigma31.wheel_positions_l)
888 'iew'
889 >>> enigma31.peg_positions
890 ([8], [21], [25])
891 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
892 'yptzuhofqvnmlkgbixwcejsrad'
893
894 >>> enigma31.advance()
895 >>> enigma31.wheel_positions
896 (3, 11, 0)
897 >>> cat(enigma31.wheel_positions_l)
898 'iex'
899 >>> enigma31.peg_positions
900 ([8], [21], [24])
901 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
902 'vkdcwhqfjibzsptngumoraeyxl'
903
904 >>> enigma31.advance()
905 >>> enigma31.wheel_positions
906 (3, 11, 1)
907 >>> cat(enigma31.wheel_positions_l)
908 'iey'
909 >>> enigma31.peg_positions
910 ([8], [21], [23])
911 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
912 'wenpbqrouxlkychdfgzvitajms'
913
914
915 >>> enigma31.set_wheels('i', 'd', 'z')
916 >>> enigma31.encipher('verylongtestmessagewithanextrabitofmessageforgoodmeasure')
917 'gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz'
918 >>> enigma31.wheel_positions
919 (3, 12, 6)
920 >>> cat(enigma31.wheel_positions_l)
921 'ifd'
922 >>> enigma31.peg_positions
923 ([8], [20], [18])
924 >>> cat(enigma31.lookup(l) for l in string.ascii_lowercase)
925 'urygzpdmxtwshqvfnbljaokice'
926
927 >>> enigma31.set_wheels('i', 'd', 'z')
928 >>> enigma31.decipher('gstsegeqdrthkfwesljjomfvcqwcfspxpfqqmewvddybarzwubxtpejz')
929 'verylongtestmessagewithanextrabitofmessageforgoodmeasure'
930 """
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,
936 plugboard_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)
942
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
950 else:
951 return object.__getattribute__(self, name)
952
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)
957
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)
968 return i
969
970 def advance(self):
971 advance_middle = False
972 advance_left = False
973 if 0 in self.right_wheel.peg_positions:
974 advance_middle = True
975 if 0 in self.middle_wheel.peg_positions:
976 advance_left = True
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()
981
982 def encipher_letter(self, letter):
983 self.advance()
984 return self.lookup(letter)
985
986 def encipher(self, message):
987 enciphered = ''
988 for letter in clean(message):
989 enciphered += self.encipher_letter(letter)
990 return enciphered
991
992 decipher = encipher
993
994
995 # for i in range(26):
996 # enigma.advance()
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)))
1002 # print()
1003
1004
1005 if __name__ == "__main__":
1006 import doctest
1007 # doctest.testmod(extraglobs={'lt': LetterTransformer(1, 'a')})
1008 doctest.testmod()
1009