#![cfg_attr(not(feature = "std"), no_std)]
#![doc(html_root_url = "https://docs.rs/secret-tree/0.5.0")]
#![warn(missing_docs, missing_debug_implementations)]
#![warn(clippy::all, clippy::pedantic)]
#![allow(
clippy::missing_errors_doc,
clippy::must_use_candidate,
clippy::module_name_repetitions
)]
#[cfg(all(not(feature = "std"), test))]
extern crate std;
use rand::{CryptoRng, RngCore, SeedableRng};
use rand_chacha::ChaChaRng;
use secrecy::{zeroize::Zeroize, ExposeSecret, Secret};
use core::{
array::TryFromSliceError,
convert::TryInto,
fmt,
str::{self, FromStr},
};
mod byte_slice;
mod kdf;
pub use crate::{byte_slice::AsByteSliceMut, kdf::SEED_LEN};
use crate::kdf::{derive_key, try_derive_key, Index, CONTEXT_LEN, SALT_LEN};
pub const MAX_NAME_LEN: usize = SALT_LEN;
pub type Seed = Secret<[u8; SEED_LEN]>;
#[derive(Debug)]
#[must_use = "A tree should generate a secret or child tree"]
pub struct SecretTree {
seed: Seed,
}
impl SecretTree {
const FILL_BYTES_CONTEXT: [u8; CONTEXT_LEN] = *b"bytes\0\0\0";
const RNG_CONTEXT: [u8; CONTEXT_LEN] = *b"rng\0\0\0\0\0";
const NAME_CONTEXT: [u8; CONTEXT_LEN] = *b"name\0\0\0\0";
const INDEX_CONTEXT: [u8; CONTEXT_LEN] = *b"index\0\0\0";
const DIGEST_START_CONTEXT: [u8; CONTEXT_LEN] = *b"digest0\0";
const DIGEST_END_CONTEXT: [u8; CONTEXT_LEN] = *b"digest1\0";
pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let mut seed = [0; 32];
rng.fill_bytes(&mut seed);
Self {
seed: Secret::new(seed),
}
}
pub fn from_seed(seed: Seed) -> Self {
Self { seed }
}
pub fn from_slice(bytes: &[u8]) -> Result<Self, TryFromSliceError> {
let seed: [u8; 32] = bytes.try_into()?;
Ok(Self {
seed: Secret::new(seed),
})
}
pub fn seed(&self) -> &Seed {
&self.seed
}
pub fn rng(self) -> ChaChaRng {
let mut seed = <ChaChaRng as SeedableRng>::Seed::default();
derive_key(
seed.as_mut(),
Index::None,
Self::RNG_CONTEXT,
self.seed.expose_secret(),
);
ChaChaRng::from_seed(seed)
}
pub fn try_fill<T: AsByteSliceMut + ?Sized>(self, dest: &mut T) -> Result<(), FillError> {
try_derive_key(
dest.as_byte_slice_mut(),
Index::None,
Self::FILL_BYTES_CONTEXT,
self.seed.expose_secret(),
)?;
dest.convert_to_le();
Ok(())
}
pub fn fill<T: AsByteSliceMut + ?Sized>(self, dest: &mut T) {
self.try_fill(dest).unwrap_or_else(|err| {
panic!("Failed filling a buffer from `SecretTree`: {}", err);
});
}
pub fn try_create_secret<T>(self) -> Result<Secret<T>, FillError>
where
T: AsByteSliceMut + Default + Zeroize,
{
let mut secret_value = T::default();
self.try_fill(&mut secret_value)?;
Ok(Secret::new(secret_value))
}
pub fn create_secret<T>(self) -> Secret<T>
where
T: AsByteSliceMut + Default + Zeroize,
{
self.try_create_secret().unwrap_or_else(|err| {
panic!("Failed creating a secret from `SecretTree`: {}", err);
})
}
pub fn child(&self, name: Name) -> Self {
let mut child_seed = [0_u8; 32];
derive_key(
&mut child_seed,
Index::Bytes(name.0),
Self::NAME_CONTEXT,
self.seed.expose_secret(),
);
Self::from_seed(Secret::new(child_seed))
}
pub fn index(&self, index: u64) -> Self {
let mut child_seed = [0_u8; 32];
derive_key(
&mut child_seed,
Index::Number(index),
Self::INDEX_CONTEXT,
self.seed.expose_secret(),
);
Self::from_seed(Secret::new(child_seed))
}
pub fn digest(&self, digest: &[u8; 32]) -> Self {
let mut first_half_of_digest = [0_u8; SALT_LEN];
first_half_of_digest.copy_from_slice(&digest[0..SALT_LEN]);
let mut second_half_of_digest = [0_u8; SALT_LEN];
second_half_of_digest.copy_from_slice(&digest[SALT_LEN..]);
let mut intermediate_seed = [0_u8; 32];
derive_key(
&mut intermediate_seed,
Index::Bytes(first_half_of_digest),
Self::DIGEST_START_CONTEXT,
self.seed.expose_secret(),
);
let intermediate_seed = Secret::new(intermediate_seed);
let mut child_seed = [0_u8; 32];
derive_key(
&mut child_seed,
Index::Bytes(second_half_of_digest),
Self::DIGEST_END_CONTEXT,
intermediate_seed.expose_secret(),
);
Self::from_seed(Secret::new(child_seed))
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FillError {
BufferTooSmall {
size: usize,
min_supported_size: usize,
},
BufferTooLarge {
size: usize,
max_supported_size: usize,
},
}
impl fmt::Display for FillError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BufferTooSmall {
size,
min_supported_size,
} => {
write!(
formatter,
"supplied buffer ({size} bytes) is too small to be filled; \
min supported size is {min_supported} bytes",
size = size,
min_supported = min_supported_size
)
}
Self::BufferTooLarge {
size,
max_supported_size,
} => {
write!(
formatter,
"supplied buffer ({size} bytes) is too large to be filled; \
max supported size is {max_supported} bytes",
size = size,
max_supported = max_supported_size
)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for FillError {}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Name([u8; SALT_LEN]);
impl Name {
pub const fn new(name: &str) -> Self {
let bytes = name.as_bytes();
let mut i = 0;
let mut buffer = [0_u8; SALT_LEN];
while i < name.len() {
assert!(bytes[i] != 0, "name contains a null char");
buffer[i] = bytes[i];
i += 1;
}
Name(buffer)
}
}
impl FromStr for Name {
type Err = NameError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
let byte_len = name.as_bytes().len();
if byte_len > SALT_LEN {
return Err(NameError::TooLong);
}
if name.as_bytes().contains(&0) {
return Err(NameError::NullChar);
}
let mut bytes = [0; SALT_LEN];
bytes[..byte_len].copy_from_slice(name.as_bytes());
Ok(Self(bytes))
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
let str_len = self.0.iter().position(|&ch| ch == 0).unwrap_or(SALT_LEN);
unsafe {
str::from_utf8_unchecked(&self.0[..str_len])
}
}
}
impl fmt::Debug for Name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("Name").field(&self.as_ref()).finish()
}
}
impl fmt::Display for Name {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_ref())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum NameError {
TooLong,
NullChar,
}
impl fmt::Display for NameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::TooLong => "name is too long, 0..=16 bytes expected",
Self::NullChar => "name contains a null char",
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for NameError {}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");
#[cfg(test)]
mod tests {
use super::*;
use rand::{Rng, SeedableRng};
use std::vec;
#[test]
fn children_with_same_bytes_in_key() {
let name = Name::new("A");
let index = u64::from(b'A');
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let named_child = tree.child(name);
let indexed_child = tree.index(index);
assert_ne!(
named_child.seed.expose_secret(),
indexed_child.seed.expose_secret()
);
}
#[test]
fn fill_and_rng_result_in_different_data() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut buffer = [0_u64; 8];
tree.child(Name::new("foo")).fill(&mut buffer);
let other_buffer: [u64; 8] = tree.child(Name::new("foo")).rng().gen();
assert_ne!(buffer, other_buffer);
}
#[test]
#[should_panic(expected = "supplied buffer (12 bytes) is too small to be filled")]
fn filling_undersized_key() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut buffer = [0_u8; 12];
tree.fill(&mut buffer);
}
#[test]
fn error_filling_undersized_key() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut buffer = [0_u8; 12];
let err = tree.try_fill(&mut buffer).unwrap_err();
assert!(matches!(
err,
FillError::BufferTooSmall {
size: 12,
min_supported_size: 16,
}
));
let err = err.to_string();
assert!(
err.contains("supplied buffer (12 bytes) is too small to be filled"),
"{}",
err
);
assert!(err.contains("min supported size is 16 bytes"), "{}", err);
}
#[test]
#[should_panic(expected = "supplied buffer (80 bytes) is too large to be filled")]
fn filling_oversized_key() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut buffer = [0_u64; 10];
tree.fill(&mut buffer);
}
#[test]
fn error_filling_oversized_key() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut buffer = [0_u64; 10];
let err = tree.try_fill(&mut buffer).unwrap_err();
assert!(matches!(
err,
FillError::BufferTooLarge {
size: 80,
max_supported_size: 64,
}
));
let err = err.to_string();
assert!(
err.contains("supplied buffer (80 bytes) is too large to be filled"),
"{}",
err
);
assert!(err.contains("max supported size is 64 bytes"), "{}", err);
}
#[test]
fn filling_acceptable_buffers() {
let mut u8_buffer = [0_u8; 40];
let mut i32_buffer = [0_i32; 16];
let mut u128_buffer = [0_u128];
let mut vec_buffer = vec![0_u16; 24];
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
tree.child(Name::new("u8")).fill(&mut u8_buffer[..]);
tree.child(Name::new("i32")).fill(&mut i32_buffer);
tree.child(Name::new("u128")).fill(&mut u128_buffer);
tree.child(Name::new("vec")).fill(&mut vec_buffer[..]);
}
#[test]
#[should_panic]
fn name_with_null_chars_cannot_be_created() {
let _name = Name::new("some\0name");
}
#[test]
fn name_with_null_chars_error() {
let err = Name::from_str("some\0name").unwrap_err();
assert!(matches!(err, NameError::NullChar));
}
#[test]
#[should_panic]
fn overly_long_name_cannot_be_created() {
let _name = Name::new("Overly long name?");
}
#[test]
fn overly_long_name_error() {
let err = Name::from_str("Overly long name?").unwrap_err();
assert!(matches!(err, NameError::TooLong));
}
#[test]
fn name_new_pads_input_with_zeros() {
const SAMPLES: &[(Name, &[u8; MAX_NAME_LEN])] = &[
(Name::new(""), b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("O"), b"O\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Ov"), b"Ov\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Ove"), b"Ove\0\0\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Over"), b"Over\0\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Overl"), b"Overl\0\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Overly"), b"Overly\0\0\0\0\0\0\0\0\0\0"),
(Name::new("Overly "), b"Overly \0\0\0\0\0\0\0\0\0"),
(Name::new("Overly l"), b"Overly l\0\0\0\0\0\0\0\0"),
(Name::new("Overly lo"), b"Overly lo\0\0\0\0\0\0\0"),
(Name::new("Overly lon"), b"Overly lon\0\0\0\0\0\0"),
(Name::new("Overly long"), b"Overly long\0\0\0\0\0"),
(Name::new("Overly long "), b"Overly long \0\0\0\0"),
(Name::new("Overly long n"), b"Overly long n\0\0\0"),
(Name::new("Overly long na"), b"Overly long na\0\0"),
(Name::new("Overly long nam"), b"Overly long nam\0"),
(Name::new("Overly long name"), b"Overly long name"),
];
for (i, &(name, expected_bytes)) in SAMPLES.iter().enumerate() {
assert_eq!(name.0, *expected_bytes);
let expected_str = &"Overly long name"[..i];
assert_eq!(name.to_string(), expected_str);
assert_eq!(name.as_ref(), expected_str);
assert!(format!("{:?}", name).contains(expected_str));
}
}
#[test]
fn buffers_with_different_size_should_be_unrelated() {
let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
let mut bytes = [0_u8; 16];
tree.child(Name::new("foo")).fill(&mut bytes);
let mut other_bytes = [0_u8; 32];
tree.child(Name::new("foo")).fill(&mut other_bytes);
assert!(bytes.iter().zip(&other_bytes).any(|(&x, &y)| x != y));
}
#[test]
fn digest_derivation_depends_on_all_bits_of_digest() {
const RNG_SEED: u64 = 12345;
let mut rng = ChaChaRng::seed_from_u64(RNG_SEED);
let tree = SecretTree::new(&mut rng);
let mut digest = [0_u8; 32];
rng.fill_bytes(&mut digest);
let child_seed = tree.digest(&digest).seed;
for byte_idx in 0..32 {
for bit_idx in 0..8 {
let mut mutated_digest = digest;
mutated_digest[byte_idx] ^= 1 << bit_idx;
assert_ne!(mutated_digest, digest);
let mutated_child_seed = tree.digest(&mutated_digest).seed;
assert_ne!(
child_seed.expose_secret(),
mutated_child_seed.expose_secret()
);
}
}
}
}