hopr_crypto_types/
lioness.rs

1//! This module implements a generic Lioness wide-block cipher.
2//!
3//! It is based on the
4//! [Lioness wide-block cipher](https://www.cl.cam.ac.uk/archive/rja14/Papers/bear-lion.pdf)
5//! as proposed by Ross Anderson and Eli Biham.
6use std::{fmt::Formatter, marker::PhantomData, ops::Sub};
7
8#[allow(deprecated)] // Until the crate updates to newer versions of `generic-array`
9use cipher::{
10    AlgorithmName, ArrayLength, Block, BlockSizeUser, Iv, IvSizeUser, Key, KeyInit, KeyIvInit, KeySizeUser,
11    StreamCipher, generic_array::GenericArray, inout::InOut,
12};
13use digest::{Digest, OutputSizeUser};
14use typenum::{B1, Diff, IsEqual, IsGreater, Unsigned};
15
16use crate::crypto_traits::PRP;
17
18/// Implementation of [Lioness wide-block cipher](https://www.cl.cam.ac.uk/archive/rja14/Papers/bear-lion.pdf) over a keyed [`Digest`] and a [`StreamCipher`].
19///
20/// ## Requirements
21/// - The output size of the `Digest` `H` must match the key size of the `StreamCipher`.
22/// - The key size of the keyed digest `H` must be equal to the key size of the `StreamCipher`.
23/// - The block size `B` can be arbitrary but must be strictly greater than the key size of the `StreamCipher`.
24/// - However, for cryptographic security, `B` must be at least twice the key size of the `StreamCipher`.
25///
26/// The key size of the Lioness cipher is 4-times the size of `StreamCipher`'s
27/// key.
28///
29/// The IV size of the Lioness cipher is 2-times the size of the `StreamCipher`'s
30/// IV size.
31#[derive(Clone, zeroize::ZeroizeOnDrop)]
32pub struct Lioness<H: KeySizeUser + OutputSizeUser, S: KeySizeUser + IvSizeUser, B: ArrayLength<u8>>
33where
34    // OutputSize of the digest must be
35    // equal to the KeySize of the
36    // stream cipher
37    H::OutputSize: IsEqual<S::KeySize, Output = B1>,
38    // KeySize of the digest must be equal to the KeySize of the stream cipher
39    H::KeySize: IsEqual<S::KeySize, Output = B1>,
40    // BlockSize must be greater or equal to the KeySize of the stream cipher
41    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
42{
43    k1: GenericArray<u8, S::KeySize>,
44    k2: GenericArray<u8, H::KeySize>,
45    k3: GenericArray<u8, S::KeySize>,
46    k4: GenericArray<u8, H::KeySize>,
47    iv1: GenericArray<u8, S::IvSize>,
48    iv2: GenericArray<u8, S::IvSize>,
49    _phantom: PhantomData<(H, S, B)>,
50}
51
52impl<H: KeySizeUser + OutputSizeUser, S: KeySizeUser + IvSizeUser, B: ArrayLength<u8>> KeySizeUser for Lioness<H, S, B>
53where
54    H::OutputSize: IsEqual<S::KeySize, Output = B1>,
55    H::KeySize: IsEqual<S::KeySize, Output = B1>,
56    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
57    // OutputSize must allow multiplication by U4
58    H::OutputSize: std::ops::Mul<cipher::consts::U4>,
59    // The product of OutputSize and U4 must be an array length
60    <H::OutputSize as std::ops::Mul<cipher::consts::U4>>::Output: ArrayLength<u8>,
61{
62    type KeySize = typenum::Prod<H::OutputSize, cipher::consts::U4>;
63}
64
65impl<H: KeySizeUser + OutputSizeUser, S: KeySizeUser + IvSizeUser, B: ArrayLength<u8>> IvSizeUser for Lioness<H, S, B>
66where
67    H::OutputSize: IsEqual<S::KeySize, Output = B1>,
68    H::KeySize: IsEqual<S::KeySize, Output = B1>,
69    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
70    // IvSize must allow multiplication by U2
71    S::IvSize: std::ops::Mul<cipher::consts::U2>,
72    // The product of IvSize with U2 must be an array length
73    <S::IvSize as std::ops::Mul<cipher::consts::U2>>::Output: ArrayLength<u8>,
74{
75    type IvSize = typenum::Prod<S::IvSize, cipher::consts::U2>;
76}
77
78impl<H: KeySizeUser + OutputSizeUser, S: KeySizeUser + IvSizeUser, B: ArrayLength<u8>> BlockSizeUser
79    for Lioness<H, S, B>
80where
81    H::OutputSize: IsEqual<S::KeySize, Output = B1>,
82    H::KeySize: IsEqual<S::KeySize, Output = B1>,
83    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
84    H::OutputSize: std::ops::Mul<cipher::consts::U4>,
85    S::IvSize: std::ops::Mul<cipher::consts::U2>,
86    <H::OutputSize as std::ops::Mul<cipher::consts::U4>>::Output: ArrayLength<u8>,
87    <S::IvSize as std::ops::Mul<cipher::consts::U2>>::Output: ArrayLength<u8>,
88{
89    type BlockSize = B;
90}
91
92impl<H: KeySizeUser + OutputSizeUser, S: KeySizeUser + IvSizeUser, B: ArrayLength<u8>> KeyIvInit for Lioness<H, S, B>
93where
94    H::OutputSize: IsEqual<S::KeySize, Output = B1>,
95    H::KeySize: IsEqual<S::KeySize, Output = B1>,
96    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
97    H::OutputSize: std::ops::Mul<cipher::consts::U4>,
98    S::IvSize: std::ops::Mul<cipher::consts::U2>,
99    <H::OutputSize as std::ops::Mul<cipher::consts::U4>>::Output: ArrayLength<u8>,
100    <S::IvSize as std::ops::Mul<cipher::consts::U2>>::Output: ArrayLength<u8>,
101{
102    fn new(key: &Key<Self>, iv: &Iv<Self>) -> Self {
103        let k = H::OutputSize::to_usize();
104        let i = S::IvSize::to_usize();
105        Self {
106            k1: GenericArray::clone_from_slice(&key[0..k]),
107            k2: GenericArray::clone_from_slice(&key[k..2 * k]),
108            k3: GenericArray::clone_from_slice(&key[2 * k..3 * k]),
109            k4: GenericArray::clone_from_slice(&key[3 * k..4 * k]),
110            iv1: GenericArray::clone_from_slice(&iv[0..i]),
111            iv2: GenericArray::clone_from_slice(&iv[i..2 * i]),
112            _phantom: Default::default(),
113        }
114    }
115}
116
117impl<H: KeySizeUser + OutputSizeUser + AlgorithmName, S: AlgorithmName + KeySizeUser + IvSizeUser, B: ArrayLength<u8>>
118    AlgorithmName for Lioness<H, S, B>
119where
120    H::OutputSize: IsEqual<<S as KeySizeUser>::KeySize, Output = B1>,
121    H::KeySize: IsEqual<S::KeySize, Output = B1>,
122    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1>,
123{
124    fn write_alg_name(f: &mut Formatter<'_>) -> std::fmt::Result {
125        f.write_str("Lioness<")?;
126        H::write_alg_name(f)?;
127        f.write_str(", ")?;
128        S::write_alg_name(f)?;
129        f.write_str(">")
130    }
131}
132
133impl<H: Digest + KeyInit, S: StreamCipher + KeyIvInit, B: ArrayLength<u8>> Lioness<H, S, B>
134where
135    H::OutputSize: IsEqual<<S as KeySizeUser>::KeySize, Output = B1>,
136    H::KeySize: IsEqual<S::KeySize, Output = B1>,
137    // BlockSize must be greater than KeySize of the stream cipher, and they must be subtractable
138    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1> + Sub<<S as KeySizeUser>::KeySize>,
139    // The difference of BlockSize minus KeySize must be an array length
140    <B as Sub<<S as KeySizeUser>::KeySize>>::Output: ArrayLength<u8>,
141    H::OutputSize: std::ops::Mul<cipher::consts::U4>,
142    <S as IvSizeUser>::IvSize: std::ops::Mul<cipher::consts::U2>,
143    <H::OutputSize as std::ops::Mul<cipher::consts::U4>>::Output: ArrayLength<u8>,
144    <<S as IvSizeUser>::IvSize as std::ops::Mul<cipher::consts::U2>>::Output: ArrayLength<u8>,
145{
146    const K: usize = <S as KeySizeUser>::KeySize::USIZE;
147
148    /// Performs encryption of the given `block`.
149    pub fn encrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
150        // L' = L ^ K1
151        let mut left_prime =
152            GenericArray::<u8, <S as KeySizeUser>::KeySize>::clone_from_slice(&block.get_in()[0..Self::K]);
153        for i in 0..Self::K {
154            left_prime[i] ^= self.k1[i];
155        }
156
157        // R = R ^ S(L', IV1)
158        let r_cpy = GenericArray::<u8, Diff<B, <S as KeySizeUser>::KeySize>>::clone_from_slice(
159            &block.get_in()[Self::K..B::USIZE],
160        );
161        S::new(&left_prime, &self.iv1)
162            .apply_keystream_b2b(&r_cpy, &mut block.get_out()[Self::K..B::USIZE])
163            .expect("slices have always equal sizes");
164
165        // R' = H_K2(R)
166        let r_prime = <H as KeyInit>::new(&self.k2)
167            .chain_update(&block.get_out()[Self::K..B::USIZE])
168            .finalize();
169
170        // L = L ^ R'
171        for i in 0..Self::K {
172            block.get_out()[i] = block.get_in()[i] ^ r_prime[i];
173        }
174
175        // L' = L ^ K3
176        let mut left_prime =
177            GenericArray::<u8, <S as KeySizeUser>::KeySize>::clone_from_slice(&block.get_out()[0..Self::K]);
178        for i in 0..Self::K {
179            left_prime[i] ^= self.k3[i];
180        }
181        // R = R ^ S(L', IV2)
182        S::new(&left_prime, &self.iv2).apply_keystream(&mut block.get_out()[Self::K..B::USIZE]);
183
184        // R' = H_K4(R)
185        let r_prime = <H as KeyInit>::new(&self.k4)
186            .chain_update(&block.get_out()[Self::K..B::USIZE])
187            .finalize();
188
189        // L = L ^ R'
190        for i in 0..Self::K {
191            block.get_out()[i] ^= r_prime[i];
192        }
193    }
194
195    /// Performs decryption of the given `block`.
196    pub fn decrypt_block(&self, mut block: InOut<'_, '_, Block<Self>>) {
197        // R' = H(K4 || R)
198        let r_prime = <H as KeyInit>::new(&self.k4)
199            .chain_update(&block.get_in()[Self::K..B::USIZE])
200            .finalize();
201
202        // L = L ^ R'
203        for i in 0..Self::K {
204            block.get_out()[i] = block.get_in()[i] ^ r_prime[i];
205        }
206
207        // L' = L ^ K3
208        let mut left_prime =
209            GenericArray::<u8, <S as KeySizeUser>::KeySize>::clone_from_slice(&block.get_out()[0..Self::K]);
210        for i in 0..Self::K {
211            left_prime[i] ^= self.k3[i];
212        }
213
214        // R = R ^ S(L', IV2)
215        let r_cpy = GenericArray::<u8, Diff<B, <S as KeySizeUser>::KeySize>>::clone_from_slice(
216            &block.get_in()[Self::K..B::USIZE],
217        );
218        S::new(&left_prime, &self.iv2)
219            .apply_keystream_b2b(&r_cpy, &mut block.get_out()[Self::K..B::USIZE])
220            .expect("slices have always equal sizes");
221
222        // R' = H(K2 || R)
223        let r_prime = <H as KeyInit>::new(&self.k2)
224            .chain_update(&block.get_out()[Self::K..B::USIZE])
225            .finalize();
226
227        // L = L ^ R'
228        for i in 0..Self::K {
229            block.get_out()[i] ^= r_prime[i];
230        }
231
232        // L' = L ^ K1
233        let mut left_prime =
234            GenericArray::<u8, <S as KeySizeUser>::KeySize>::clone_from_slice(&block.get_out()[0..Self::K]);
235        for i in 0..Self::K {
236            left_prime[i] ^= self.k1[i];
237        }
238
239        // R = R ^ S(L', IV1)
240        S::new(&left_prime, &self.iv1).apply_keystream(&mut block.get_out()[Self::K..B::USIZE]);
241    }
242}
243
244impl<H: Digest + KeyInit, S: StreamCipher + KeyIvInit, B: ArrayLength<u8>> PRP for Lioness<H, S, B>
245where
246    H::OutputSize: IsEqual<<S as KeySizeUser>::KeySize, Output = B1>,
247    H::KeySize: IsEqual<S::KeySize, Output = B1>,
248    B: IsGreater<<S as KeySizeUser>::KeySize, Output = B1> + Sub<<S as KeySizeUser>::KeySize>,
249    <B as Sub<<S as KeySizeUser>::KeySize>>::Output: ArrayLength<u8>,
250    H::OutputSize: std::ops::Mul<cipher::consts::U4>,
251    <S as IvSizeUser>::IvSize: std::ops::Mul<cipher::consts::U2>,
252    <H::OutputSize as std::ops::Mul<cipher::consts::U4>>::Output: ArrayLength<u8>,
253    <<S as IvSizeUser>::IvSize as std::ops::Mul<cipher::consts::U2>>::Output: ArrayLength<u8>,
254{
255    fn forward(&self, data: &mut Block<Self>) {
256        self.encrypt_block(data.into());
257    }
258
259    fn inverse(&self, data: &mut Block<Self>) {
260        self.decrypt_block(data.into());
261    }
262}
263
264/// Type-alias for Lioness wide-block cipher instantiated using Blake3 cryptographic hash function and ChaCha20 stream
265/// cipher.
266pub type LionessBlake3ChaCha20<B> = Lioness<blake3::Hasher, chacha20::ChaCha20, B>;
267
268#[cfg(test)]
269mod tests {
270    use hex_literal::hex;
271    use typenum::{U33, U1024};
272
273    use super::*;
274
275    #[test]
276    fn lioness_sizes() {
277        assert_eq!(
278            <blake3::Hasher as OutputSizeUser>::output_size(),
279            chacha20::ChaCha20::key_size()
280        );
281
282        let key_sz = LionessBlake3ChaCha20::<U33>::key_size();
283        let iv_sz = LionessBlake3ChaCha20::<U33>::iv_size();
284        let block_sz = LionessBlake3ChaCha20::<U33>::block_size();
285
286        assert_eq!(key_sz, <blake3::Hasher as OutputSizeUser>::output_size() * 4);
287        assert_eq!(iv_sz, chacha20::ChaCha20::iv_size() * 2);
288        assert_eq!(block_sz, U33::USIZE);
289    }
290
291    #[test]
292    fn lioness_forward_inverse() {
293        let lioness = LionessBlake3ChaCha20::<U1024>::new(&Default::default(), &Default::default());
294
295        let mut data = GenericArray::<u8, U1024>::default();
296        let data_clone = data.clone();
297        assert_eq!(data, data_clone);
298
299        lioness.encrypt_block((&mut data).into());
300        assert_ne!(data, data_clone);
301
302        lioness.decrypt_block((&mut data).into());
303        assert_eq!(data, data_clone);
304    }
305
306    #[test]
307    fn lioness_forward_kat() {
308        let lioness = LionessBlake3ChaCha20::<U33>::new(&Default::default(), &Default::default());
309
310        let mut data = GenericArray::<u8, U33>::default();
311
312        lioness.encrypt_block((&mut data).into());
313        let ka = hex!("36690b60686f3c997a7bfb3808aa18a1b5808b750587ed04a01ebd836dd3ea97b4");
314        assert_eq!(data.as_slice(), &ka);
315    }
316
317    #[test]
318    fn lioness_inverse_kat() {
319        let lioness = LionessBlake3ChaCha20::<U33>::new(&Default::default(), &Default::default());
320
321        let mut data = GenericArray::<u8, U33>::default();
322
323        lioness.decrypt_block((&mut data).into());
324        let ka = hex!("7857b5bb58995ac8c59eff412dad35af72a7d1e1ff1caba132aef382b15789a6cb");
325        assert_eq!(data.as_slice(), &ka);
326    }
327
328    #[test]
329    fn lioness_forward_inverse_random() {
330        let (k, iv) = LionessBlake3ChaCha20::<U1024>::generate_key_iv(hopr_crypto_random::rng());
331        let lioness = LionessBlake3ChaCha20::<U1024>::new(&k, &iv);
332
333        let mut data = GenericArray::<u8, U1024>::default();
334        hopr_crypto_random::random_fill(&mut data);
335        let data_clone = data.clone();
336        assert_eq!(data, data_clone);
337
338        lioness.encrypt_block((&mut data).into());
339        assert_ne!(data, data_clone);
340
341        lioness.decrypt_block((&mut data).into());
342        assert_eq!(data, data_clone);
343    }
344
345    #[test]
346    fn lioness_forward_inverse_random_separate_buffers() {
347        let (k, iv) = LionessBlake3ChaCha20::<U1024>::generate_key_iv(hopr_crypto_random::rng());
348        let lioness = LionessBlake3ChaCha20::<U1024>::new(&k, &iv);
349
350        let mut data_in = GenericArray::<u8, U1024>::default();
351        let mut data_out = GenericArray::<u8, U1024>::default();
352        hopr_crypto_random::random_fill(&mut data_in);
353        let data_orig = data_in.clone();
354        assert_eq!(data_in, data_orig);
355
356        lioness.encrypt_block((&data_in, &mut data_out).into());
357        assert_ne!(data_out, data_orig);
358
359        let data_in = data_out;
360        let mut data_out = GenericArray::<u8, U1024>::default();
361        lioness.decrypt_block((&data_in, &mut data_out).into());
362        assert_eq!(data_out, data_orig);
363    }
364}