Files
nym/nym-outfox/src/lion.rs
T
Jędrzej Stuczyński 827c13b69e moved nym-gateway-probe to monorepo and updated rust-edition to 2024 (#6094)
dont build netstack in CI

additional rust 2024 fixes

fixes

removed temp.rs

first round of cleanup

removed duplicated NS types

moved gateway probe to the monorepo
2025-10-08 16:17:43 +01:00

113 lines
4.2 KiB
Rust

//! # The lion all-or-nothing transform
//!
//! The lion transform implements a keyed permutation (block cipher) with a
//! variable length block size. It takes a key of 32 bytes, and a message of
//! length >= 48 bytes.
//!
//! The cryptographic primitives used to implement the transform are a
//! stream cipher `PRF1(IV, KEY)` (using `crypto_stream_xsalsa20`), a message authentication
//! code `PRF2(MSG, KEY)` (using `HMAC-SHA-512-256`) and a key derivation function `KDF(KEY, ID)`
//! (using `Blake2b`).
//!
//! The message to encode is split into two parts `M = [L0, R0]`, where L is 24 bytes, and
//! R is the remaining of the message.
//!
//! Encoding then proceeds in 3 steps:
//! * `R1 = PRF1(L0, KDF(key, subkey_0)) XOR R0;`
//! * `L1 = PRF2(R1, KDF(key, subkey_1)) XOR L0;`
//! * `R2 = PRF1(L1, KDF(key, subkey_2)) XOR R1;`
//!
//! The output of the transform is the concatenated byte string `M' = [L1, R2]` which has the same
//! length as the original message.
//!
//! ## Manual key schedule.
//!
//! If you just want to encode / decode using lion as a wide-block block cipher simply use the
//! [lion_transform_encrypt] and [lion_transform_decrypt] functions.
//!
//! If you know what you are doing you can determine your own key schedule for the transform. The
//! key schedule for encypt and decrypt are [1, 2, 3] and [3, 2, 1] respectivelly. You may define
//! a key schedule that is symmetric (such as [1, 2, 1]) to build a transform T(k,m) that has the
//! property m = T(k, T(k, m)).
use chacha20::Key;
use chacha20::XChaCha20;
use chacha20::XNonce;
use chacha20::cipher::{KeyIvInit, StreamCipher};
use zeroize::Zeroize;
use crate::constants::{CONTEXT, MIN_MESSAGE_LEN, TAG_LEN};
use crate::error::OutfoxError;
/// The lion transform encryption function.
///
/// The `key` must be 32 bytes, and the `message` >= 48. The message is
/// mutated to the encrypted message.
pub fn lion_transform_encrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutfoxError> {
lion_transform(message, key, [1, 2, 3])
}
/// The lion transform decryption function.
///
/// The `key` must be 32 bytes, and the `message` >= 48. The message
/// is mutated to the decrypted message.
pub fn lion_transform_decrypt(message: &mut [u8], key: &[u8]) -> Result<(), OutfoxError> {
lion_transform(message, key, [3, 2, 1])
}
/// The core of the lion transform function.
///
/// Takes a message and a key, and applies the all-or-nothing transform. The key schedule
/// represents the values of the 3 subkeys used by the 3 phases of the transform.
///
/// The `key` must be 32 bytes, and the `message` >= 48.
///
/// Unless you know what you are doing use [lion_transform_encrypt] and
/// [lion_transform_decrypt] instead.
pub fn lion_transform(
message: &mut [u8],
key: &[u8],
key_schedule: [u64; 3],
) -> Result<(), OutfoxError> {
if key.len() != 32 {
return Err(OutfoxError::InvalidKeyLength);
}
if message.len() < MIN_MESSAGE_LEN {
return Err(OutfoxError::InvalidMessageLength);
}
// Stage 1: Use stream cipher with Nonce from left size, to xor to the right side
let mut derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[0]), key);
let lion_stage_1_key = Key::from_slice(&derived_key);
let left_short_message = XNonce::from_slice(&message[..TAG_LEN]);
let mut cipher = XChaCha20::new(lion_stage_1_key, left_short_message);
cipher.apply_keystream(&mut message[TAG_LEN..]);
// Stage 2: Use HMAC of right size, and xor to the left side
derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[1]), key);
let mac = blake3::keyed_hash(&derived_key, &message[TAG_LEN..]);
let tag_to_xor = mac.as_bytes();
// Xor resulting HMAC into the left (short) message
for i in 0..TAG_LEN {
message[i] ^= tag_to_xor[i];
}
// Stage 3: (same as 1)
derived_key = blake3::derive_key(&format!("{}{}", CONTEXT, key_schedule[2]), key);
let lion_stage_3_key = Key::from_slice(&derived_key);
let left_short_message_final = XNonce::from_slice(&message[..TAG_LEN]);
let mut cipher = XChaCha20::new(lion_stage_3_key, left_short_message_final);
cipher.apply_keystream(&mut message[TAG_LEN..]);
// clean up temp key
derived_key.zeroize();
Ok(())
}