1 // Written in the D programming language. 2 3 /** 4 * A library for simulating the Enigma machines. 5 * 6 * Copyright: Copyright Kazuya Takahashi 2016. 7 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 * Authors: Kazuya Takahashi 9 */ 10 module enigma; 11 12 private enum size_t N = 26; 13 14 private template isSomeStringOrDcharRange(T) 15 { 16 import std.range.primitives : isInfinite, isInputRange, ElementType; 17 import std.traits : isSomeString; 18 19 enum isSomeStringOrDcharRange = isSomeString!T || 20 (isInputRange!T && !isInfinite!T && is(ElementType!T : dchar)); 21 } 22 23 24 /// 25 struct Rotor 26 { 27 import boolean_matrix : BSM; 28 29 immutable BSM!N perm; 30 private immutable bool hasNotch = false; 31 private immutable size_t[] turnovers; 32 33 /++ 34 + Constructs a rotor with no turnover notches. 35 + If ringOffset is `2`, it corresponds to "C-03". 36 +/ 37 this()(in auto ref BSM!N perm, size_t ringOffset) pure 38 in 39 { 40 import boolean_matrix : isBijective; 41 42 assert(perm.isBijective, "Rotor must be bijective."); 43 } 44 body 45 { 46 import boolean_matrix : lowerRotator, upperRotator; 47 48 this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset); 49 } 50 51 /++ 52 + Constructs a rotor with one turnover notch. 53 + If turnover is `1`, the next rotor steps when this rotor steps from B to C. 54 + If ringOffset is `2`, it corresponds to "C-03". 55 +/ 56 this()(in auto ref BSM!N perm, size_t turnover, size_t ringOffset) pure 57 { 58 this(perm, ringOffset); 59 this.turnovers = [turnover % N]; 60 hasNotch = true; 61 } 62 63 /++ 64 + Constructs a rotor with two turnover notches. 65 + If turnover1 is `1` and turnover2 is `25`, the next rotor steps 66 + when this rotor steps from B to C and from Z to A. 67 + If ringOffset is `2`, it corresponds to "C-03". 68 +/ 69 this()(in auto ref BSM!N perm, size_t turnover1, size_t turnover2, size_t ringOffset) pure 70 { 71 import std.algorithm.sorting : sort; 72 import std.array : array; 73 74 this(perm, ringOffset); 75 this.turnovers = [turnover1 % N, turnover2 % N].sort().array; 76 hasNotch = true; 77 } 78 79 /++ 80 + Constructs a rotor with five turnover notches. 81 + If turnover1 is `1` and turnover2 is `25`, the next rotor steps 82 + when this rotor steps from B to C and from Z to A. 83 + If ringOffset is `2`, it corresponds to "C-03". 84 +/ 85 this()(in auto ref BSM!N perm, size_t turnover1, size_t turnover2, size_t turnover3, size_t turnover4, size_t turnover5, size_t ringOffset) pure 86 { 87 import std.algorithm.sorting : sort; 88 import std.array : array; 89 90 this(perm, ringOffset); 91 this.turnovers = [turnover1 % N, turnover2 % N, turnover3 % N, turnover4 % N, turnover5 % N].sort().array; 92 hasNotch = true; 93 } 94 } 95 96 /++ 97 + A convenience function to make a rotor with no turnover notches 98 + from a forward substitution. 99 + If ringSetting is `'C'`, it corresponds to "C-03". 100 +/ 101 auto rotor(S)(in S forwardSubstitution, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 102 in 103 { 104 import std.algorithm.comparison : isPermutation; 105 import std.algorithm.iteration : map; 106 import std.ascii : isAlpha, toUpper; 107 import std.range : iota, walkLength; 108 109 assert(forwardSubstitution.walkLength == N, "Bad length."); 110 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 111 assert(ringSetting.isAlpha, "Bad ring setting."); 112 } 113 body 114 { 115 import std.algorithm.iteration : map; 116 import std.array : array; 117 import std.ascii : toUpper; 118 import boolean_matrix : permutation; 119 120 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 121 ringSetting.toUpper - 'A'); 122 } 123 124 /++ 125 + A convenience function to make a rotor with one turnover notch 126 + from a forward substitution. 127 + If turnover is `'B'`, the next rotor steps when this rotor steps from B to C. 128 + If ringSetting is `'C'`, it corresponds to "C-03". 129 +/ 130 auto rotor(S)(in S forwardSubstitution, dchar turnover, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 131 in 132 { 133 import std.algorithm.comparison : isPermutation; 134 import std.algorithm.iteration : map; 135 import std.ascii : isAlpha, toUpper; 136 import std.range : iota, walkLength; 137 138 assert(forwardSubstitution.walkLength == N, "Bad length."); 139 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 140 assert(turnover.isAlpha, "Bad turnover setting."); 141 assert(ringSetting.isAlpha, "Bad ring setting."); 142 } 143 body 144 { 145 import std.algorithm.iteration : map; 146 import std.array : array; 147 import std.ascii : toUpper; 148 import boolean_matrix : permutation; 149 150 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 151 turnover.toUpper - 'A', ringSetting.toUpper - 'A'); 152 } 153 154 /++ 155 + A convenience function to make a rotor with two turnover notches 156 + from a forward substitution. 157 + If turnover1 is `'B'` and turnover2 is `'Z'`, the next rotor steps 158 + when this rotor steps from B to C and from Z to A. 159 + If ringSetting is `'C'`, it corresponds to "C-03". 160 +/ 161 auto rotor(S)(in S forwardSubstitution, dchar turnover1, dchar turnover2, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 162 in 163 { 164 import std.algorithm.comparison : isPermutation; 165 import std.algorithm.iteration : map; 166 import std.ascii : isAlpha, toUpper; 167 import std.range : iota, walkLength; 168 169 assert(forwardSubstitution.walkLength == N, "Bad length."); 170 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 171 assert(turnover1.isAlpha, "Bad turnover setting."); 172 assert(turnover2.isAlpha, "Bad turnover setting."); 173 assert(ringSetting.isAlpha, "Bad ring setting."); 174 } 175 body 176 { 177 import std.algorithm.iteration : map; 178 import std.array : array; 179 import std.ascii : toUpper; 180 import boolean_matrix : permutation; 181 182 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 183 turnover1.toUpper - 'A', turnover2.toUpper - 'A', ringSetting.toUpper - 'A'); 184 } 185 186 /++ 187 + A convenience function to make a rotor with five turnover notches 188 + from a forward substitution. 189 + If turnover1 is `'B'` and turnover2 is `'Z'`, the next rotor steps 190 + when this rotor steps from B to C and from Z to A. 191 + If ringSetting is `'C'`, it corresponds to "C-03". 192 +/ 193 auto rotor(S)(in S forwardSubstitution, dchar turnover1, dchar turnover2, dchar turnover3, dchar turnover4, dchar turnover5, dchar ringSetting) pure if (isSomeStringOrDcharRange!S) 194 in 195 { 196 import std.algorithm.comparison : isPermutation; 197 import std.algorithm.iteration : map; 198 import std.ascii : isAlpha, toUpper; 199 import std.range : iota, walkLength; 200 201 assert(forwardSubstitution.walkLength == N, "Bad length."); 202 assert(N.iota.isPermutation(forwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 203 assert(turnover1.isAlpha, "Bad turnover setting."); 204 assert(turnover2.isAlpha, "Bad turnover setting."); 205 assert(turnover3.isAlpha, "Bad turnover setting."); 206 assert(turnover4.isAlpha, "Bad turnover setting."); 207 assert(turnover5.isAlpha, "Bad turnover setting."); 208 assert(ringSetting.isAlpha, "Bad ring setting."); 209 } 210 body 211 { 212 import std.algorithm.iteration : map; 213 import std.array : array; 214 import std.ascii : toUpper; 215 import boolean_matrix : permutation; 216 217 return Rotor(forwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N, 218 turnover1.toUpper - 'A', turnover2.toUpper - 'A', turnover3.toUpper - 'A', 219 turnover4.toUpper - 'A', turnover5.toUpper - 'A', ringSetting.toUpper - 'A'); 220 } 221 222 /// 223 struct EntryWheel 224 { 225 import boolean_matrix : BSM; 226 227 immutable BSM!N perm; 228 /// 229 this()(in auto ref BSM!N perm) pure 230 in 231 { 232 import boolean_matrix : isBijective; 233 234 assert(perm.isBijective, "Entry wheel must be bijective."); 235 } 236 body 237 { 238 this.perm = cast(immutable) perm; 239 } 240 241 alias perm this; 242 } 243 244 /++ 245 + A convenience function to make an entry wheel from a substitution. 246 +/ 247 auto entryWheel(S)(in S backwardSubstitution) pure if (isSomeStringOrDcharRange!S) 248 in 249 { 250 import std.algorithm.comparison : isPermutation; 251 import std.algorithm.iteration : map; 252 import std.ascii : toUpper; 253 import std.range : iota, walkLength; 254 255 assert(backwardSubstitution.walkLength == N, "Bad length."); 256 assert(N.iota.isPermutation(backwardSubstitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 257 } 258 body 259 { 260 import std.algorithm.iteration : map; 261 import std.array : array; 262 import std.ascii : toUpper; 263 import boolean_matrix : permutation, transpose; 264 265 return EntryWheel(backwardSubstitution.map!toUpper.map!"a-'A'".array.permutation!N.transpose); 266 } 267 268 /// 269 struct Plugboard 270 { 271 import boolean_matrix : BSM; 272 273 immutable BSM!N perm; 274 /// 275 this()(in auto ref BSM!N perm) pure 276 in 277 { 278 import boolean_matrix : isBijective, isSymmetric; 279 280 assert(perm.isBijective, "Plugboard must be bijective."); 281 assert(perm.isSymmetric, "Plugboard must be symmetric."); 282 } 283 body 284 { 285 this.perm = cast(immutable) perm; 286 } 287 288 alias perm this; 289 } 290 291 /++ 292 + A convenience function to make a plugboard from a substitution. 293 +/ 294 auto plugboard(S)(in S substitution) pure if (isSomeStringOrDcharRange!S) 295 in 296 { 297 import std.algorithm.comparison : isPermutation; 298 import std.algorithm.iteration : map; 299 import std.ascii : toUpper; 300 import std.range : iota, walkLength; 301 302 assert(substitution.walkLength == N, "Bad length."); 303 assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 304 } 305 body 306 { 307 import std.algorithm.iteration : map; 308 import std.array : array; 309 import std.ascii : toUpper; 310 import boolean_matrix : permutation; 311 312 return Plugboard(substitution.map!toUpper.map!"a-'A'".array.permutation!N); 313 } 314 315 /// 316 struct Reflector 317 { 318 import boolean_matrix : BSM; 319 320 immutable BSM!N perm; 321 /// 322 this()(in auto ref BSM!N perm, size_t ringOffset) pure 323 in 324 { 325 import boolean_matrix : isBijective, isIrreflexive, isSymmetric; 326 327 assert(perm.isBijective, "Reflector must be bijective."); 328 assert(perm.isIrreflexive, "Reflector must be irreflexive."); 329 assert(perm.isSymmetric, "Reflector must be bijective."); 330 } 331 body 332 { 333 import boolean_matrix : lowerRotator, upperRotator; 334 335 this.perm = lowerRotator!N(ringOffset) * perm * upperRotator!N(ringOffset); 336 } 337 338 alias perm this; 339 } 340 341 /++ 342 + A convenience function to make a reflector from a substitution. 343 +/ 344 auto reflector(S)(in S substitution, dchar ringSetting = 'A') pure if (isSomeStringOrDcharRange!S) 345 in 346 { 347 import std.algorithm.comparison : isPermutation; 348 import std.algorithm.iteration : map; 349 import std.algorithm.searching : canFind; 350 import std.ascii : isAlpha, toUpper; 351 import std.range : iota, walkLength, zip; 352 353 assert(substitution.walkLength == N, "Bad length."); 354 assert(!N.iota.zip(substitution.map!toUpper.map!"a-'A'").canFind!"a[0]==a[1]", "Self-loop found."); 355 assert(N.iota.isPermutation(substitution.map!toUpper.map!"a-'A'"), "Bad permutation."); 356 assert(ringSetting.isAlpha, "Bad ring setting."); 357 } 358 body 359 { 360 import std.algorithm.iteration : map; 361 import std.array : array; 362 import std.ascii : toUpper; 363 import boolean_matrix : permutation; 364 365 return Reflector(substitution.map!toUpper.map!"a-'A'".array.permutation!N, 366 ringSetting.toUpper - 'A'); 367 } 368 369 /// Currently machines with the double-stepping mechanism are available. 370 struct Enigma(size_t rotorN, bool fixedFinalRotor = false, bool hasPlugboard = true, bool settableReflectorPos = false) 371 { 372 import boolean_matrix : BSM; 373 private immutable BSM!N composedInputPerm; 374 private immutable Rotor[rotorN] rotors; 375 private immutable BSM!N reflector; 376 private size_t[rotorN] rotationStates; 377 378 import meta_workaround : Repeat; 379 380 /// 381 this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors, 382 in Reflector reflector, in dchar[rotorN] rotorStartPos) 383 in 384 { 385 foreach (dchar c; rotorStartPos) 386 { 387 import std.ascii : isAlpha; 388 389 assert(c.isAlpha, "Bad start position."); 390 } 391 } 392 body 393 { 394 foreach (i, ref e; rotationStates) 395 { 396 import std.ascii : toUpper; 397 398 e = rotorStartPos[i].toUpper - 'A'; 399 } 400 401 this.composedInputPerm = cast(immutable) entryWheel.perm; 402 this.rotors[] = cast(immutable)[rotors][]; 403 this.reflector = cast(immutable) reflector.perm; 404 } 405 406 /// 407 static if (settableReflectorPos) 408 { 409 this(in EntryWheel entryWheel, in Repeat!(rotorN, Rotor) rotors, 410 in Reflector reflector, in dchar[rotorN] rotorStartPos, 411 dchar reflectorPos) 412 in 413 { 414 import std.ascii : isAlpha; 415 416 assert(reflectorPos.isAlpha, "Bad reflector position."); 417 } 418 body 419 { 420 this(entryWheel, rotors, reflector, rotorStartPos); 421 422 import std.ascii : toUpper; 423 import boolean_matrix : lowerRotator, upperRotator; 424 425 immutable refOffset = reflectorPos.toUpper - 'A'; 426 this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset); 427 } 428 } 429 430 /// 431 static if (hasPlugboard) 432 { 433 this(in Plugboard plugboard, in EntryWheel entryWheel, 434 in Repeat!(rotorN, Rotor) rotors, 435 in Reflector reflector, in dchar[rotorN] rotorStartPos) 436 { 437 this(entryWheel, rotors, reflector, rotorStartPos); 438 this.composedInputPerm = cast(immutable) (entryWheel * plugboard); 439 } 440 441 /// 442 static if (settableReflectorPos) 443 { 444 this(in Plugboard plugboard, in EntryWheel entryWheel, 445 in Repeat!(rotorN, Rotor) rotors, 446 in Reflector reflector, in dchar[rotorN] rotorStartPos, 447 dchar reflectorPos) 448 in 449 { 450 import std.ascii : isAlpha; 451 452 assert(reflectorPos.isAlpha, "Bad reflector position."); 453 } 454 body 455 { 456 this(plugboard, entryWheel, rotors, reflector, rotorStartPos); 457 458 import std.ascii : toUpper; 459 import boolean_matrix : lowerRotator, upperRotator; 460 461 immutable refOffset = reflectorPos.toUpper - 'A'; 462 this.reflector = upperRotator!N(refOffset) * reflector * lowerRotator!N(refOffset); 463 } 464 } 465 } 466 467 private void step() 468 { 469 enum movableRotorN = fixedFinalRotor ? rotorN - 1 : rotorN; 470 bool[movableRotorN] stepFlag; 471 472 stepFlag[0] = true; 473 474 // Handles double stepping 475 foreach (rotorID; 0 .. movableRotorN - 1) 476 { 477 import std.algorithm.searching : canFind; 478 479 if (rotors[rotorID].turnovers.canFind(rotationStates[rotorID])) 480 { 481 stepFlag[rotorID] = true; 482 stepFlag[rotorID + 1] = true; 483 } 484 } 485 486 foreach (rotorID, e; stepFlag) 487 { 488 if (e) 489 { 490 rotationStates[rotorID] = (rotationStates[rotorID] + 1) % N; 491 } 492 } 493 } 494 495 import boolean_matrix : BCV; 496 497 /+ 498 + fwdPerm = (Um*Rm*Lm)*(Um-1*Rm-1*Lm-1)*...*(U0*R0*L0)*P 499 + = Um*(Rm*Lm*Um-1)*(Rm-1*Lm-1*Um-2)*...*(R0*L0)*P 500 + = Um*(Rm*RELm)*(Rm-1*RELm-1)*...*(R0*L0)*P 501 +/ 502 private BCV!N composeForward(in ref BCV!N inputVec, size_t rotorID) 503 { 504 import boolean_matrix : lowerRotator, upperRotator; 505 506 immutable ptrdiff_t x = rotorID == 0 ? rotationStates[0] : rotationStates[rotorID] - rotationStates[rotorID - 1]; 507 immutable relRotator = x > 0 ? lowerRotator!N(x) : upperRotator!N(-x); 508 immutable composedVec = rotors[rotorID].perm * (relRotator * inputVec); 509 return rotorID == rotorN - 1 ? upperRotator!N(rotationStates[rotorID]) * composedVec 510 : composeForward(composedVec, rotorID + 1); 511 } 512 513 /+ 514 + bwdPerm = [(Um*Rm*Lm)*(Um-1*Rm-1*Lm-1)*...*(U0*R0*L0)*P]^-1 515 + = [Um*(Rm*RELm)*(Rm-1*RELm-1)*...*(R0*L0)*P]^-1 516 + = P^-1*(R0*L0)^-1*...(Rm-1*RELm-1)^-1*(Rm*RELm)^-1*Um^-1 517 + = P^-1*(U0*R0^-1)*...(RELm-1^-1*Rm-1^-1)*(RELm^-1*Rm^-1)*Lm 518 +/ 519 private BCV!N composeBackward(in ref BCV!N inputVec, size_t rotorID) 520 { 521 import boolean_matrix : lowerRotator, transpose, upperRotator; 522 523 immutable ptrdiff_t x = rotorID == 0 ? rotationStates[0] : rotationStates[rotorID] - rotationStates[rotorID - 1]; 524 immutable relRotatorInv = x > 0 ? upperRotator!N(x) : lowerRotator!N(-x); 525 immutable iv = rotorID == rotorN - 1 ? lowerRotator!N(rotationStates[rotorN - 1]) * inputVec : inputVec; 526 immutable composedVec = relRotatorInv * (rotors[rotorID].perm.transpose * iv); 527 return rotorID == 0 ? composedVec : composeBackward(composedVec, rotorID - 1); 528 } 529 530 private auto process(size_t keyInputID) 531 out (r) 532 { 533 assert(r >= 0); 534 } 535 body 536 { 537 step(); 538 539 import boolean_matrix : transpose, BCV; 540 541 immutable composedInput = composedInputPerm * BCV!N.e(keyInputID); 542 immutable reflectorOutput = reflector * composeForward(composedInput, 0); 543 immutable composedOutput = composedInputPerm.transpose * composeBackward(reflectorOutput,rotorN - 1) ; 544 import std.algorithm.searching : countUntil; 545 546 immutable r = composedOutput[].countUntil!"a"; 547 return r; 548 } 549 550 /// Enciphers only an alphabetical character through the current Enigma machine. 551 dchar opCall(dchar keyInput) 552 { 553 import std.ascii : isAlpha, toUpper; 554 555 return keyInput.isAlpha ? process(keyInput.toUpper - 'A') + 'A' : keyInput; 556 } 557 } 558 559 /// Enigma I 'Wehrmacht', which has three rotor slots. 560 alias EnigmaI = Enigma!3; 561 562 /// Enigma M3, which has three rotor slots. 563 alias EnigmaM3 = EnigmaI; 564 565 /// Enigma M4, which has four rotor slots. The fourth rotor never rotates. 566 alias EnigmaM4 = Enigma!(4, true); 567 568 /// Norway Enigma, which has three rotor slots. 569 alias Norway = EnigmaI; 570 571 /// Enigma D, which has three rotor slots and no plugboard. The reflector can be set to any positions. 572 alias EnigmaD = Enigma!(3, false, false, true); 573 574 /// Swiss K, which has three rotor slots and no plugboard. The reflector can be set to any positions. 575 alias SwissK = EnigmaD; 576 577 /// Enigma R, which has three rotor slots and no plugboard. The reflector can be set to any positions. 578 alias EnigmaR = EnigmaD; 579 580 /// Enigma T 'Tirpitz', which has three rotor slots and no plugboard. The reflector can be set to any positions. 581 alias EnigmaT = EnigmaD; 582 583 /// Predefined existent rotors. 584 auto rotorI(dchar ringSetting = 'A') pure 585 { 586 return rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', ringSetting); 587 } 588 589 /// ditto 590 auto rotorII(dchar ringSetting = 'A') pure 591 { 592 return rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', ringSetting); 593 } 594 595 /// ditto 596 auto rotorIII(dchar ringSetting = 'A') pure 597 { 598 return rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', ringSetting); 599 } 600 601 /// ditto 602 auto rotorIV(dchar ringSetting = 'A') pure 603 { 604 return rotor("ESOVPZJAYQUIRHXLNFTGKDCMWB", 'J', ringSetting); 605 } 606 607 /// ditto 608 auto rotorV(dchar ringSetting = 'A') pure 609 { 610 return rotor("VZBRGITYUPSDNHLXAWMJQOFECK", 'Z', ringSetting); 611 } 612 613 /// ditto 614 auto rotorVI(dchar ringSetting = 'A') pure 615 { 616 return rotor("JPGVOUMFYQBENHZRDKASXLICTW", 'Z', 'M', ringSetting); 617 } 618 619 /// ditto 620 auto rotorVII(dchar ringSetting = 'A') pure 621 { 622 return rotor("NZJHGRCXMYSWBOUFAIVLPEKQDT", 'Z', 'M', ringSetting); 623 } 624 625 /// ditto 626 auto rotorVIII(dchar ringSetting = 'A') pure 627 { 628 return rotor("FKQHTLXOCBJSPDZRAMEWNIUYGV", 'Z', 'M', ringSetting); 629 } 630 631 /// ditto 632 auto rotorINor(dchar ringSetting = 'A') pure 633 { 634 return rotor("WTOKASUYVRBXJHQCPZEFMDINLG", 'Q', ringSetting); 635 } 636 637 /// ditto 638 auto rotorIINor(dchar ringSetting = 'A') pure 639 { 640 return rotor("GJLPUBSWEMCTQVHXAOFZDRKYNI", 'E', ringSetting); 641 } 642 643 /// ditto 644 auto rotorIIINor(dchar ringSetting = 'A') pure 645 { 646 return rotor("JWFMHNBPUSDYTIXVZGRQLAOEKC", 'V', ringSetting); 647 } 648 649 /// ditto 650 alias rotorIVNor = rotorIV; 651 652 /// ditto 653 auto rotorVNor(dchar ringSetting = 'A') pure 654 { 655 return rotor("HEJXQOTZBVFDASCILWPGYNMURK", 'Z', ringSetting); 656 } 657 658 /// ditto 659 auto rotorID(dchar ringSetting = 'A') pure 660 { 661 return rotor("LPGSZMHAEOQKVXRFYBUTNICJDW", 'Y', ringSetting); 662 } 663 664 /// ditto 665 auto rotorIID(dchar ringSetting = 'A') pure 666 { 667 return rotor("SLVGBTFXJQOHEWIRZYAMKPCNDU", 'E', ringSetting); 668 } 669 670 /// ditto 671 auto rotorIIID(dchar ringSetting = 'A') pure 672 { 673 return rotor("CJGDPSHKTURAWZXFMYNQOBVLIE", 'N', ringSetting); 674 } 675 676 /// ditto 677 auto rotorIK(dchar ringSetting = 'A') pure 678 { 679 return rotor("PEZUOHXSCVFMTBGLRINQJWAYDK", 'Y', ringSetting); 680 } 681 682 /// ditto 683 auto rotorIIK(dchar ringSetting = 'A') pure 684 { 685 return rotor("ZOUESYDKFWPCIQXHMVBLGNJRAT", 'E', ringSetting); 686 } 687 688 /// ditto 689 auto rotorIIIK(dchar ringSetting = 'A') pure 690 { 691 return rotor("EHRVXGAOBQUSIMZFLYNWKTPDJC", 'N', ringSetting); 692 } 693 694 /// ditto 695 auto rotorIR(dchar ringSetting = 'A') pure 696 { 697 return rotor("JGDQOXUSCAMIFRVTPNEWKBLZYH", 'N', ringSetting); 698 } 699 700 /// ditto 701 auto rotorIIR(dchar ringSetting = 'A') pure 702 { 703 return rotor("NTZPSFBOKMWRCJDIVLAEYUXHGQ", 'E', ringSetting); 704 } 705 706 /// ditto 707 auto rotorIIIR(dchar ringSetting = 'A') pure 708 { 709 return rotor("JVIUBHTCDYAKEQZPOSGXNRMWFL", 'Y', ringSetting); 710 } 711 712 /// ditto 713 auto rotorIT(dchar ringSetting = 'A') pure 714 { 715 return rotor("KPTYUELOCVGRFQDANJMBSWHZXI", 'W', 'Z', 'E', 'K', 'Q', ringSetting); 716 } 717 718 auto rotorIIT(dchar ringSetting = 'A') pure 719 { 720 return rotor("UPHZLWEQMTDJXCAKSOIGVBYFNR", 'W', 'Z', 'F', 'L', 'R', ringSetting); 721 } 722 723 auto rotorIIIT(dchar ringSetting = 'A') pure 724 { 725 return rotor("QUDLYRFEKONVZAXWHMGPJBSICT", 'W', 'Z', 'E', 'K', 'Q', ringSetting); 726 } 727 728 auto rotorIVT(dchar ringSetting = 'A') pure 729 { 730 return rotor("CIWTBKXNRESPFLYDAGVHQUOJZM", 'W', 'Z', 'F', 'L', 'R', ringSetting); 731 } 732 733 auto rotorVT(dchar ringSetting = 'A') pure 734 { 735 return rotor("UAXGISNJBVERDYLFZWTPCKOHMQ", 'Y', 'C', 'F', 'K', 'R', ringSetting); 736 } 737 738 auto rotorVIT(dchar ringSetting = 'A') pure 739 { 740 return rotor("XFUZGALVHCNYSEWQTDMRBKPIOJ", 'X', 'E', 'I', 'M', 'Q', ringSetting); 741 } 742 743 auto rotorVIIT(dchar ringSetting = 'A') pure 744 { 745 return rotor("BJVFTXPLNAYOZIKWGDQERUCHSM", 'Y', 'C', 'F', 'K', 'R', ringSetting); 746 } 747 748 auto rotorVIIIT(dchar ringSetting = 'A') pure 749 { 750 return rotor("YMTPNZHWKODAJXELUQVGCBISFR", 'X', 'E', 'I', 'M', 'Q', ringSetting); 751 } 752 753 754 /++ 755 + Predefined existent rotors. Because these rotors have no turnover notches, they are generally set 756 + side by side with a reflector. 757 +/ 758 auto rotorBeta(dchar ringSetting = 'A') pure 759 { 760 return rotor("LEYJVCNIXWPBQMDRTAKZGFUHOS", ringSetting); 761 } 762 763 /// ditto 764 auto rotorGamma(dchar ringSetting = 'A') pure 765 { 766 return rotor("FSOKANUERHMBTIYCWLQPZXVGJD", ringSetting); 767 } 768 769 /// Predefined the simplest entry wheel which does not substitute. 770 auto entryWheelABC() pure 771 { 772 return entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 773 } 774 775 /// Predefined entry wheel: QWE... -> ABC... 776 auto entryWheelQWE() pure 777 { 778 return entryWheel("QWERTZUIOASDFGHJKPYXCVBNML"); 779 } 780 781 /// Predefined entry wheel: KZR... -> ABC... 782 auto entryWheelKZR() pure 783 { 784 return entryWheel("KZROUQHYAIGBLWVSTDXFPNMCJE"); 785 } 786 787 /// Predefined the simplest plugboard which does not substitute. 788 auto plugboardDoNothing() pure 789 { 790 return plugboard("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 791 } 792 793 /// Predefined existent reflectors. 794 auto reflectorA() pure 795 { 796 return reflector("EJMZALYXVBWFCRQUONTSPIKHGD"); 797 } 798 799 auto reflectorB() pure 800 { 801 return reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT"); 802 } 803 804 /// ditto 805 auto reflectorC() pure 806 { 807 return reflector("FVPJIAOYEDRZXWGCTKUQSBNMHL"); 808 } 809 810 /// ditto 811 auto reflectorBThin() pure 812 { 813 return reflector("ENKQAUYWJICOPBLMDXZVFTHRGS"); 814 } 815 816 /// ditto 817 auto reflectorCThin() pure 818 { 819 return reflector("RDOBJNTKVEHMLFCWZAXGYIPSUQ"); 820 } 821 822 /// ditto 823 auto reflectorNor() pure 824 { 825 return reflector("MOWJYPUXNDSRAIBFVLKZGQCHET"); 826 } 827 828 /// ditto 829 auto reflectorD(dchar ringSetting = 'A') pure 830 { 831 return reflector("IMETCGFRAYSQBZXWLHKDVUPOJN", ringSetting); 832 } 833 834 /// ditto 835 alias reflectorK = reflectorD; 836 837 /// ditto 838 auto reflectorR(dchar ringSetting = 'A') pure 839 { 840 return reflector("QYHOGNECVPUZTFDJAXWMKISRBL", ringSetting); 841 } 842 843 /// ditto 844 auto reflectorT(dchar ringSetting = 'A') pure 845 { 846 return reflector("GEKPBTAUMOCNILJDXZYFHWVQSR", ringSetting); 847 } 848 849 // Double stepping test (http://www.cryptomuseum.com/crypto/enigma/working.htm) 850 unittest 851 { 852 auto m3 = EnigmaM3(plugboardDoNothing, entryWheelABC, rotorI, rotorII, rotorIII, reflectorB, "ODA"); 853 854 assert(m3.rotationStates == [14, 3, 0]); 855 856 assert(m3('A') == 'H'); 857 assert(m3.rotationStates == [15, 3, 0]); 858 859 assert(m3('A') == 'D'); 860 assert(m3.rotationStates == [16, 3, 0]); 861 862 assert(m3('A') == 'Z'); 863 assert(m3.rotationStates == [17, 4, 0]); 864 865 assert(m3('A') == 'G'); 866 assert(m3.rotationStates == [18, 5, 1]); 867 868 assert(m3('A') == 'O'); 869 assert(m3.rotationStates == [19, 5, 1]); 870 871 assert(m3('A') == 'V'); 872 assert(m3.rotationStates == [20, 5, 1]); 873 } 874 875 // The noches are fixed to the ring. 876 unittest 877 { 878 auto ed = EnigmaD(entryWheelQWE, rotorID, rotorIID, rotorIIID, reflectorD, "UDN" /*!*/ , 'B'); 879 880 assert(ed.rotationStates == [20, 3, 13]); 881 882 assert(ed('A') == 'Z'); 883 assert(ed.rotationStates == [21, 3, 13]); 884 885 assert(ed('A') == 'D'); 886 assert(ed.rotationStates == [22, 3, 13]); 887 888 assert(ed('A') == 'V'); 889 assert(ed.rotationStates == [23, 3, 13]); 890 891 assert(ed('A') == 'I'); 892 assert(ed.rotationStates == [24, 3, 13]); 893 894 assert(ed('A') == 'C'); 895 assert(ed.rotationStates == [25, 4, 13]); 896 897 assert(ed('A') == 'Z'); 898 assert(ed.rotationStates == [0, 5, 14]); 899 900 901 // The K's rotor positions are same as the D's. 902 auto sk = SwissK(entryWheelQWE, rotorIK('Z'), rotorIIK('Y'), rotorIIIK, reflectorK('E'), "UDN" /*!*/ , 'X'); 903 904 assert(sk.rotationStates == [20, 3, 13]); 905 906 assert(sk('A') == 'Y'); 907 assert(sk.rotationStates == [21, 3, 13]); 908 909 assert(sk('A') == 'H'); 910 assert(sk.rotationStates == [22, 3, 13]); 911 912 assert(sk('A') == 'U'); 913 assert(sk.rotationStates == [23, 3, 13]); 914 915 assert(sk('A') == 'M'); 916 assert(sk.rotationStates == [24, 3, 13]); 917 918 assert(sk('A') == 'V'); 919 assert(sk.rotationStates == [25, 4, 13]); 920 921 assert(sk('A') == 'Q'); 922 assert(sk.rotationStates == [0, 5, 14]); 923 } 924 925 unittest 926 { 927 auto nor1 = Norway(entryWheelABC, rotorINor, rotorIINor, rotorIIINor, reflectorNor, "PDL"); 928 929 assert(nor1('A') == 'I'); 930 assert(nor1('A') == 'F'); 931 assert(nor1('A') == 'L'); 932 assert(nor1('A') == 'F'); 933 assert(nor1('A') == 'F'); 934 assert(nor1('A') == 'H'); 935 936 937 auto nor2 = Norway(plugboard("ABCDEGFHIJKLMNOPQRSTUVWXYZ"), entryWheelABC, rotorINor, rotorIVNor, rotorVNor, reflectorNor, "PDL"); 938 939 assert(nor2('A') == 'P'); 940 assert(nor2('A') == 'G'); 941 assert(nor2('A') == 'J'); 942 assert(nor2('A') == 'N'); 943 assert(nor2('A') == 'U'); 944 assert(nor2('A') == 'Z'); 945 } 946 947 unittest 948 { 949 auto er = EnigmaR(entryWheelQWE, rotorIR, rotorIIR, rotorIIIR, reflectorR('E'), "KBA", 'C'); 950 951 assert(er('A') == 'Y'); 952 assert(er('A') == 'P'); 953 assert(er('A') == 'I'); 954 assert(er('A') == 'R'); 955 assert(er('A') == 'Z'); 956 assert(er('A') == 'S'); 957 } 958 959 unittest 960 { 961 auto et1 = EnigmaT(entryWheelKZR, rotorIT, rotorIIT, rotorIIIT, reflectorT, "AFR", 'H'); 962 963 assert(et1('A') == 'E'); 964 assert(et1('A') == 'T'); 965 assert(et1('A') == 'U'); 966 assert(et1('A') == 'I'); 967 assert(et1('A') == 'H'); 968 assert(et1('A') == 'M'); 969 970 971 auto et2 = EnigmaT(entryWheelKZR, rotorVIT, rotorVT, rotorIVT, reflectorT, "AFR", 'A'); 972 973 assert(et2('A') == 'X'); 974 assert(et2('A') == 'F'); 975 assert(et2('A') == 'M'); 976 assert(et2('A') == 'R'); 977 assert(et2('A') == 'C'); 978 assert(et2('A') == 'B'); 979 980 981 auto et3 = EnigmaT(entryWheelKZR, rotorVIIT, rotorVIIIT('Z'), rotorIVT, reflectorT('C'), "AFR", 'V'); 982 983 assert(et3('A') == 'D'); 984 assert(et3('A') == 'S'); 985 assert(et3('A') == 'F'); 986 assert(et3('A') == 'N'); 987 assert(et3('A') == 'D'); 988 assert(et3('A') == 'T'); 989 } 990 991 /// Step-by-step enciphering. 992 unittest 993 { 994 immutable pbCI = plugboard("ABIDEFGHCJKLMNOPQRSTUVWXYZ"); // C <-> I 995 immutable enWh = entryWheel("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 996 immutable refB = reflector("YRUHQSLDPXNGOKMIEBFZCWVJAT"); 997 immutable rot1 = rotor("EKMFLGDQVZNTOWYHXUSPAIBRCJ", 'Q', 'A'); 998 immutable rot2 = rotor("AJDKSIRUXBLHWTMCQGZNPYFVOE", 'E', 'B'); 999 immutable rot3 = rotor("BDFHJLCPRTXVZNYEIWGAKMUSQO", 'V', 'A'); 1000 1001 auto e3 = Enigma!3(pbCI, enWh, rot1, rot2, rot3, refB, ['X', 'Q', 'E']); 1002 assert(e3('A') == 'K'); 1003 assert(e3('a') == 'T'); // A lowercase is automatically converted to an uppercase. 1004 assert(e3('5') == '5'); // A non-alphabetical character does not changes 1005 assert(e3('Ü') == 'Ü'); // the machine state and will be output as it is. 1006 assert(e3('A') == 'Q'); 1007 } 1008 1009 /// Encipherment is decipherment. 1010 unittest 1011 { 1012 // These have the same settings. 1013 auto encipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY"); 1014 auto decipherer = Enigma!2(entryWheelABC, rotorVI, rotorVII, reflectorC, "PY"); 1015 1016 foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 1017 { 1018 auto enciphered = encipherer(c); 1019 auto deciphered = decipherer(enciphered); 1020 } 1021 } 1022 1023 /++ 1024 + A certain equivalence of the M3 and the M4. 1025 +/ 1026 unittest 1027 { 1028 // These have the equivalent settings. 1029 auto m3 = EnigmaM3(entryWheelABC, rotorIII, rotorII, rotorI, reflectorB /*!*/ , 1030 "FOO"); 1031 auto m4 = EnigmaM4(entryWheelABC, rotorIII, rotorII, rotorI, 1032 rotorBeta('A') /*!*/ , reflectorBThin /*!*/ , "FOOA" /*!*/ ); // FOO*A* 1033 1034 // If each machine has just one movable rotor... 1035 auto e1 = Enigma!1(entryWheelABC, rotorI, reflectorC /*!*/ , "X"); 1036 auto e2fixed = Enigma!(2, true /*!*/ )(entryWheelABC, rotorI, 1037 rotorGamma('A') /*!*/ , reflectorCThin /*!*/ , "XA" /*!*/ ); // X*A* 1038 1039 foreach (dchar c; "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 1040 { 1041 assert(m3(c) == m4(c)); 1042 assert(e1(c) == e2fixed(c)); 1043 } 1044 } 1045 1046 /// Encipher with the M4 and decipher with the equivalent M3. 1047 unittest 1048 { 1049 import std.algorithm.comparison : equal; 1050 import std.algorithm.iteration : each, map; 1051 import std.array : appender; 1052 1053 // These have the equivalent settings. 1054 auto m4 = EnigmaM4(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'), 1055 rotorII('V'), rotorI('R'), rotorBeta, reflectorBThin, "UEQA"); 1056 auto m3 = EnigmaM3(plugboard("SBCDEGFHIJKLMNOPQRATUVWXYZ"), entryWheelABC, rotorIII('Y'), 1057 rotorII('V'), rotorI('R'), reflectorB, "UEQ"); 1058 1059 auto enciphered = appender!dstring; 1060 "ABCDEFGHIJKLMNOPQRSTUVWXYZ".map!m4.each!(c => enciphered.put(c)); 1061 assert(enciphered.data == "RIIGSIBEBIZKCTZSSDGQMLSVUX"); 1062 1063 auto deciphered = enciphered.data.map!m3; 1064 assert(deciphered.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); 1065 }