lorenz_sz/
lib.rs

1//! Simulator of the [Lorenz SZ40 cipher machine](https://en.wikipedia.org/wiki/Lorenz_cipher).
2//!
3//! ## Example
4//!
5//! ```rust
6//! use lorenz_sz::{Machine, patterns::ZMUG_PATTERN};
7//!
8//! let machine = Machine::new(&ZMUG_PATTERN);
9//! for k in machine.keystream().take(50) {
10//!     // You'll need to handle the encryption yourself; this crate only
11//!     // implements the pseudo-random number generator.
12//!     println!("{}", k);
13//! }
14//! ```
15
16#![no_std]
17
18use core::iter::{Cycle, Iterator, Peekable};
19use core::slice::Iter;
20
21pub type ChiPattern = ([bool; 41], [bool; 31], [bool; 29], [bool; 26], [bool; 23]);
22pub type MuPattern = ([bool; 37], [bool; 61]);
23pub type PsiPattern = ([bool; 43], [bool; 47], [bool; 51], [bool; 53], [bool; 59]);
24
25/// A Lorenz SZ40 machine.
26pub struct Machine<'a> {
27    chi: &'a ChiPattern,
28    mu: &'a MuPattern,
29    psi: &'a PsiPattern,
30}
31
32impl<'a> Machine<'a> {
33    /// Create a machine from a set of wheel patterns.
34    pub fn new((chi, mu, psi): &'a (ChiPattern, MuPattern, PsiPattern)) -> Self {
35        Machine { chi, mu, psi }
36    }
37
38    /// Obtain an iterator over the keystream produced by the machine.
39    pub fn keystream(&self) -> Keystream {
40        Keystream {
41            // I suppose that the chi streams don't need to be peekable, since
42            // they always step. But it makes the typing easier, and presumably
43            // the compiler is smart enough to optimise this.
44            chi: (
45                self.chi.0.iter().cycle().peekable(),
46                self.chi.1.iter().cycle().peekable(),
47                self.chi.2.iter().cycle().peekable(),
48                self.chi.3.iter().cycle().peekable(),
49                self.chi.4.iter().cycle().peekable(),
50            ),
51            mu: (
52                self.mu.0.iter().cycle().peekable(),
53                self.mu.1.iter().cycle().peekable(),
54            ),
55            psi: (
56                self.psi.0.iter().cycle().peekable(),
57                self.psi.1.iter().cycle().peekable(),
58                self.psi.2.iter().cycle().peekable(),
59                self.psi.3.iter().cycle().peekable(),
60                self.psi.4.iter().cycle().peekable(),
61            ),
62        }
63    }
64}
65
66type Impulse<'a> = Peekable<Cycle<Iter<'a, bool>>>;
67
68/// A keystream produced by a Lorenz SZ40 machine.
69pub struct Keystream<'a> {
70    chi: (
71        Impulse<'a>,
72        Impulse<'a>,
73        Impulse<'a>,
74        Impulse<'a>,
75        Impulse<'a>,
76    ),
77    mu: (Impulse<'a>, Impulse<'a>),
78    psi: (
79        Impulse<'a>,
80        Impulse<'a>,
81        Impulse<'a>,
82        Impulse<'a>,
83        Impulse<'a>,
84    ),
85}
86
87impl<'a> Iterator for Keystream<'a> {
88    type Item = u32;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        // Determine the total motor. For now, we're ignoring limitations, so
92        // this is just the basic motor.
93        let basic_motor: bool = if *self.mu.1.next().unwrap() {
94            *self.mu.0.next().unwrap()
95        } else {
96            **self.mu.0.peek().unwrap()
97        };
98        let total_motor = basic_motor;
99
100        // Produce the keystream character.
101        let chi: u32 = (*self.chi.0.next().unwrap() as u32)
102            | ((*self.chi.1.next().unwrap() as u32) << 1)
103            | ((*self.chi.2.next().unwrap() as u32) << 2)
104            | ((*self.chi.3.next().unwrap() as u32) << 3)
105            | ((*self.chi.4.next().unwrap() as u32) << 4);
106        if total_motor {
107            Some(
108                chi ^ ((*self.psi.0.next().unwrap() as u32)
109                    | ((*self.psi.1.next().unwrap() as u32) << 1)
110                    | ((*self.psi.2.next().unwrap() as u32) << 2)
111                    | ((*self.psi.3.next().unwrap() as u32) << 3)
112                    | ((*self.psi.4.next().unwrap() as u32) << 4)),
113            )
114        } else {
115            Some(
116                chi ^ ((**self.psi.0.peek().unwrap() as u32)
117                    | ((**self.psi.1.peek().unwrap() as u32) << 1)
118                    | ((**self.psi.2.peek().unwrap() as u32) << 2)
119                    | ((**self.psi.3.peek().unwrap() as u32) << 3)
120                    | ((**self.psi.4.peek().unwrap() as u32) << 4)),
121            )
122        }
123    }
124}
125
126#[cfg(test)]
127mod test {
128    use super::*;
129
130    #[test]
131    fn it_produces_zmug_keystream() {
132        // This test-case is based on CyberChef's implementation of Lorenz.
133        let machine = Machine::new(&patterns::ZMUG_PATTERN);
134        let mut keystream = machine.keystream();
135        assert_eq!(keystream.next(), Some(0b01010 ^ 0b10001));
136        assert_eq!(keystream.next(), Some(0b11000 ^ 0b10001));
137        assert_eq!(keystream.next(), Some(0b00101 ^ 0b01110));
138        assert_eq!(keystream.next(), Some(0b11101 ^ 0b10110));
139        assert_eq!(keystream.next(), Some(0b00110 ^ 0b11100));
140        let mut keystream = keystream.skip(995);
141        assert_eq!(keystream.next(), Some(0b11110));
142        assert_eq!(keystream.next(), Some(0b01110));
143        assert_eq!(keystream.next(), Some(0b11111));
144        assert_eq!(keystream.next(), Some(0b10111));
145        assert_eq!(keystream.next(), Some(0b11010));
146    }
147}
148
149pub mod patterns {
150    use crate::{ChiPattern, MuPattern, PsiPattern};
151
152    /// The cams used by the infamous 30 August 1941 "HQIBPEXEZMUG" message.
153    #[rustfmt::skip]
154    pub const ZMUG_PATTERN: (ChiPattern, MuPattern, PsiPattern) = (
155        (
156            [false, false, true , true , false, false, true , true , true , false, false, false, false, true , true , true , false, false, true , true , true , false, false, false, false, true , false, false, true , true , false, true , true , false, false, false, true , true , false, true , true ],
157            [true , false, false, false, true , true , false, false, true , true , false, false, false, true , false, true , true , true , true , false, true , true , true , false, false, false, false, true , true , false, true ],
158            [false, false, true , true , true , true , false, true , true , false, false, false, true , true , true , false, false, false, true , true , false, false, false, true , true , false, false, true , false],
159            [true , true , false, true , false, false, true , true , true , false, true , false, false, true , true , false, false, false, true , true , false, false, true , false, true , false],
160            [false, true , false, true , false, false, false, false, true , true , true , false, true , false, false, false, true , true , true , true , false, false, true ],
161        ),
162        (
163            [false, true , true , true , false, true , true , true , false, true , true , true , false, true , false, true , true , false, true , true , true , false, true , true , true , false, true , true , false, true , false, true , true , false, true , false, true ],
164            [true , true , false, true , false, true , false, true , true , true , true , false, true , false, true , false, true , true , true , false, true , true , true , false, true , true , true , false, true , true , false, true , true , false, true , true , false, true , true , false, true , true , true , false, true , false, true , false, true , true , true , false, true , true , true , false, true , false, true , true , false],
165        ),
166        (
167            [true , false, false, false, true , true , true , false, false, true , true , true , false, false, true , true , true , true , false, false, false, true , true , false, false, false, true , true , false, false, true , true , true , false, false, false, true , true , false, false, true , false, true ],
168            [false, true , true , false, true , false, false, true , true , true , false, false, true , true , true , false, false, true , true , false, false, false, true , true , true , true , false, false, false, true , true , true , false, false, true , true , false, false, true , true , true , false, false, false, true , false, false],
169            [false, true , true , true , false, false, true , true , false, false, false, true , true , false, false, false, true , true , true , false, false, false, true , false, false, false, true , true , true , true , false, false, false, true , false, false, true , true , true , false, false, true , true , true , false, false, true , true , false, false, true ],
170            [false, true , false, true , true , true , false, false, true , true , false, false, false, true , true , false, false, true , false, false, true , true , true , false, false, true , true , false, false, false, true , false, false, false, true , true , true , true , false, false, true , true , true , false, false, true , true , false, false, true , true , true , false],
171            [true , false, true , true , false, false, false, true , false, false, true , true , true , false, false, true , true , false, false, true , true , true , false, false, true , true , false, false, false, true , true , true , true , false, false, true , false, false, true , true , true , false, false, true , true , true , true , false, false, false, true , false, false, false, true , true , true , false, false],
172        ),
173    );
174}