From 0756ea2f3e8705e4988fe45497e4953f0cbeb491 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 25 Apr 2022 16:52:28 +0000 Subject: [PATCH 1/3] f4jumble: Add crate-level documentation --- components/f4jumble/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/components/f4jumble/src/lib.rs b/components/f4jumble/src/lib.rs index 1afea0523..2a45482a0 100644 --- a/components/f4jumble/src/lib.rs +++ b/components/f4jumble/src/lib.rs @@ -1,3 +1,48 @@ +//! This crate provides a mechanism for "jumbling" byte slices in a reversible way. +//! +//! Many byte encodings such as [Base64] and [Bech32] do not have "cascading" behaviour: +//! changing an input byte at one position has no effect on the encoding of bytes at +//! distant positions. This can be a problem if users generally check the correctness of +//! encoded strings by eye, as they will tend to only check the first and/or last few +//! characters of the encoded string. In some situations (for example, a hardware device +//! displaying on its screen an encoded string provided by an untrusted computer), it is +//! potentially feasible for an adversary to change some internal portion of the encoded +//! string in a way that is beneficial to them, without the user noticing. +//! +//! [Base64]: https://en.wikipedia.org/wiki/Base64 +//! [Bech32]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#Bech32 +//! +//! The function F4Jumble (and its inverse function, F4Jumble⁻¹) are length-preserving +//! transformations can be used to trivially introduce cascading behaviour to existing +//! encodings: +//! - Prepare the raw `message` bytes as usual. +//! - Pass `message` through [`f4jumble`] or [`f4jumble_mut`] to obtain the jumbled bytes. +//! - Encode the jumbled bytes with the encoding scheme. +//! +//! Changing any byte of `message` will result in a completely different sequence of +//! jumbled bytes. Specifically, F4Jumble uses an unkeyed 4-round Feistel construction to +//! approximate a random permutation. +//! +//! ![Diagram of 4-round unkeyed Feistel construction](https://zips.z.cash/zip-0316-f4.png) +//! +//! ## Efficiency +//! +//! The cost is dominated by 4 BLAKE2b compressions for message lengths up to 128 bytes. +//! For longer messages, the cost increases to 6 BLAKE2b compressions for 128 < lₘ ≤ 192, +//! and 10 BLAKE2b compressions for 192 < lₘ ≤ 256, for example. The maximum cost for +//! which the algorithm is defined would be 196608 BLAKE2b compressions at lₘ = 4194368. +//! +//! The implementations in this crate require memory of roughly lₘ bytes plus the size of +//! a BLAKE2b hash state. It is possible to reduce this by (for example, with F4Jumble⁻¹) +//! streaming the `d` part of the jumbled encoding three times from a less +//! memory-constrained device. It is essential that the streamed value of `d` is the same +//! on each pass, which can be verified using a Message Authentication Code (with key held +//! only by the Consumer) or collision-resistant hash function. After the first pass of +//! `d`, the implementation is able to compute `y`; after the second pass it is able to +//! compute `a`; and the third allows it to compute and incrementally parse `b`. The +//! maximum memory usage during this process would be 128 bytes plus two BLAKE2b hash +//! states. + #![no_std] use blake2b_simd::{Params as Blake2bParams, OUTBYTES}; From b02d64a6ef7fecb69065ae273a7948f5957e706e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 25 Apr 2022 16:52:56 +0000 Subject: [PATCH 2/3] f4jumble: Add method documentation --- components/f4jumble/Cargo.toml | 1 + components/f4jumble/src/lib.rs | 84 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/components/f4jumble/Cargo.toml b/components/f4jumble/Cargo.toml index 077c6cbb6..6d7031c0a 100644 --- a/components/f4jumble/Cargo.toml +++ b/components/f4jumble/Cargo.toml @@ -17,6 +17,7 @@ edition = "2018" blake2b_simd = { version = "1", default-features = false } [dev-dependencies] +hex = "0.4" proptest = "1" [features] diff --git a/components/f4jumble/src/lib.rs b/components/f4jumble/src/lib.rs index 2a45482a0..b48a40a58 100644 --- a/components/f4jumble/src/lib.rs +++ b/components/f4jumble/src/lib.rs @@ -181,6 +181,28 @@ fn ceildiv(num: usize, den: usize) -> usize { (num + den - 1) / den } +/// Encodes the given message in-place using F4Jumble. +/// +/// Returns an error if the message is an invalid length. `message` will be unmodified in +/// this case. +/// +/// # Examples +/// +/// ``` +/// let mut message_a = *b"The package from Alice arrives tomorrow morning."; +/// f4jumble::f4jumble_mut(&mut message_a[..]).unwrap(); +/// assert_eq!( +/// hex::encode(message_a), +/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27", +/// ); +/// +/// let mut message_b = *b"The package from Sarah arrives tomorrow morning."; +/// f4jumble::f4jumble_mut(&mut message_b[..]).unwrap(); +/// assert_eq!( +/// hex::encode(message_b), +/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8", +/// ); +/// ``` pub fn f4jumble_mut(message: &mut [u8]) -> Result<(), Error> { if VALID_LENGTH.contains(&message.len()) { State::new(message).apply_f4jumble(); @@ -190,6 +212,26 @@ pub fn f4jumble_mut(message: &mut [u8]) -> Result<(), Error> { } } +/// Decodes the given message in-place using F4Jumble⁻¹. +/// +/// Returns an error if the message is an invalid length. `message` will be unmodified in +/// this case. +/// +/// # Examples +/// +/// ``` +/// let mut message_a = hex::decode( +/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27") +/// .unwrap(); +/// f4jumble::f4jumble_inv_mut(&mut message_a).unwrap(); +/// assert_eq!(message_a, b"The package from Alice arrives tomorrow morning."); +/// +/// let mut message_b = hex::decode( +/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8") +/// .unwrap(); +/// f4jumble::f4jumble_inv_mut(&mut message_b).unwrap(); +/// assert_eq!(message_b, b"The package from Sarah arrives tomorrow morning."); +/// ``` pub fn f4jumble_inv_mut(message: &mut [u8]) -> Result<(), Error> { if VALID_LENGTH.contains(&message.len()) { State::new(message).apply_f4jumble_inv(); @@ -199,12 +241,54 @@ pub fn f4jumble_inv_mut(message: &mut [u8]) -> Result<(), Error> { } } +/// Encodes the given message using F4Jumble, and returns the encoded message as a vector +/// of bytes. +/// +/// Returns an error if the message is an invalid length. +/// +/// # Examples +/// +/// ``` +/// let message_a = b"The package from Alice arrives tomorrow morning."; +/// let encoded_a = f4jumble::f4jumble(message_a).unwrap(); +/// assert_eq!( +/// hex::encode(encoded_a), +/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27", +/// ); +/// +/// let message_b = b"The package from Sarah arrives tomorrow morning."; +/// let encoded_b = f4jumble::f4jumble(message_b).unwrap(); +/// assert_eq!( +/// hex::encode(encoded_b), +/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8", +/// ); +/// ``` #[cfg(feature = "std")] pub fn f4jumble(message: &[u8]) -> Result, Error> { let mut result = message.to_vec(); f4jumble_mut(&mut result).map(|()| result) } +/// Decodes the given message using F4Jumble⁻¹, and returns the decoded message as a +/// vector of bytes. +/// +/// Returns an error if the message is an invalid length. +/// +/// # Examples +/// +/// ``` +/// let encoded_a = hex::decode( +/// "861c51ee746b0313476967a3483e7e1ff77a2952a17d3ed9e0ab0f502e1179430322da9967b613545b1c36353046ca27") +/// .unwrap(); +/// let message_a = f4jumble::f4jumble_inv(&encoded_a).unwrap(); +/// assert_eq!(message_a, b"The package from Alice arrives tomorrow morning."); +/// +/// let encoded_b = hex::decode( +/// "af1d55f2695aea02440867bbbfae3b08e8da55b625de3fa91432ab7b2c0a7dff9033ee666db1513ba5761ef482919fb8") +/// .unwrap(); +/// let message_b = f4jumble::f4jumble_inv(&encoded_b).unwrap(); +/// assert_eq!(message_b, b"The package from Sarah arrives tomorrow morning."); +/// ``` #[cfg(feature = "std")] pub fn f4jumble_inv(message: &[u8]) -> Result, Error> { let mut result = message.to_vec(); From 32d6b57d71d104ec1afe444fd4aff5a70a80b5b2 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 25 Apr 2022 16:53:27 +0000 Subject: [PATCH 3/3] f4jumble: Document APIs that require feature flags --- components/f4jumble/Cargo.toml | 4 ++++ components/f4jumble/src/lib.rs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/components/f4jumble/Cargo.toml b/components/f4jumble/Cargo.toml index 6d7031c0a..4b1cb17de 100644 --- a/components/f4jumble/Cargo.toml +++ b/components/f4jumble/Cargo.toml @@ -13,6 +13,10 @@ readme = "README.md" license = "MIT OR Apache-2.0" edition = "2018" +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + [dependencies] blake2b_simd = { version = "1", default-features = false } diff --git a/components/f4jumble/src/lib.rs b/components/f4jumble/src/lib.rs index b48a40a58..bc8676752 100644 --- a/components/f4jumble/src/lib.rs +++ b/components/f4jumble/src/lib.rs @@ -44,6 +44,7 @@ //! states. #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] use blake2b_simd::{Params as Blake2bParams, OUTBYTES}; @@ -264,6 +265,7 @@ pub fn f4jumble_inv_mut(message: &mut [u8]) -> Result<(), Error> { /// ); /// ``` #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn f4jumble(message: &[u8]) -> Result, Error> { let mut result = message.to_vec(); f4jumble_mut(&mut result).map(|()| result) @@ -290,6 +292,7 @@ pub fn f4jumble(message: &[u8]) -> Result, Error> { /// assert_eq!(message_b, b"The package from Sarah arrives tomorrow morning."); /// ``` #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn f4jumble_inv(message: &[u8]) -> Result, Error> { let mut result = message.to_vec(); f4jumble_inv_mut(&mut result).map(|()| result)