sphinx_packet/payload/
mod.rs

1// Copyright 2020 Nym Technologies SA
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::constants::SECURITY_PARAMETER;
16use crate::payload::key::{PayloadKey, SphinxPayloadKey};
17use crate::{Error, ErrorKind, Result};
18use blake2::VarBlake2b;
19use chacha::ChaCha; // we might want to swap this one with a different implementation
20use lioness::Lioness;
21use std::borrow::Borrow;
22
23pub mod key;
24
25// payload consists of security parameter long zero-padding, plaintext and '1' byte to indicate start of padding
26// (it can optionally be followed by zero-padding
27pub const PAYLOAD_OVERHEAD_SIZE: usize = SECURITY_PARAMETER + 1;
28
29// TODO: question: is padding to some pre-defined length a sphinx-specific thing or rather
30// something for our particular use case?
31#[derive(Debug)]
32#[cfg_attr(test, derive(PartialEq))]
33pub struct Payload(Vec<u8>);
34
35// is_empty does not make sense in this context, as you can't construct an empty Payload
36#[allow(clippy::len_without_is_empty)]
37impl Payload {
38    /// Tries to encapsulate provided plaintext message inside a sphinx payload adding
39    /// as many layers of encryption as there are keys provided.
40    /// Note that the encryption layers are going to be added in *reverse* order!
41    pub fn encapsulate_message<K>(
42        plaintext_message: &[u8],
43        payload_keys: &[K],
44        payload_size: usize,
45    ) -> Result<Self>
46    where
47        K: for<'a> SphinxPayloadKey<'a>,
48    {
49        Self::validate_parameters(payload_size, plaintext_message.len())?;
50        let mut payload = Self::set_final_payload(plaintext_message, payload_size);
51
52        // remember that we need to reverse the order of encryption
53        for payload_key in payload_keys.iter().rev() {
54            payload = payload.add_encryption_layer(payload_key.payload_key())?;
55        }
56
57        Ok(payload)
58    }
59
60    /// Ensures the desires payload_size is longer than the required overhead as well
61    /// as the blocksize of lioness encryption.
62    /// It also checks if the plaintext can fit in the specified payload [size].
63    fn validate_parameters(payload_size: usize, plaintext_len: usize) -> Result<()> {
64        if payload_size < PAYLOAD_OVERHEAD_SIZE {
65            return Err(Error::new(
66                ErrorKind::InvalidPayload,
67                "specified payload_size is smaller than the required overhead",
68            ));
69        // lioness blocksize is 32 bytes (in this implementation)
70        // Technically this check shouldn't happen if you're not going to add any
71        // encryption layers to the payload, but then why are you even using sphinx?
72        } else if payload_size < lioness::DIGEST_RESULT_SIZE {
73            return Err(Error::new(
74                ErrorKind::InvalidPayload,
75                "specified payload_size is smaller lioness block size",
76            ));
77        }
78
79        let maximum_plaintext_length = payload_size - PAYLOAD_OVERHEAD_SIZE;
80        if plaintext_len > maximum_plaintext_length {
81            return Err(Error::new(
82                ErrorKind::InvalidPayload,
83                format!(
84                    "too long message provided. Message was: {}B long, maximum_plaintext_length is: {}B",
85                    plaintext_len,
86                    maximum_plaintext_length
87                ),
88            ));
89        }
90        Ok(())
91    }
92
93    /// Attaches leading and trailing paddings of correct lengths to the provided plaintext message.
94    /// Note: this function should only ever be called in [`encapsulate_message`] after
95    /// [`validate_parameters`] was performed.
96    fn set_final_payload(plaintext_message: &[u8], payload_size: usize) -> Self {
97        let final_payload: Vec<u8> = std::iter::repeat_n(0u8, SECURITY_PARAMETER) // start with zero-padding
98            .chain(plaintext_message.iter().copied()) // put the plaintext
99            .chain(std::iter::once(1)) // add single 1 byte to indicate start of padding
100            .chain(std::iter::repeat(0u8)) // and fill everything else with zeroes
101            .take(payload_size) // take however much we need (remember, iterators are lazy)
102            .collect();
103
104        Payload(final_payload)
105    }
106
107    /// Tries to add an additional layer of encryption onto self.
108    fn add_encryption_layer<P: Borrow<PayloadKey>>(mut self, payload_key: P) -> Result<Self> {
109        let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(payload_key.borrow());
110
111        if let Err(err) = lioness_cipher.encrypt(&mut self.0) {
112            return Err(Error::new(
113                ErrorKind::InvalidPayload,
114                format!("error while encrypting payload - {}", err),
115            ));
116        };
117        Ok(self)
118    }
119
120    /// Tries to remove single layer of encryption from self.
121    pub fn unwrap<P: Borrow<PayloadKey>>(mut self, payload_key: P) -> Result<Self> {
122        let lioness_cipher = Lioness::<VarBlake2b, ChaCha>::new_raw(payload_key.borrow());
123
124        if let Err(err) = lioness_cipher.decrypt(&mut self.0) {
125            return Err(Error::new(
126                ErrorKind::InvalidPayload,
127                format!("error while unwrapping payload - {}", err),
128            ));
129        };
130        Ok(self)
131    }
132
133    // attempt to find the index of the element indicating starting of the padding AFTER the initial
134    // SECURITY_PARAMETER 0s got ignored
135    // NOTE: this method must only be called after ensuring the internal vector is longer than `PAYLOAD_OVERHEAD_SIZE`
136    fn find_start_of_padding(&self) -> Result<usize> {
137        let padded_plaintext = &self.0[SECURITY_PARAMETER..];
138        padded_plaintext
139            .iter()
140            .rposition(|b| *b == 1)
141            .ok_or(Error::new(
142                ErrorKind::InvalidPayload,
143                "malformed payload - invalid trailing padding",
144            ))
145    }
146
147    /// After calling [`unwrap`] required number of times with correct `payload_keys`, tries to parse
148    /// the resultant payload content into original encapsulated plaintext message.
149    pub fn recover_plaintext(self) -> Result<Vec<u8>> {
150        if self.len() < PAYLOAD_OVERHEAD_SIZE {
151            return Err(Error::new(
152                ErrorKind::InvalidPayload,
153                "malformed payload - no leading zero padding present",
154            ));
155        }
156
157        // assuming our payload is fully decrypted it has the following structure:
158        // 00000.... (SECURITY_PARAMETER length)
159        // plaintext (variable)
160        // 1 (single 1 byte)
161        // 0000 ... to pad to specified `payload_size`
162
163        // In order to recover the plaintext we need to ignore first SECURITY_PARAMETER bytes
164        // Then remove all tailing zeroes until first 1
165        // and finally remove the first 1. The result should be our plaintext.
166        // However, we must check if first SECURITY_PARAMETER bytes are actually 0
167        if !self.0.iter().take(SECURITY_PARAMETER).all(|b| *b == 0) {
168            return Err(Error::new(
169                ErrorKind::InvalidPayload,
170                "malformed payload - no leading zero padding present",
171            ));
172        }
173
174        let padding_start = self.find_start_of_padding()?;
175        // take only bytes until the start of the padding (but not including it)
176        // and furthermore, remember to skip the initial 0s
177
178        Ok(self
179            .into_inner()
180            .into_iter()
181            .skip(SECURITY_PARAMETER)
182            .take(padding_start)
183            .collect())
184    }
185
186    fn into_inner(self) -> Vec<u8> {
187        self.0
188    }
189
190    fn inner(&self) -> &[u8] {
191        &self.0
192    }
193
194    pub fn len(&self) -> usize {
195        self.0.len()
196    }
197
198    /// View this `Payload` as slice of bytes.
199    pub fn as_bytes(&self) -> &[u8] {
200        self.inner()
201    }
202
203    /// Convert this `Payload` as a vector of bytes.
204    pub fn into_bytes(self) -> Vec<u8> {
205        self.into_inner()
206    }
207
208    /// Tries to recover `Payload` from a slice of bytes.
209    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
210        // with payloads being dynamic in size, the only thing we can do
211        // is to check if it at least is longer than the minimum length
212        if bytes.len() < PAYLOAD_OVERHEAD_SIZE {
213            return Err(Error::new(
214                ErrorKind::InvalidPayload,
215                "too short payload provided",
216            ));
217        }
218
219        Ok(Payload(bytes.to_vec()))
220    }
221}
222
223#[cfg(test)]
224mod building_payload_from_bytes {
225    use super::*;
226
227    #[test]
228    fn from_bytes_returns_error_if_bytes_are_too_short() {
229        let bytes = [0u8; 1].to_vec();
230        let expected = ErrorKind::InvalidPayload;
231        match Payload::from_bytes(&bytes) {
232            Err(err) => assert_eq!(expected, err.kind()),
233            _ => panic!("Should have returned an error when packet bytes too short"),
234        };
235    }
236}
237
238#[cfg(test)]
239mod parameter_verification {
240    use super::*;
241
242    #[test]
243    fn it_returns_an_error_if_payload_size_is_smaller_than_the_overhead() {
244        assert!(Payload::validate_parameters(PAYLOAD_OVERHEAD_SIZE - 1, 16).is_err());
245    }
246
247    #[test]
248    fn it_returns_an_error_if_payload_size_is_smaller_than_the_lioness_blocklen() {
249        assert!(Payload::validate_parameters(lioness::DIGEST_RESULT_SIZE - 1, 16).is_err());
250    }
251
252    #[test]
253    fn it_returns_an_error_if_message_is_longer_than_maximum_allowed_length() {
254        let payload_length = 100;
255        let max_allowed_length = payload_length - PAYLOAD_OVERHEAD_SIZE;
256        assert!(Payload::validate_parameters(payload_length, max_allowed_length + 1).is_err());
257    }
258}
259
260#[cfg(test)]
261mod final_payload_setting {
262    use super::*;
263
264    #[test]
265    fn adds_correct_padding() {
266        let plaintext_lengths = vec![0, 1, 16, 128, 4096];
267        for plaintext_length in plaintext_lengths {
268            // ensure payload always has correct length, because we're not testing for that
269            let payload_size = plaintext_length + lioness::DIGEST_RESULT_SIZE;
270            let final_payload =
271                Payload::set_final_payload(&vec![42u8; plaintext_length], payload_size);
272            let final_payload_inner = final_payload.into_inner();
273
274            // first SECURITY_PARAMETER bytes have to be 0
275            assert!(final_payload_inner
276                .iter()
277                .take(SECURITY_PARAMETER)
278                .all(|&b| b == 0));
279            // then the actual message should follow
280            assert!(final_payload_inner
281                .iter()
282                .skip(SECURITY_PARAMETER)
283                .take(plaintext_length)
284                .all(|&b| b == 42));
285            // single one
286            assert_eq!(
287                final_payload_inner[SECURITY_PARAMETER + plaintext_length],
288                1
289            );
290            // and the rest should be 0 padding
291            assert!(final_payload_inner
292                .iter()
293                .skip(SECURITY_PARAMETER + plaintext_length + 1)
294                .all(|&b| b == 0))
295        }
296    }
297}
298
299#[cfg(test)]
300mod test_encapsulating_payload {
301    use super::*;
302    use crate::constants::PAYLOAD_KEY_SIZE;
303
304    #[test]
305    fn works_with_single_encryption_layer() {
306        let message = vec![1u8, 16];
307        let payload_size = 512;
308        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
309
310        assert!(Payload::encapsulate_message(&message, &[payload_key_1], payload_size).is_ok())
311    }
312
313    #[test]
314    fn works_with_five_encryption_layers() {
315        let message = vec![1u8, 16];
316        let payload_size = 512;
317        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
318        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
319        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
320        let payload_key_4 = [6u8; PAYLOAD_KEY_SIZE];
321        let payload_key_5 = [7u8; PAYLOAD_KEY_SIZE];
322
323        assert!(Payload::encapsulate_message(
324            &message,
325            &[
326                payload_key_1,
327                payload_key_2,
328                payload_key_3,
329                payload_key_4,
330                payload_key_5
331            ],
332            payload_size
333        )
334        .is_ok())
335    }
336}
337
338#[cfg(test)]
339mod test_unwrapping_payload {
340    use super::*;
341    use crate::constants::{PAYLOAD_KEY_SIZE, SECURITY_PARAMETER};
342    use crate::packet::builder::DEFAULT_PAYLOAD_SIZE;
343
344    #[test]
345    fn unwrapping_results_in_original_payload_plaintext() {
346        let message = vec![42u8; 16];
347        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
348        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
349        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
350        let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
351
352        let encrypted_payload =
353            Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
354
355        let unwrapped_payload = payload_keys
356            .iter()
357            .fold(encrypted_payload, |current_layer, payload_key| {
358                current_layer.unwrap(payload_key).unwrap()
359            });
360
361        let zero_bytes = vec![0u8; SECURITY_PARAMETER];
362        let additional_padding =
363            vec![0u8; DEFAULT_PAYLOAD_SIZE - PAYLOAD_OVERHEAD_SIZE - message.len()];
364        let expected_payload = [zero_bytes, message, vec![1], additional_padding].concat();
365        assert_eq!(expected_payload, unwrapped_payload.into_inner());
366    }
367}
368
369#[cfg(test)]
370mod plaintext_recovery {
371    use super::*;
372    use crate::constants::PAYLOAD_KEY_SIZE;
373    use crate::packet::builder::DEFAULT_PAYLOAD_SIZE;
374
375    #[test]
376    fn it_is_possible_to_recover_plaintext_from_valid_payload() {
377        let message = vec![42u8; 160];
378
379        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
380        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
381        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
382        let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
383
384        let encrypted_payload =
385            Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
386
387        let unwrapped_payload = payload_keys
388            .iter()
389            .fold(encrypted_payload, |current_layer, payload_key| {
390                current_layer.unwrap(payload_key).unwrap()
391            });
392
393        let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
394
395        assert_eq!(message, recovered_plaintext);
396    }
397
398    // tests for correct padding detection
399    #[test]
400    fn it_is_possible_to_recover_plaintext_even_if_is_just_ones() {
401        let message = vec![1u8; 160];
402
403        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
404        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
405        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
406        let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
407
408        let encrypted_payload =
409            Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
410
411        let unwrapped_payload = payload_keys
412            .iter()
413            .fold(encrypted_payload, |current_layer, payload_key| {
414                current_layer.unwrap(payload_key).unwrap()
415            });
416
417        let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
418
419        assert_eq!(message, recovered_plaintext);
420    }
421
422    // tests for correct padding detection
423    #[test]
424    fn it_is_possible_to_recover_plaintext_even_if_is_just_zeroes() {
425        let message = vec![0u8; 160];
426
427        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
428        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
429        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
430        let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
431
432        let encrypted_payload =
433            Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
434
435        let unwrapped_payload = payload_keys
436            .iter()
437            .fold(encrypted_payload, |current_layer, payload_key| {
438                current_layer.unwrap(payload_key).unwrap()
439            });
440
441        let recovered_plaintext = unwrapped_payload.recover_plaintext().unwrap();
442
443        assert_eq!(message, recovered_plaintext);
444    }
445
446    #[test]
447    fn it_fails_to_recover_plaintext_from_invalid_payload() {
448        let message = vec![42u8; 160];
449
450        let payload_key_1 = [3u8; PAYLOAD_KEY_SIZE];
451        let payload_key_2 = [4u8; PAYLOAD_KEY_SIZE];
452        let payload_key_3 = [5u8; PAYLOAD_KEY_SIZE];
453        let payload_keys = [payload_key_1, payload_key_2, payload_key_3];
454
455        let encrypted_payload =
456            Payload::encapsulate_message(&message, &payload_keys, DEFAULT_PAYLOAD_SIZE).unwrap();
457
458        let unwrapped_payload = payload_keys
459            .iter()
460            .skip(1) // 'forget' about one key to obtain invalid decryption
461            .fold(encrypted_payload, |current_layer, payload_key| {
462                current_layer.unwrap(payload_key).unwrap()
463            });
464
465        assert!(unwrapped_payload.recover_plaintext().is_err())
466    }
467
468    #[test]
469    fn it_fails_to_recover_plaintext_from_incorrectly_constructed_payload() {
470        let zero_payload = Payload(vec![0u8; DEFAULT_PAYLOAD_SIZE]);
471
472        assert!(zero_payload.recover_plaintext().is_err());
473    }
474}