Merge pull request #544 from zcash/451-f4jumble-docs

Add documentation to `f4jumble` crate
This commit is contained in:
Kris Nuttycombe 2022-04-25 16:07:04 -06:00 committed by GitHub
commit 6c22d41a31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 0 deletions

View File

@ -13,10 +13,15 @@ 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 }
[dev-dependencies]
hex = "0.4"
proptest = "1"
[features]

View File

@ -1,4 +1,50 @@
//! 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]
#![cfg_attr(docsrs, feature(doc_cfg))]
use blake2b_simd::{Params as Blake2bParams, OUTBYTES};
@ -136,6 +182,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();
@ -145,6 +213,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();
@ -154,13 +242,57 @@ 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")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn f4jumble(message: &[u8]) -> Result<Vec<u8>, 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")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn f4jumble_inv(message: &[u8]) -> Result<Vec<u8>, Error> {
let mut result = message.to_vec();
f4jumble_inv_mut(&mut result).map(|()| result)