f4jumble/lib.rs
1//! This crate provides a mechanism for "jumbling" byte slices in a reversible way.
2//!
3//! Many byte encodings such as [Base64] and [Bech32] do not have "cascading" behaviour:
4//! changing an input byte at one position has no effect on the encoding of bytes at
5//! distant positions. This can be a problem if users generally check the correctness of
6//! encoded strings by eye, as they will tend to only check the first and/or last few
7//! characters of the encoded string. In some situations (for example, a hardware device
8//! displaying on its screen an encoded string provided by an untrusted computer), it is
9//! potentially feasible for an adversary to change some internal portion of the encoded
10//! string in a way that is beneficial to them, without the user noticing.
11//!
12//! [Base64]: https://en.wikipedia.org/wiki/Base64
13//! [Bech32]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32
14//!
15//! The function F4Jumble (and its inverse function, F4Jumble⁻¹) are length-preserving
16//! transformations can be used to trivially introduce cascading behaviour to existing
17//! encodings:
18//! - Prepare the raw `message` bytes as usual.
19//! - Pass `message` through [`f4jumble`] or [`f4jumble_mut`] to obtain the jumbled bytes.
20//! - Encode the jumbled bytes with the encoding scheme.
21//!
22//! Changing any byte of `message` will result in a completely different sequence of
23//! jumbled bytes. Specifically, F4Jumble uses an unkeyed 4-round Feistel construction to
24//! approximate a random permutation.
25//!
26//! 
27//!
28//! ## Efficiency
29//!
30//! The cost is dominated by 4 BLAKE2b compressions for message lengths up to 128 bytes.
31//! For longer messages, the cost increases to 6 BLAKE2b compressions for 128 < lₘ ≤ 192,
32//! and 10 BLAKE2b compressions for 192 < lₘ ≤ 256, for example. The maximum cost for
33//! which the algorithm is defined would be 196608 BLAKE2b compressions at lₘ = 4194368.
34//!
35//! The implementations in this crate require memory of roughly lₘ bytes plus the size of
36//! a BLAKE2b hash state. It is possible to reduce this by (for example, with F4Jumble⁻¹)
37//! streaming the `d` part of the jumbled encoding three times from a less
38//! memory-constrained device. It is essential that the streamed value of `d` is the same
39//! on each pass, which can be verified using a Message Authentication Code (with key held
40//! only by the Consumer) or collision-resistant hash function. After the first pass of
41//! `d`, the implementation is able to compute `y`; after the second pass it is able to
42//! compute `a`; and the third allows it to compute and incrementally parse `b`. The
43//! maximum memory usage during this process would be 128 bytes plus two BLAKE2b hash
44//! states.
45
46#![no_std]
47#![cfg_attr(docsrs, feature(doc_cfg))]
48
49use blake2b_simd::{Params as Blake2bParams, OUTBYTES};
50
51use core::cmp::min;
52use core::fmt;
53use core::ops::RangeInclusive;
54use core::result::Result;
55
56#[cfg(feature = "alloc")]
57extern crate alloc;
58
59#[cfg(feature = "std")]
60#[macro_use]
61extern crate std;
62
63#[cfg(feature = "alloc")]
64use alloc::vec::Vec;
65
66#[cfg(test)]
67mod test_vectors;
68#[cfg(all(test, feature = "std"))]
69mod test_vectors_long;
70
71/// Length of F4Jumbled message must lie in the range VALID_LENGTH.
72///
73/// VALID_LENGTH = 48..=4194368
74pub const VALID_LENGTH: RangeInclusive<usize> = 48..=4194368;
75
76/// Errors produced by F4Jumble.
77#[derive(Debug)]
78pub enum Error {
79 /// Value error indicating that length of F4Jumbled message does not
80 /// lie in the range [`VALID_LENGTH`].
81 InvalidLength,
82}
83
84impl fmt::Display for Error {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Self::InvalidLength => write!(
88 f,
89 "Message length must be in interval ({}..={})",
90 *VALID_LENGTH.start(),
91 *VALID_LENGTH.end()
92 ),
93 }
94 }
95}
96
97#[cfg(feature = "std")]
98impl std::error::Error for Error {}
99
100macro_rules! H_PERS {
101 ( $i:expr ) => {
102 [
103 85, 65, 95, 70, 52, 74, 117, 109, 98, 108, 101, 95, 72, $i, 0, 0,
104 ]
105 };
106}
107
108macro_rules! G_PERS {
109 ( $i:expr, $j:expr ) => {
110 [
111 85,
112 65,
113 95,
114 70,
115 52,
116 74,
117 117,
118 109,
119 98,
120 108,
121 101,
122 95,
123 71,
124 $i,
125 ($j & 0xFF) as u8,
126 ($j >> 8) as u8,
127 ]
128 };
129}
130
131struct State<'a> {
132 left: &'a mut [u8],
133 right: &'a mut [u8],
134}
135
136impl<'a> State<'a> {
137 fn new(message: &'a mut [u8]) -> Self {
138 let left_length = min(OUTBYTES, message.len() / 2);
139 let (left, right) = message.split_at_mut(left_length);
140 State { left, right }
141 }
142
143 fn h_round(&mut self, i: u8) {
144 let hash = Blake2bParams::new()
145 .hash_length(self.left.len())
146 .personal(&H_PERS!(i))
147 .hash(self.right);
148 xor(self.left, hash.as_bytes())
149 }
150
151 fn g_round(&mut self, i: u8) {
152 for j in 0..ceildiv(self.right.len(), OUTBYTES) {
153 let hash = Blake2bParams::new()
154 .hash_length(OUTBYTES)
155 .personal(&G_PERS!(i, j as u16))
156 .hash(self.left);
157 xor(&mut self.right[j * OUTBYTES..], hash.as_bytes());
158 }
159 }
160
161 fn apply_f4jumble(&mut self) {
162 self.g_round(0);
163 self.h_round(0);
164 self.g_round(1);
165 self.h_round(1);
166 }
167
168 fn apply_f4jumble_inv(&mut self) {
169 self.h_round(1);
170 self.g_round(1);
171 self.h_round(0);
172 self.g_round(0);
173 }
174}
175
176/// XORs bytes of the `source` to bytes of the `target`.
177///
178/// This method operates over the first `min(source.len(), target.len())` bytes.
179fn xor(target: &mut [u8], source: &[u8]) {
180 for (source, target) in source.iter().zip(target.iter_mut()) {
181 *target ^= source;
182 }
183}
184
185fn ceildiv(num: usize, den: usize) -> usize {
186 (num + den - 1) / den
187}
188
189/// Encodes the given message in-place using F4Jumble.
190///
191/// Returns an error if the message is an invalid length. `message` will be unmodified in
192/// this case.
193///
194/// # Examples
195///
196/// ```
197/// let mut message_a = *b"The package from Alice arrives tomorrow morning.";
198/// f4jumble::f4jumble_mut(&mut message_a[..]).unwrap();
199/// assert_eq!(
200/// hex::encode(message_a),
201/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27",
202/// );
203///
204/// let mut message_b = *b"The package from Sarah arrives tomorrow morning.";
205/// f4jumble::f4jumble_mut(&mut message_b[..]).unwrap();
206/// assert_eq!(
207/// hex::encode(message_b),
208/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8",
209/// );
210/// ```
211pub fn f4jumble_mut(message: &mut [u8]) -> Result<(), Error> {
212 if VALID_LENGTH.contains(&message.len()) {
213 State::new(message).apply_f4jumble();
214 Ok(())
215 } else {
216 Err(Error::InvalidLength)
217 }
218}
219
220/// Decodes the given message in-place using F4Jumble⁻¹.
221///
222/// Returns an error if the message is an invalid length. `message` will be unmodified in
223/// this case.
224///
225/// # Examples
226///
227/// ```
228/// let mut message_a = hex::decode(
229/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27")
230/// .unwrap();
231/// f4jumble::f4jumble_inv_mut(&mut message_a).unwrap();
232/// assert_eq!(message_a, b"The package from Alice arrives tomorrow morning.");
233///
234/// let mut message_b = hex::decode(
235/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8")
236/// .unwrap();
237/// f4jumble::f4jumble_inv_mut(&mut message_b).unwrap();
238/// assert_eq!(message_b, b"The package from Sarah arrives tomorrow morning.");
239/// ```
240pub fn f4jumble_inv_mut(message: &mut [u8]) -> Result<(), Error> {
241 if VALID_LENGTH.contains(&message.len()) {
242 State::new(message).apply_f4jumble_inv();
243 Ok(())
244 } else {
245 Err(Error::InvalidLength)
246 }
247}
248
249/// Encodes the given message using F4Jumble, and returns the encoded message as a vector
250/// of bytes.
251///
252/// Returns an error if the message is an invalid length.
253///
254/// # Examples
255///
256/// ```
257/// let message_a = b"The package from Alice arrives tomorrow morning.";
258/// let encoded_a = f4jumble::f4jumble(message_a).unwrap();
259/// assert_eq!(
260/// hex::encode(encoded_a),
261/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27",
262/// );
263///
264/// let message_b = b"The package from Sarah arrives tomorrow morning.";
265/// let encoded_b = f4jumble::f4jumble(message_b).unwrap();
266/// assert_eq!(
267/// hex::encode(encoded_b),
268/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8",
269/// );
270/// ```
271#[cfg(feature = "alloc")]
272#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
273pub fn f4jumble(message: &[u8]) -> Result<Vec<u8>, Error> {
274 let mut result = message.to_vec();
275 f4jumble_mut(&mut result).map(|()| result)
276}
277
278/// Decodes the given message using F4Jumble⁻¹, and returns the decoded message as a
279/// vector of bytes.
280///
281/// Returns an error if the message is an invalid length.
282///
283/// # Examples
284///
285/// ```
286/// let encoded_a = hex::decode(
287/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27")
288/// .unwrap();
289/// let message_a = f4jumble::f4jumble_inv(&encoded_a).unwrap();
290/// assert_eq!(message_a, b"The package from Alice arrives tomorrow morning.");
291///
292/// let encoded_b = hex::decode(
293/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8")
294/// .unwrap();
295/// let message_b = f4jumble::f4jumble_inv(&encoded_b).unwrap();
296/// assert_eq!(message_b, b"The package from Sarah arrives tomorrow morning.");
297/// ```
298#[cfg(feature = "alloc")]
299#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
300pub fn f4jumble_inv(message: &[u8]) -> Result<Vec<u8>, Error> {
301 let mut result = message.to_vec();
302 f4jumble_inv_mut(&mut result).map(|()| result)
303}
304
305#[cfg(test)]
306mod common_tests {
307 use super::{f4jumble_inv_mut, f4jumble_mut, test_vectors};
308
309 #[test]
310 fn h_pers() {
311 assert_eq!(&H_PERS!(7), b"UA_F4Jumble_H\x07\x00\x00");
312 }
313
314 #[test]
315 fn g_pers() {
316 assert_eq!(&G_PERS!(7, 13), b"UA_F4Jumble_G\x07\x0d\x00");
317 assert_eq!(&G_PERS!(7, 65535), b"UA_F4Jumble_G\x07\xff\xff");
318 }
319
320 #[test]
321 fn f4jumble_check_vectors_mut() {
322 #[cfg(not(feature = "std"))]
323 let mut cache = [0u8; test_vectors::MAX_VECTOR_LENGTH];
324 #[cfg(feature = "std")]
325 let mut cache = vec![0u8; test_vectors::MAX_VECTOR_LENGTH];
326 for v in test_vectors::TEST_VECTORS {
327 let data = &mut cache[..v.normal.len()];
328 data.clone_from_slice(v.normal);
329 f4jumble_mut(data).unwrap();
330 assert_eq!(data, v.jumbled);
331 f4jumble_inv_mut(data).unwrap();
332 assert_eq!(data, v.normal);
333 }
334 }
335}
336
337#[cfg(feature = "std")]
338#[cfg(test)]
339mod std_tests {
340 use blake2b_simd::blake2b;
341 use proptest::collection::vec;
342 use proptest::prelude::*;
343 use std::format;
344 use std::vec::Vec;
345
346 use super::{f4jumble, f4jumble_inv, test_vectors, test_vectors_long, VALID_LENGTH};
347
348 proptest! {
349 #![proptest_config(ProptestConfig::with_cases(5))]
350
351 #[test]
352 fn f4jumble_roundtrip(msg in vec(any::<u8>(), VALID_LENGTH)) {
353 let jumbled = f4jumble(&msg).unwrap();
354 let jumbled_len = jumbled.len();
355 prop_assert_eq!(
356 msg.len(), jumbled_len,
357 "Jumbled length {} was not equal to message length {}",
358 jumbled_len, msg.len()
359 );
360
361 let unjumbled = f4jumble_inv(&jumbled).unwrap();
362 prop_assert_eq!(
363 jumbled_len, unjumbled.len(),
364 "Unjumbled length {} was not equal to jumbled length {}",
365 unjumbled.len(), jumbled_len
366 );
367
368 prop_assert_eq!(msg, unjumbled, "Unjumbled message did not match original message.");
369 }
370 }
371
372 #[test]
373 fn f4jumble_check_vectors() {
374 for v in test_vectors::TEST_VECTORS {
375 let jumbled = f4jumble(v.normal).unwrap();
376 assert_eq!(jumbled, v.jumbled);
377 let unjumbled = f4jumble_inv(v.jumbled).unwrap();
378 assert_eq!(unjumbled, v.normal);
379 }
380 }
381
382 #[test]
383 fn f4jumble_check_vectors_long() {
384 for v in test_vectors_long::TEST_VECTORS {
385 let normal: Vec<u8> = (0..v.length).map(|i| i as u8).collect();
386 let jumbled = f4jumble(&normal).unwrap();
387 assert_eq!(blake2b(&jumbled).as_bytes(), v.jumbled_hash);
388 let unjumbled = f4jumble_inv(&jumbled).unwrap();
389 assert_eq!(unjumbled, normal);
390 }
391 }
392}