Skip to main content

cryptography/ciphers/
twofish.rs

1//! Twofish block cipher — AES submission (1998).
2//!
3//! 128-bit block cipher with the three standard key sizes:
4//!
5//! - `Twofish128` / `Twofish128Ct`
6//! - `Twofish192` / `Twofish192Ct`
7//! - `Twofish256` / `Twofish256Ct`
8//!
9//! The fast path keeps direct lookup tables for the 8-bit `q0` / `q1`
10//! permutations used inside the keyed `h` function. `Ct` variants evaluate the
11//! same permutations from the published 4-bit building blocks with fixed-scan
12//! nibble selection so the round function and key schedule avoid
13//! secret-indexed table reads.
14
15use crate::ct::zeroize_slice;
16use crate::BlockCipher;
17
18// Twofish key-schedule stride constant from the submission.
19const RHO: u32 = 0x0101_0101;
20// Twofish uses two GF(2^8) reduction polynomials:
21// - MDS matrix multiply: v(x) = x^8 + x^6 + x^5 + x^3 + 1 (0x169)
22// - RS key compressor:  w(x) = x^8 + x^6 + x^3 + x^2 + 1 (0x14d)
23const MDS_GF_POLY: u16 = 0x0169;
24const RS_GF_POLY: u16 = 0x014d;
25
26const Q0_T0: [u8; 16] = [8, 1, 7, 13, 6, 15, 3, 2, 0, 11, 5, 9, 14, 12, 10, 4];
27const Q0_T1: [u8; 16] = [14, 12, 11, 8, 1, 2, 3, 5, 15, 4, 10, 6, 7, 0, 9, 13];
28const Q0_T2: [u8; 16] = [11, 10, 5, 14, 6, 13, 9, 0, 12, 8, 15, 3, 2, 4, 7, 1];
29const Q0_T3: [u8; 16] = [13, 7, 15, 4, 1, 2, 6, 14, 9, 11, 3, 0, 8, 5, 12, 10];
30
31const Q1_T0: [u8; 16] = [2, 8, 11, 13, 15, 7, 6, 14, 3, 1, 9, 4, 0, 10, 12, 5];
32const Q1_T1: [u8; 16] = [1, 14, 2, 11, 4, 12, 3, 7, 6, 13, 10, 5, 15, 9, 0, 8];
33const Q1_T2: [u8; 16] = [4, 12, 7, 5, 1, 6, 9, 10, 0, 14, 13, 8, 2, 11, 3, 15];
34const Q1_T3: [u8; 16] = [11, 9, 5, 1, 12, 3, 13, 14, 6, 4, 7, 15, 2, 0, 8, 10];
35
36// Reed-Solomon matrix used to compress each 64-bit key chunk into S-box key words.
37const RS: [[u8; 8]; 4] = [
38    [0x01, 0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E],
39    [0xA4, 0x56, 0x82, 0xF3, 0x1E, 0xC6, 0x68, 0xE5],
40    [0x02, 0xA1, 0xFC, 0xC1, 0x47, 0xAE, 0x3D, 0x19],
41    [0xA4, 0x55, 0x87, 0x5A, 0x58, 0xDB, 0x9E, 0x03],
42];
43
44// Maximum-distance-separable matrix for the `h()` output diffusion layer.
45const MDS: [[u8; 4]; 4] = [
46    [0x01, 0xEF, 0x5B, 0x5B],
47    [0x5B, 0xEF, 0xEF, 0x01],
48    [0xEF, 0x5B, 0x01, 0xEF],
49    [0xEF, 0x01, 0xEF, 0x5B],
50];
51
52#[inline]
53const fn nibble_lookup(table: &[u8; 16], idx: u8) -> u8 {
54    table[idx as usize]
55}
56
57#[inline]
58const fn ror4(x: u8) -> u8 {
59    ((x >> 1) | ((x & 1) << 3)) & 0x0f
60}
61
62// Twofish defines q0 / q1 as 8-bit bijections built from two rounds of a
63// balanced Feistel network over nibble pairs, interleaved with four fixed
64// 4-bit lookup stages (T0..T3).  We keep that structure visible so the fast
65// and `Ct` paths share the same logic and only differ in how each nibble is
66// selected (direct table vs. `ct_lookup_u8_16`).
67//
68// One round of the Feistel mix: given upper nibble `a` and lower nibble `b`,
69//   a' = a ^ b
70//   b' = a ^ ror4(b) ^ ((a << 3) & 0xf)
71// where ror4 is a 4-bit right rotation.  This provides the avalanche that
72// makes q a non-trivial permutation despite the small nibble tables.
73const fn q_perm_const(x: u8, which: usize) -> u8 {
74    let (t0, t1, t2, t3) = if which == 0 {
75        (&Q0_T0, &Q0_T1, &Q0_T2, &Q0_T3)
76    } else {
77        (&Q1_T0, &Q1_T1, &Q1_T2, &Q1_T3)
78    };
79
80    let a0 = x >> 4;
81    let b0 = x & 0x0f;
82    // Round 1 Feistel mix.
83    let a1 = a0 ^ b0;
84    let b1 = a0 ^ ror4(b0) ^ ((a0 << 3) & 0x0f);
85    // Two independent nibble lookups.
86    let a2 = nibble_lookup(t0, a1);
87    let b2 = nibble_lookup(t1, b1);
88    // Round 2 Feistel mix.
89    let a3 = a2 ^ b2;
90    let b3 = a2 ^ ror4(b2) ^ ((a2 << 3) & 0x0f);
91    // Final two independent nibble lookups.
92    let a4 = nibble_lookup(t2, a3);
93    let b4 = nibble_lookup(t3, b3);
94    (b4 << 4) | a4
95}
96
97const fn build_q(which: usize) -> [u8; 256] {
98    let mut out = [0u8; 256];
99    let mut i = 0u8;
100    loop {
101        out[i as usize] = q_perm_const(i, which);
102        if i == u8::MAX {
103            break;
104        }
105        i = i.wrapping_add(1);
106    }
107    out
108}
109
110const Q0: [u8; 256] = build_q(0);
111const Q1: [u8; 256] = build_q(1);
112
113#[inline]
114fn q_perm_ct(x: u8, which: usize) -> u8 {
115    let (t0, t1, t2, t3) = if which == 0 {
116        (&Q0_T0, &Q0_T1, &Q0_T2, &Q0_T3)
117    } else {
118        (&Q1_T0, &Q1_T1, &Q1_T2, &Q1_T3)
119    };
120
121    let a0 = x >> 4;
122    let b0 = x & 0x0f;
123    let a1 = a0 ^ b0;
124    let b1 = a0 ^ ror4(b0) ^ ((a0 << 3) & 0x0f);
125    let a2 = crate::ct::ct_lookup_u8_16(t0, a1);
126    let b2 = crate::ct::ct_lookup_u8_16(t1, b1);
127    let a3 = a2 ^ b2;
128    let b3 = a2 ^ ror4(b2) ^ ((a2 << 3) & 0x0f);
129    let a4 = crate::ct::ct_lookup_u8_16(t2, a3);
130    let b4 = crate::ct::ct_lookup_u8_16(t3, b3);
131    (b4 << 4) | a4
132}
133
134#[inline]
135fn q_perm(x: u8, which: usize, use_ct: bool) -> u8 {
136    if use_ct {
137        q_perm_ct(x, which)
138    } else if which == 0 {
139        Q0[x as usize]
140    } else {
141        Q1[x as usize]
142    }
143}
144
145#[inline]
146fn gf_mul(mut a: u8, mut b: u8, poly: u16) -> u8 {
147    let mut out = 0u8;
148    for _ in 0..8 {
149        let mask = 0u8.wrapping_sub(b & 1);
150        out ^= a & mask;
151        let hi = a & 0x80;
152        a <<= 1;
153        a ^= ((poly & 0xff) as u8) & 0u8.wrapping_sub((hi >> 7) & 1);
154        b >>= 1;
155    }
156    out
157}
158
159fn rs_mds_encode(bytes: [u8; 8]) -> u32 {
160    // The RS matrix compresses each 64-bit key chunk into one S-box key word.
161    let mut out = [0u8; 4];
162    let mut row = 0usize;
163    while row < 4 {
164        let mut acc = 0u8;
165        let mut col = 0usize;
166        while col < 8 {
167            acc ^= gf_mul(RS[row][col], bytes[col], RS_GF_POLY);
168            col += 1;
169        }
170        out[row] = acc;
171        row += 1;
172    }
173    u32::from_le_bytes(out)
174}
175
176#[inline]
177fn b(word: u32, idx: usize) -> u8 {
178    ((word >> (idx * 8)) & 0xff) as u8
179}
180
181fn mds_multiply(y: [u8; 4]) -> u32 {
182    // Twofish's keyed `h()` function always ends with the fixed 4x4 MDS mix.
183    let mut out = [0u8; 4];
184    let mut row = 0usize;
185    while row < 4 {
186        let mut acc = 0u8;
187        let mut col = 0usize;
188        while col < 4 {
189            acc ^= gf_mul(MDS[row][col], y[col], MDS_GF_POLY);
190            col += 1;
191        }
192        out[row] = acc;
193        row += 1;
194    }
195    u32::from_le_bytes(out)
196}
197
198fn h(x: u32, l: &[u32; 4], words: usize, use_ct: bool) -> u32 {
199    let mut y = x.to_le_bytes();
200
201    // Extra key words add extra q-permutation layers for 192- and 256-bit
202    // keys before the shared 128-bit tail of the construction.
203    if words == 4 {
204        y[0] = q_perm(y[0], 1, use_ct) ^ b(l[3], 0);
205        y[1] = q_perm(y[1], 0, use_ct) ^ b(l[3], 1);
206        y[2] = q_perm(y[2], 0, use_ct) ^ b(l[3], 2);
207        y[3] = q_perm(y[3], 1, use_ct) ^ b(l[3], 3);
208    }
209    if words >= 3 {
210        y[0] = q_perm(y[0], 1, use_ct) ^ b(l[2], 0);
211        y[1] = q_perm(y[1], 1, use_ct) ^ b(l[2], 1);
212        y[2] = q_perm(y[2], 0, use_ct) ^ b(l[2], 2);
213        y[3] = q_perm(y[3], 0, use_ct) ^ b(l[2], 3);
214    }
215
216    // The final three q layers are the common keyed core from the submission
217    // paper. This implementation computes them directly instead of building
218    // the large keyed MDS tables used by faster Twofish software.
219    y[0] = q_perm(
220        q_perm(q_perm(y[0], 0, use_ct) ^ b(l[1], 0), 0, use_ct) ^ b(l[0], 0),
221        1,
222        use_ct,
223    );
224    y[1] = q_perm(
225        q_perm(q_perm(y[1], 1, use_ct) ^ b(l[1], 1), 0, use_ct) ^ b(l[0], 1),
226        0,
227        use_ct,
228    );
229    y[2] = q_perm(
230        q_perm(q_perm(y[2], 0, use_ct) ^ b(l[1], 2), 1, use_ct) ^ b(l[0], 2),
231        1,
232        use_ct,
233    );
234    y[3] = q_perm(
235        q_perm(q_perm(y[3], 1, use_ct) ^ b(l[1], 3), 1, use_ct) ^ b(l[0], 3),
236        0,
237        use_ct,
238    );
239
240    mds_multiply(y)
241}
242
243fn expand_key<const N: usize>(key: &[u8; N], use_ct: bool) -> ([u32; 40], [u32; 4], usize) {
244    let words = N / 8;
245
246    let mut me = [0u32; 4];
247    let mut mo = [0u32; 4];
248    let mut s_words = [0u32; 4];
249
250    let mut word_idx = 0usize;
251    while word_idx < words {
252        // Even and odd 32-bit words feed separate `h()` calls in the subkey
253        // schedule, while the RS matrix derives the S-box key words in reverse
254        // chunk order.
255        me[word_idx] = u32::from_le_bytes(key[word_idx * 8..word_idx * 8 + 4].try_into().unwrap());
256        mo[word_idx] =
257            u32::from_le_bytes(key[word_idx * 8 + 4..word_idx * 8 + 8].try_into().unwrap());
258        let chunk: &[u8; 8] = key[word_idx * 8..word_idx * 8 + 8].try_into().unwrap();
259        s_words[words - 1 - word_idx] = rs_mds_encode(*chunk);
260        word_idx += 1;
261    }
262
263    let mut sub = [0u32; 40];
264    let mut subkey_idx = 0usize;
265    while subkey_idx < 20 {
266        // K[0..3] are input whitening, K[4..7] output whitening, and the
267        // remaining 32 words supply the 16 rounds.
268        let even_input = u32::try_from(2 * subkey_idx).expect("subkey index fits in u32");
269        let odd_input = even_input + 1;
270        let even_g = h(even_input.wrapping_mul(RHO), &me, words, use_ct);
271        let odd_g = h(odd_input.wrapping_mul(RHO), &mo, words, use_ct).rotate_left(8);
272        sub[2 * subkey_idx] = even_g.wrapping_add(odd_g);
273        sub[2 * subkey_idx + 1] = even_g
274            .wrapping_add(odd_g.wrapping_add(odd_g))
275            .rotate_left(9);
276        subkey_idx += 1;
277    }
278
279    (sub, s_words, words)
280}
281
282#[inline]
283fn round_f(
284    x0: u32,
285    x1: u32,
286    subkeys: &[u32; 40],
287    s: &[u32; 4],
288    words: usize,
289    round: usize,
290    use_ct: bool,
291) -> (u32, u32) {
292    // Twofish's round function is the pair of keyed `g()` calls followed by
293    // the pseudo-Hadamard transform and round subkey injection.
294    let t0 = h(x0, s, words, use_ct);
295    let t1 = h(x1.rotate_left(8), s, words, use_ct);
296    let f0 = t0.wrapping_add(t1).wrapping_add(subkeys[8 + 2 * round]);
297    let f1 = t0
298        .wrapping_add(t1.wrapping_add(t1))
299        .wrapping_add(subkeys[8 + 2 * round + 1]);
300    (f0, f1)
301}
302
303#[derive(Clone, Copy)]
304struct TwofishCore {
305    subkeys: [u32; 40],
306    s: [u32; 4],
307    words: usize,
308    use_ct: bool,
309}
310
311impl TwofishCore {
312    fn new<const N: usize>(key: &[u8; N], use_ct: bool) -> Self {
313        let (subkeys, s, words) = expand_key(key, use_ct);
314        Self {
315            subkeys,
316            s,
317            words,
318            use_ct,
319        }
320    }
321
322    fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
323        let mut x0 = u32::from_le_bytes(block[0..4].try_into().unwrap()) ^ self.subkeys[0];
324        let mut x1 = u32::from_le_bytes(block[4..8].try_into().unwrap()) ^ self.subkeys[1];
325        let mut x2 = u32::from_le_bytes(block[8..12].try_into().unwrap()) ^ self.subkeys[2];
326        let mut x3 = u32::from_le_bytes(block[12..16].try_into().unwrap()) ^ self.subkeys[3];
327
328        let mut round = 0usize;
329        while round < 8 {
330            // Two rounds are grouped per loop so the Feistel word swap stays
331            // explicit without introducing a separate temporary block shuffle.
332            let (f0, f1) = round_f(
333                x0,
334                x1,
335                &self.subkeys,
336                &self.s,
337                self.words,
338                2 * round,
339                self.use_ct,
340            );
341            x2 = (x2 ^ f0).rotate_right(1);
342            x3 = x3.rotate_left(1) ^ f1;
343
344            let (f0, f1) = round_f(
345                x2,
346                x3,
347                &self.subkeys,
348                &self.s,
349                self.words,
350                2 * round + 1,
351                self.use_ct,
352            );
353            x0 = (x0 ^ f0).rotate_right(1);
354            x1 = x1.rotate_left(1) ^ f1;
355
356            round += 1;
357        }
358
359        let c0 = x2 ^ self.subkeys[4];
360        let c1 = x3 ^ self.subkeys[5];
361        let c2 = x0 ^ self.subkeys[6];
362        let c3 = x1 ^ self.subkeys[7];
363
364        let mut out = [0u8; 16];
365        out[0..4].copy_from_slice(&c0.to_le_bytes());
366        out[4..8].copy_from_slice(&c1.to_le_bytes());
367        out[8..12].copy_from_slice(&c2.to_le_bytes());
368        out[12..16].copy_from_slice(&c3.to_le_bytes());
369        out
370    }
371
372    fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
373        let mut x2 = u32::from_le_bytes(block[0..4].try_into().unwrap()) ^ self.subkeys[4];
374        let mut x3 = u32::from_le_bytes(block[4..8].try_into().unwrap()) ^ self.subkeys[5];
375        let mut x0 = u32::from_le_bytes(block[8..12].try_into().unwrap()) ^ self.subkeys[6];
376        let mut x1 = u32::from_le_bytes(block[12..16].try_into().unwrap()) ^ self.subkeys[7];
377
378        let mut round = 8usize;
379        while round > 0 {
380            round -= 1;
381
382            // Decryption walks the same structure backward with the round
383            // subkeys consumed in reverse order.
384            let (f0, f1) = round_f(
385                x2,
386                x3,
387                &self.subkeys,
388                &self.s,
389                self.words,
390                2 * round + 1,
391                self.use_ct,
392            );
393            x1 = (x1 ^ f1).rotate_right(1);
394            x0 = x0.rotate_left(1) ^ f0;
395
396            let (f0, f1) = round_f(
397                x0,
398                x1,
399                &self.subkeys,
400                &self.s,
401                self.words,
402                2 * round,
403                self.use_ct,
404            );
405            x3 = (x3 ^ f1).rotate_right(1);
406            x2 = x2.rotate_left(1) ^ f0;
407        }
408
409        let p0 = x0 ^ self.subkeys[0];
410        let p1 = x1 ^ self.subkeys[1];
411        let p2 = x2 ^ self.subkeys[2];
412        let p3 = x3 ^ self.subkeys[3];
413
414        let mut out = [0u8; 16];
415        out[0..4].copy_from_slice(&p0.to_le_bytes());
416        out[4..8].copy_from_slice(&p1.to_le_bytes());
417        out[8..12].copy_from_slice(&p2.to_le_bytes());
418        out[12..16].copy_from_slice(&p3.to_le_bytes());
419        out
420    }
421}
422
423macro_rules! define_twofish_type {
424    ($name:ident, $name_ct:ident, $key_len:expr) => {
425        pub struct $name {
426            core: TwofishCore,
427        }
428
429        impl $name {
430            /// Expand the user key into the whitening and round subkeys.
431            pub fn new(key: &[u8; $key_len]) -> Self {
432                Self {
433                    core: TwofishCore::new(key, false),
434                }
435            }
436
437            /// Expand the key and then wipe the caller-owned key buffer.
438            pub fn new_wiping(key: &mut [u8; $key_len]) -> Self {
439                let out = Self::new(key);
440                zeroize_slice(key);
441                out
442            }
443
444            /// Encrypt one 128-bit block.
445            pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
446                self.core.encrypt_block(block)
447            }
448
449            /// Decrypt one 128-bit block.
450            pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
451                self.core.decrypt_block(block)
452            }
453        }
454
455        impl BlockCipher for $name {
456            const BLOCK_LEN: usize = 16;
457
458            fn encrypt(&self, block: &mut [u8]) {
459                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
460                let out = self.encrypt_block(arr);
461                block.copy_from_slice(&out);
462            }
463
464            fn decrypt(&self, block: &mut [u8]) {
465                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
466                let out = self.decrypt_block(arr);
467                block.copy_from_slice(&out);
468            }
469        }
470
471        impl Drop for $name {
472            fn drop(&mut self) {
473                zeroize_slice(&mut self.core.subkeys);
474                zeroize_slice(&mut self.core.s);
475            }
476        }
477
478        pub struct $name_ct {
479            core: TwofishCore,
480        }
481
482        impl $name_ct {
483            /// Expand the user key into the whitening and round subkeys.
484            pub fn new(key: &[u8; $key_len]) -> Self {
485                Self {
486                    core: TwofishCore::new(key, true),
487                }
488            }
489
490            /// Expand the key and then wipe the caller-owned key buffer.
491            pub fn new_wiping(key: &mut [u8; $key_len]) -> Self {
492                let out = Self::new(key);
493                zeroize_slice(key);
494                out
495            }
496
497            /// Encrypt one 128-bit block with the software constant-time path.
498            pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
499                self.core.encrypt_block(block)
500            }
501
502            /// Decrypt one 128-bit block with the software constant-time path.
503            pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
504                self.core.decrypt_block(block)
505            }
506        }
507
508        impl BlockCipher for $name_ct {
509            const BLOCK_LEN: usize = 16;
510
511            fn encrypt(&self, block: &mut [u8]) {
512                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
513                let out = self.encrypt_block(arr);
514                block.copy_from_slice(&out);
515            }
516
517            fn decrypt(&self, block: &mut [u8]) {
518                let arr: &[u8; 16] = (&*block).try_into().expect("wrong block length");
519                let out = self.decrypt_block(arr);
520                block.copy_from_slice(&out);
521            }
522        }
523
524        impl Drop for $name_ct {
525            fn drop(&mut self) {
526                zeroize_slice(&mut self.core.subkeys);
527                zeroize_slice(&mut self.core.s);
528            }
529        }
530    };
531}
532
533define_twofish_type!(Twofish128, Twofish128Ct, 16);
534define_twofish_type!(Twofish192, Twofish192Ct, 24);
535define_twofish_type!(Twofish256, Twofish256Ct, 32);
536
537pub type Twofish = Twofish128;
538pub type TwofishCt = Twofish128Ct;
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543
544    fn decode_hex<const N: usize>(s: &str) -> [u8; N] {
545        assert_eq!(s.len(), N * 2);
546        let mut out = [0u8; N];
547        let bytes = s.as_bytes();
548        let mut i = 0usize;
549        while i < N {
550            let hi = u8::try_from((bytes[2 * i] as char).to_digit(16).unwrap())
551                .expect("decoded hex nibble fits in u8");
552            let lo = u8::try_from((bytes[2 * i + 1] as char).to_digit(16).unwrap())
553                .expect("decoded hex nibble fits in u8");
554            out[i] = (hi << 4) | lo;
555            i += 1;
556        }
557        out
558    }
559
560    #[test]
561    fn twofish128_zero_kat() {
562        let key = [0u8; 16];
563        let pt = [0u8; 16];
564        let ct = decode_hex::<16>("9F589F5CF6122C32B6BFEC2F2AE8C35A");
565        let fast = Twofish128::new(&key);
566        let slow = Twofish128Ct::new(&key);
567        assert_eq!(fast.encrypt_block(&pt), ct);
568        assert_eq!(slow.encrypt_block(&pt), ct);
569        assert_eq!(fast.decrypt_block(&ct), pt);
570        assert_eq!(slow.decrypt_block(&ct), pt);
571    }
572
573    #[test]
574    fn twofish128_nonzero_kat() {
575        // Twofish paper ("ecb_tbl.txt", Full Encryptions, KEYSIZE=128, I=4).
576        let key = decode_hex::<16>("D491DB16E7B1C39E86CB086B789F5419");
577        let pt = decode_hex::<16>("019F9809DE1711858FAAC3A3BA20FBC3");
578        let ct = decode_hex::<16>("6363977DE839486297E661C6C9D668EB");
579        let fast = Twofish128::new(&key);
580        let slow = Twofish128Ct::new(&key);
581        assert_eq!(fast.encrypt_block(&pt), ct);
582        assert_eq!(slow.encrypt_block(&pt), ct);
583        assert_eq!(fast.decrypt_block(&ct), pt);
584        assert_eq!(slow.decrypt_block(&ct), pt);
585    }
586
587    #[test]
588    fn twofish192_zero_kat() {
589        let key = [0u8; 24];
590        let pt = [0u8; 16];
591        let ct = decode_hex::<16>("EFA71F788965BD4453F860178FC19101");
592        let fast = Twofish192::new(&key);
593        let slow = Twofish192Ct::new(&key);
594        assert_eq!(fast.encrypt_block(&pt), ct);
595        assert_eq!(slow.encrypt_block(&pt), ct);
596        assert_eq!(fast.decrypt_block(&ct), pt);
597        assert_eq!(slow.decrypt_block(&ct), pt);
598    }
599
600    #[test]
601    fn twofish192_nonzero_kat() {
602        // Twofish paper ("ecb_tbl.txt", Full Encryptions, KEYSIZE=192, I=4).
603        let key = decode_hex::<24>("88B2B2706B105E36B446BB6D731A1E88EFA71F788965BD44");
604        let pt = decode_hex::<16>("39DA69D6BA4997D585B6DC073CA341B2");
605        let ct = decode_hex::<16>("182B02D81497EA45F9DAACDC29193A65");
606        let fast = Twofish192::new(&key);
607        let slow = Twofish192Ct::new(&key);
608        assert_eq!(fast.encrypt_block(&pt), ct);
609        assert_eq!(slow.encrypt_block(&pt), ct);
610        assert_eq!(fast.decrypt_block(&ct), pt);
611        assert_eq!(slow.decrypt_block(&ct), pt);
612    }
613
614    #[test]
615    fn twofish256_zero_kat() {
616        let key = [0u8; 32];
617        let pt = [0u8; 16];
618        let ct = decode_hex::<16>("57FF739D4DC92C1BD7FC01700CC8216F");
619        let fast = Twofish256::new(&key);
620        let slow = Twofish256Ct::new(&key);
621        assert_eq!(fast.encrypt_block(&pt), ct);
622        assert_eq!(slow.encrypt_block(&pt), ct);
623        assert_eq!(fast.decrypt_block(&ct), pt);
624        assert_eq!(slow.decrypt_block(&ct), pt);
625    }
626
627    #[test]
628    fn twofish256_nonzero_kat() {
629        // Twofish paper ("ecb_tbl.txt", Full Encryptions, KEYSIZE=256, I=4).
630        let key =
631            decode_hex::<32>("D43BB7556EA32E46F2A282B7D45B4E0D57FF739D4DC92C1BD7FC01700CC8216F");
632        let pt = decode_hex::<16>("90AFE91BB288544F2C32DC239B2635E6");
633        let ct = decode_hex::<16>("6CB4561C40BF0A9705931CB6D408E7FA");
634        let fast = Twofish256::new(&key);
635        let slow = Twofish256Ct::new(&key);
636        assert_eq!(fast.encrypt_block(&pt), ct);
637        assert_eq!(slow.encrypt_block(&pt), ct);
638        assert_eq!(fast.decrypt_block(&ct), pt);
639        assert_eq!(slow.decrypt_block(&ct), pt);
640    }
641
642    #[test]
643    fn q_tables_match_ct_path() {
644        let mut i = 0usize;
645        while i < 256 {
646            let idx = u8::try_from(i).expect("Q table index fits in u8");
647            assert_eq!(Q0[i], q_perm_ct(idx, 0));
648            assert_eq!(Q1[i], q_perm_ct(idx, 1));
649            i += 1;
650        }
651    }
652}