hopr_crypto_sphinx/
prp.rs1use 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
9const 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
16pub const PRP_MIN_LENGTH: usize = PRP_INTERMEDIATE_KEY_LENGTH;
18
19#[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 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#[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 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 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 pub fn from_parameters(params: PRPParameters) -> Self {
88 Self::new(params.key, params.iv) }
90}
91
92impl PRP {
93 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 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 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 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 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"); 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}