hopr_crypto_sphinx/
prp.rs

1use crate::derivation::generate_key_iv;
2use blake2::Blake2bMac;
3use digest::{FixedOutput, Mac};
4use hopr_crypto_types::errors::CryptoError::InvalidParameterSize;
5use hopr_crypto_types::primitives::{SecretKey, SimpleStreamCipher};
6use hopr_crypto_types::utils;
7use zeroize::ZeroizeOnDrop;
8
9// Module-specific constants
10const PRP_INTERMEDIATE_KEY_LENGTH: usize = 32;
11const PRP_INTERMEDIATE_IV_LENGTH: usize = 16;
12const PRP_KEY_LENGTH: usize = 4 * PRP_INTERMEDIATE_KEY_LENGTH;
13const PRP_IV_LENGTH: usize = 4 * PRP_INTERMEDIATE_IV_LENGTH;
14const HASH_KEY_PRP: &str = "HASH_KEY_PRP";
15
16// The minimum input length must be at least size of the key, which is XORed with plaintext/ciphertext
17pub const PRP_MIN_LENGTH: usize = PRP_INTERMEDIATE_KEY_LENGTH;
18
19/// Parameters for the Pseudo-Random Permutation (PRP) function
20/// This consists of IV and the raw secret key for use by the underlying cryptographic transformation.
21#[derive(ZeroizeOnDrop)]
22pub struct PRPParameters {
23    key: [u8; PRP_KEY_LENGTH],
24    iv: [u8; PRP_IV_LENGTH],
25}
26
27impl Default for PRPParameters {
28    fn default() -> Self {
29        Self {
30            key: [0u8; PRP_KEY_LENGTH],
31            iv: [0u8; PRP_IV_LENGTH],
32        }
33    }
34}
35
36impl PRPParameters {
37    /// Creates new parameters for the PRP by expanding the given
38    /// keying material into the secret key and IV for the underlying cryptographic transformation.
39    pub fn new(secret: &SecretKey) -> Self {
40        let mut ret = PRPParameters::default();
41        generate_key_iv(secret, HASH_KEY_PRP.as_bytes(), &mut ret.key, &mut ret.iv, false);
42        ret
43    }
44}
45
46/// Implementation of Pseudo-Random Permutation (PRP).
47/// Currently based on the Lioness wide-block cipher.
48#[derive(ZeroizeOnDrop)]
49pub struct PRP {
50    keys: [[u8; PRP_INTERMEDIATE_KEY_LENGTH]; 4],
51    ivs: [[u8; PRP_INTERMEDIATE_IV_LENGTH]; 4],
52}
53
54impl PRP {
55    /// Creates new instance of the PRP using the raw key and IV.
56    pub fn new(key: [u8; PRP_KEY_LENGTH], iv: [u8; PRP_IV_LENGTH]) -> Self {
57        Self {
58            keys: [
59                key[..PRP_INTERMEDIATE_KEY_LENGTH].try_into().unwrap(),
60                key[PRP_INTERMEDIATE_KEY_LENGTH..2 * PRP_INTERMEDIATE_KEY_LENGTH]
61                    .try_into()
62                    .unwrap(),
63                key[2 * PRP_INTERMEDIATE_KEY_LENGTH..3 * PRP_INTERMEDIATE_KEY_LENGTH]
64                    .try_into()
65                    .unwrap(),
66                key[3 * PRP_INTERMEDIATE_KEY_LENGTH..4 * PRP_INTERMEDIATE_KEY_LENGTH]
67                    .try_into()
68                    .unwrap(),
69            ],
70            ivs: [
71                // NOTE: ChaCha20 takes only 12 byte IV
72                iv[..PRP_INTERMEDIATE_IV_LENGTH].try_into().unwrap(),
73                iv[PRP_INTERMEDIATE_IV_LENGTH..2 * PRP_INTERMEDIATE_IV_LENGTH]
74                    .try_into()
75                    .unwrap(),
76                iv[2 * PRP_INTERMEDIATE_IV_LENGTH..3 * PRP_INTERMEDIATE_IV_LENGTH]
77                    .try_into()
78                    .unwrap(),
79                iv[3 * PRP_INTERMEDIATE_IV_LENGTH..4 * PRP_INTERMEDIATE_IV_LENGTH]
80                    .try_into()
81                    .unwrap(),
82            ],
83        }
84    }
85
86    /// Creates a new PRP instance using the given parameters
87    pub fn from_parameters(params: PRPParameters) -> Self {
88        Self::new(params.key, params.iv) // Parameter size checking taken care of by PRPParameters
89    }
90}
91
92impl PRP {
93    /// Applies forward permutation on the given plaintext and returns a new buffer
94    /// containing the result.
95    pub fn forward(&self, plaintext: &[u8]) -> hopr_crypto_types::errors::Result<Box<[u8]>> {
96        let mut out = Vec::from(plaintext);
97        self.forward_inplace(&mut out)?;
98        Ok(out.into_boxed_slice())
99    }
100
101    /// Applies forward permutation on the given plaintext and modifies the given buffer in-place.
102    pub fn forward_inplace(&self, plaintext: &mut [u8]) -> hopr_crypto_types::errors::Result<()> {
103        if plaintext.len() >= PRP_MIN_LENGTH {
104            Self::xor_keystream(plaintext, &self.keys[0], &self.ivs[0]);
105            Self::xor_hash(plaintext, &self.keys[1], &self.ivs[1]);
106            Self::xor_keystream(plaintext, &self.keys[2], &self.ivs[2]);
107            Self::xor_hash(plaintext, &self.keys[3], &self.ivs[3]);
108            Ok(())
109        } else {
110            Err(InvalidParameterSize {
111                name: "plaintext".into(),
112                expected: PRP_MIN_LENGTH,
113            })
114        }
115    }
116
117    /// Applies inverse permutation on the given plaintext and returns a new buffer
118    /// containing the result.
119    pub fn inverse(&self, ciphertext: &[u8]) -> hopr_crypto_types::errors::Result<Box<[u8]>> {
120        let mut out = Vec::from(ciphertext);
121        self.inverse_inplace(&mut out)?;
122        Ok(out.into_boxed_slice())
123    }
124
125    /// Applies inverse permutation on the given ciphertext and modifies the given buffer in-place.
126    pub fn inverse_inplace(&self, ciphertext: &mut [u8]) -> hopr_crypto_types::errors::Result<()> {
127        if ciphertext.len() >= PRP_MIN_LENGTH {
128            Self::xor_hash(ciphertext, &self.keys[3], &self.ivs[3]);
129            Self::xor_keystream(ciphertext, &self.keys[2], &self.ivs[2]);
130            Self::xor_hash(ciphertext, &self.keys[1], &self.ivs[1]);
131            Self::xor_keystream(ciphertext, &self.keys[0], &self.ivs[0]);
132            Ok(())
133        } else {
134            Err(InvalidParameterSize {
135                name: "ciphertext".into(),
136                expected: PRP_MIN_LENGTH,
137            })
138        }
139    }
140
141    // Internal helper functions
142
143    fn xor_hash(data: &mut [u8], key: &[u8], iv: &[u8]) {
144        let mut blake = Blake2bMac::<typenum::U32>::new_with_salt_and_personal(key, iv, &[])
145            .expect("invalid intermediate key or iv size"); // should not happen
146        blake.update(&data[PRP_MIN_LENGTH..]);
147
148        utils::xor_inplace(data, &blake.finalize_fixed());
149    }
150
151    fn xor_keystream(data: &mut [u8], key: &[u8], iv: &[u8]) {
152        let mut key_cpy = Vec::from(key);
153        utils::xor_inplace(key_cpy.as_mut_slice(), &data[0..PRP_MIN_LENGTH]);
154
155        let iv_cpy = &iv[4..iv.len()];
156
157        let mut cipher = SimpleStreamCipher::new(
158            key_cpy.try_into().expect("invalid keystream key size"),
159            iv_cpy.try_into().expect("invalid keystream iv size"),
160        );
161
162        let block_counter = u32::from_le_bytes(iv[0..4].try_into().unwrap());
163        cipher.set_block_counter(block_counter);
164
165        cipher.apply(&mut data[PRP_MIN_LENGTH..]);
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172    use hex_literal::hex;
173    use hopr_crypto_random::random_bytes;
174
175    #[test]
176    fn test_prp_fixed() {
177        let prp = PRP::new([0u8; 4 * 32], [0u8; 4 * 16]);
178
179        let data = [1u8; 278];
180
181        let ct = prp.forward(&data).unwrap();
182        let pt = prp.inverse(&ct).unwrap();
183
184        assert_eq!(&data, pt.as_ref());
185    }
186
187    #[test]
188    fn test_prp_random() {
189        let prp = PRP::new(random_bytes(), random_bytes());
190        let data: [u8; 278] = random_bytes();
191
192        let ct = prp.forward(&data).unwrap();
193        let pt = prp.inverse(&ct).unwrap();
194
195        assert_ne!(&data, ct.as_ref(), "ciphertext must be different than plaintext");
196        assert_eq!(&data, pt.as_ref(), "plaintexts must be the same");
197    }
198
199    #[test]
200    fn test_prp_random_inplace() {
201        let prp = PRP::new(random_bytes(), random_bytes());
202        let mut data: [u8; 278] = random_bytes();
203        let data_old = data;
204
205        prp.forward_inplace(&mut data).unwrap();
206        assert_ne!(&data_old, data.as_ref(), "buffer must be encrypted in-place");
207
208        prp.inverse_inplace(&mut data).unwrap();
209        assert_eq!(&data_old, data.as_ref(), "buffer must be decrypted in-place");
210    }
211
212    #[test]
213    fn test_prp_parameters() {
214        let expected_key = hex!("a9c6632c9f76e5e4dd03203196932350a47562f816cebb810c64287ff68586f35cb715a26e268fc3ce68680e16767581de4e2cb3944c563d1f1a0cc077f3e788a12f31ae07111d77a876a66de5bdd6176bdaa2e07d1cb2e36e428afafdebb2109f70ce8422c8821233053bdd5871523ffb108f1e0f86809999a99d407590df25");
215        let expected_iv = hex!("a59991716be504b26471dea53d688c4bab8e910328e54ebb6ebf07b49e6d12eacfc56e0935ba2300559b43ede25aa09eee7e8a2deea5f0bdaee2e859834edd38");
216
217        let params = PRPParameters::new(&SecretKey::default());
218
219        assert_eq!(expected_key, params.key);
220        assert_eq!(expected_iv, params.iv)
221    }
222
223    #[test]
224    fn test_prp_ciphertext_from_params() {
225        let params = PRPParameters::new(&SecretKey::default());
226
227        let expected_key = hex!("a9c6632c9f76e5e4dd03203196932350a47562f816cebb810c64287ff68586f35cb715a26e268fc3ce68680e16767581de4e2cb3944c563d1f1a0cc077f3e788a12f31ae07111d77a876a66de5bdd6176bdaa2e07d1cb2e36e428afafdebb2109f70ce8422c8821233053bdd5871523ffb108f1e0f86809999a99d407590df25");
228        let expected_iv = hex!("a59991716be504b26471dea53d688c4bab8e910328e54ebb6ebf07b49e6d12eacfc56e0935ba2300559b43ede25aa09eee7e8a2deea5f0bdaee2e859834edd38");
229        assert_eq!(expected_key, params.key);
230        assert_eq!(expected_iv, params.iv);
231
232        let prp = PRP::from_parameters(params);
233
234        let pt = [0u8; 100];
235        let ct = prp.forward(&pt).unwrap();
236
237        assert_eq!([0u8; 100], pt, "plain text must not change for in-place operation");
238        assert_ne!(&pt, ct.as_ref(), "plain text must be different from ciphertext");
239    }
240}