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