secret_tree/lib.rs
1//! Hierarchical secret derivation with Blake2b and random number generators.
2//!
3//! # How it works
4//!
5//! This crate provides [`SecretTree`] – a structure produced from a 32-byte seed that
6//! may be converted into a secret key or a cryptographically secure
7//! pseudo-random number generator (CSPRNG).
8//! Besides that, an `SecretTree` can produce child trees, which are
9//! identified by a string [`Name`] or an integer index. This enables creating
10//! *hierarchies* of secrets (like `some_secret/0`, `some_secret/1` and `other_secret/foo/1/bar`),
11//! which are ultimately derived from a single `SecretTree`. It’s enough to securely store
12//! the seed of this root tree (e.g., in a passphrase-encrypted form) to recreate all secrets.
13//!
14//! The derived secrets cannot be linked; leakage of a derived secret does not compromise
15//! sibling secrets or the parent `SecretTree`.
16//!
17//! # Crate features
18//!
19//! The crate is `no_std`-compatible. There is optional `std` support enabled via the `std` feature,
20//! which is on by default.
21//!
22//! # Implementation details
23//!
24//! `SecretTree` uses the [Blake2b] keyed hash function to derive the following kinds of data:
25//!
26//! - secret key
27//! - CSPRNG seed (the RNG used is [`ChaChaRng`])
28//! - seeds for child `SecretTree`s
29//!
30//! The procedure is similar to the use of Blake2b for key derivation in [libsodium]\:
31//!
32//! - Blake2b is used with a custom initialization block. The block has two
33//! customizable parameters of interest: *salt* and *personalization* (each is 16 bytes).
34//! See the table below for information how these two parameters are set for each type
35//! of derived data.
36//! - The key is the seed of the `SecretTree` instance used for derivation.
37//! - The message is an empty bit string.
38//!
39//! The length of derived data is 32 bytes in all cases.
40//!
41//! ## Salt and personalization
42//!
43//! | Data type | Salt | Personalization |
44//! |:----------|:-----|:----------------|
45//! | Secret key | `[0; 16]` | `b"bytes\0\0...\0"` |
46//! | CSPRNG seed | `[0; 16]` | `b"rng\0\0...\0"` |
47//! | Seed for a [named child](SecretTree::child()) | `name.as_bytes()` (zero-padded) | `b"name\0\0...\0"` |
48//! | Seed for an [indexed child](SecretTree::index()) | `LittleEndian(index)` | `b"index\0\0...\0"` |
49//! | Seed for a [digest child](SecretTree::digest()) (1st iter) | `digest[..16]` | `b"digest0\0\0...\0"` |
50//! | Seed for a digest child (2nd iter) | `digest[16..]` | `b"digest1\0\0...\0"` |
51//!
52//! Derivation of a secret key, CSPRNG seed and seeds for indexed children are
53//! all fully compatible with libsodium.
54//! libsodium uses the salt section in the Blake2b initialization block to store
55//! the *index* of a child key, and the personalization section to store its *context*.
56//!
57//! For example, the CSPRNG seed can be computed as follows (if we translate libsodium API
58//! from C to Rust):
59//!
60//! ```
61//! use rand::SeedableRng;
62//! use rand_chacha::ChaChaRng;
63//! # fn crypto_kdf_derive_from_key(_: &mut [u8], _: u64, _: &[u8; 8], _: &[u8; 32]) {}
64//!
65//! let parent_seed: [u8; 32] = // ...
66//! # [0; 32];
67//! let mut rng_seed = [0; 32];
68//! crypto_kdf_derive_from_key(
69//! &mut rng_seed,
70//! /* index */ 0,
71//! /* context */ b"rng\0\0\0\0\0",
72//! /* master_key */ &parent_seed,
73//! );
74//! let rng = ChaChaRng::from_seed(rng_seed);
75//! ```
76//!
77//! In case of named and digest children, we utilize the entire salt section, while libsodium
78//! only uses the first 8 bytes.
79//!
80//! For digest children, the derivation procedure is applied 2 times, taking the first 16 bytes
81//! and the remaining 16 bytes of the digest respectively. The 32-byte key derived on the first
82//! iteration is used as the master key input for the second iteration. Such a procedure
83//! is necessary because Blake2b only supports 16-byte salts.
84//!
85//! # Design motivations
86//!
87//! - We allow to derive RNGs besides keys in order to allow a richer variety of applications.
88//! RNGs can be used in more complex use cases than fixed-size byte arrays,
89//! e.g., when the length of the secret depends on previous RNG output, or RNG is used to sample
90//! a complex distribution.
91//! - Derivation in general (instead of using a single `SeedableRng` to create all secrets)
92//! allows to add new secrets or remove old ones without worrying about compatibility.
93//! - Child RNGs identified by an index can be used to derive secrets of the same type,
94//! the quantity of which is unbounded. As an example, they can be used to produce
95//! blinding factors for [Pedersen commitments] (e.g., in a privacy-focused cryptocurrency).
96//! - Some steps are taken to make it difficult to use `SecretTree` incorrectly. For example,
97//! `rng()` and `fill()` methods consume the tree instance, which makes it harder to reuse
98//! the same RNG for multiple purposes (which is not intended).
99//!
100//! [libsodium]: https://download.libsodium.org/doc/key_derivation
101//! [Blake2b]: https://tools.ietf.org/html/rfc7693
102//! [Pedersen commitments]: https://en.wikipedia.org/wiki/Commitment_scheme
103
104#![cfg_attr(not(feature = "std"), no_std)]
105// Documentation settings
106#![doc(html_root_url = "https://docs.rs/secret-tree/0.6.0")]
107// Linter settings
108#![warn(missing_docs, missing_debug_implementations)]
109#![warn(clippy::all, clippy::pedantic)]
110#![allow(
111 clippy::missing_errors_doc,
112 clippy::must_use_candidate,
113 clippy::module_name_repetitions
114)]
115
116#[cfg(all(not(feature = "std"), test))]
117extern crate std;
118
119use core::{
120 array::TryFromSliceError,
121 convert::TryInto,
122 fmt,
123 str::{self, FromStr},
124};
125
126use rand_chacha::ChaChaRng;
127use rand_core::{CryptoRng, RngCore, SeedableRng};
128use secrecy::{zeroize::Zeroize, CloneableSecret, ExposeSecret, SecretBox};
129
130use crate::kdf::{derive_key, try_derive_key, Index, CONTEXT_LEN, SALT_LEN};
131pub use crate::{byte_slice::AsByteSliceMut, kdf::SEED_LEN};
132
133mod byte_slice;
134mod kdf;
135
136/// Maximum byte length of a [`Name`] (16).
137pub const MAX_NAME_LEN: usize = SALT_LEN;
138
139/// Wrapper around seed bytes.
140#[derive(Debug, Clone, Default)]
141struct SeedBytes([u8; SEED_LEN]);
142
143impl Zeroize for SeedBytes {
144 fn zeroize(&mut self) {
145 self.0.zeroize();
146 }
147}
148
149impl CloneableSecret for SeedBytes {}
150
151/// Seed for a [`SecretTree`].
152#[derive(Debug, Clone)]
153pub struct Seed(SecretBox<SeedBytes>);
154
155impl Seed {
156 /// Generates a random seed using the provided RNG.
157 pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
158 Self(SecretBox::<SeedBytes>::init_with_mut(|seed| {
159 rng.fill_bytes(&mut seed.0);
160 }))
161 }
162
163 fn init_with(init_fn: impl FnOnce(&mut [u8; SEED_LEN])) -> Self {
164 Self(SecretBox::<SeedBytes>::init_with_mut(|seed_bytes| {
165 init_fn(&mut seed_bytes.0);
166 }))
167 }
168
169 /// Exposes the bytes contained in this seed.
170 pub fn expose_secret(&self) -> &[u8; SEED_LEN] {
171 &self.0.expose_secret().0
172 }
173}
174
175/// Creates a seed from a (potentially unsecured) byte slice.
176impl From<&[u8; SEED_LEN]> for Seed {
177 fn from(bytes: &[u8; SEED_LEN]) -> Self {
178 Self::init_with(|seed_bytes| {
179 *seed_bytes = *bytes;
180 })
181 }
182}
183
184/// Seeded structure that can be used to produce secrets and child `SecretTree`s.
185///
186/// # Usage
187///
188/// During the program lifecycle, a root `SecretTree` should be restored from
189/// a secure persistent form (e.g., a passphrase-encrypted file) and then used to derive
190/// child trees and secrets. On the first use, the root should be initialized from a CSPRNG, such
191/// as `rand::thread_rng()`. The tree is not needed during the program execution and can
192/// be safely dropped after deriving necessary secrets (which zeroes out the tree seed).
193///
194/// It is possible to modify the derivation hierarchy over the course of program evolution
195/// by adding new secrets or abandoning the existing ones.
196/// However, the purpose of any given tree path should be fixed; that is, if some version
197/// of a program used path `foo/bar` to derive an Ed25519 keypair, a newer version
198/// shouldn’t use `foo/bar` to derive an AES-128 key. Violating this rule may lead
199/// to leaking the secret.
200///
201/// # Examples
202///
203/// ```
204/// use secret_tree::{SecretTree, Name};
205/// use rand::Rng;
206/// use secrecy::{ExposeSecret, SecretBox};
207///
208/// let tree = SecretTree::new(&mut rand::rng());
209/// // Don't forget to securely store secrets! Here, we wrap them
210/// // in a container that automatically zeroes the secret on drop.
211/// let first_secret: SecretBox<[u8; 32]> = tree
212/// .child(Name::new("first"))
213/// .create_secret();
214///
215/// // We can derive hierarchical secrets. The secrets below
216/// // follow logical paths `sequence/0`, `sequence/1`, .., `sequence/4`
217/// // relative to the `tree`.
218/// let child_store = tree.child(Name::new("sequence"));
219/// let more_secrets: Vec<SecretBox<[u64; 4]>> = (0..5)
220/// .map(|i| SecretBox::new(Box::new(child_store.index(i).rng().random())))
221/// .collect();
222///
223/// // The tree is compactly stored as a single 32-byte seed.
224/// let seed = tree.seed().clone();
225/// drop(tree);
226///
227/// // If we restore the tree from the seed, we can restore all derived secrets.
228/// let tree = SecretTree::from_seed(seed);
229/// let restored_secret: SecretBox<[u8; 32]> = tree
230/// .child(Name::new("first"))
231/// .create_secret();
232/// assert_eq!(
233/// first_secret.expose_secret(),
234/// restored_secret.expose_secret()
235/// );
236/// ```
237#[derive(Debug)]
238#[must_use = "A tree should generate a secret or child tree"]
239pub struct SecretTree {
240 seed: Seed,
241}
242
243impl SecretTree {
244 const FILL_BYTES_CONTEXT: [u8; CONTEXT_LEN] = *b"bytes\0\0\0";
245 const RNG_CONTEXT: [u8; CONTEXT_LEN] = *b"rng\0\0\0\0\0";
246 const NAME_CONTEXT: [u8; CONTEXT_LEN] = *b"name\0\0\0\0";
247 const INDEX_CONTEXT: [u8; CONTEXT_LEN] = *b"index\0\0\0";
248 const DIGEST_START_CONTEXT: [u8; CONTEXT_LEN] = *b"digest0\0";
249 const DIGEST_END_CONTEXT: [u8; CONTEXT_LEN] = *b"digest1\0";
250
251 /// Generates a tree by sampling its seed from the supplied RNG.
252 pub fn new<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
253 Self {
254 seed: Seed::new(rng),
255 }
256 }
257
258 /// Creates a tree from the seed.
259 pub fn from_seed(seed: Seed) -> Self {
260 Self { seed }
261 }
262
263 /// Restores a tree from the seed specified as a byte slice.
264 ///
265 /// # Errors
266 ///
267 /// Returns an error if `bytes` has an invalid length (not [`SEED_LEN`]).
268 pub fn from_slice(bytes: &[u8]) -> Result<Self, TryFromSliceError> {
269 let seed_ref: &[u8; 32] = bytes.try_into()?;
270 Ok(Self {
271 seed: seed_ref.into(),
272 })
273 }
274
275 /// Returns the tree seed.
276 pub fn seed(&self) -> &Seed {
277 &self.seed
278 }
279
280 /// Converts this tree into a cryptographically secure pseudo-random number generator
281 /// (CSPRNG). This RNG can then be used to reproducibly create secrets (e.g., secret keys).
282 ///
283 /// # Security
284 ///
285 /// [`Self::fill()`] should be preferred if the secret allows it. While using a CSPRNG
286 /// to generate secrets is theoretically sound, it introduces a new entity that
287 /// may leak information.
288 /// `fill()` is especially useful if the filled buffer implements zeroing on drop;
289 /// the state of a CSPRNG generator returned by `rng()` **is not** zeroed on drop and thus
290 /// creates a potential attack vector. (However theoretical it may be; `ChaChaRng`
291 /// has a notably small state size - ~160 bytes, so it may be better localized
292 /// and have lower risk to be accessed by the adversary than other CSPRNG implementations.)
293 pub fn rng(self) -> ChaChaRng {
294 let mut seed = <ChaChaRng as SeedableRng>::Seed::default();
295 derive_key(
296 seed.as_mut(),
297 Index::None,
298 Self::RNG_CONTEXT,
299 self.seed.expose_secret(),
300 );
301 ChaChaRng::from_seed(seed)
302 }
303
304 /// Tries to fill the specified buffer with a key derived from the seed of this tree.
305 ///
306 /// # Errors
307 ///
308 /// Errors if the buffer does not have length `16..=64` bytes. Use [`Self::rng()`]
309 /// if the buffer size may be outside these bounds, or if the secret must be derived
310 /// in a more complex way.
311 pub fn try_fill<T: AsByteSliceMut + ?Sized>(self, dest: &mut T) -> Result<(), FillError> {
312 try_derive_key(
313 dest.as_byte_slice_mut(),
314 Index::None,
315 Self::FILL_BYTES_CONTEXT,
316 self.seed.expose_secret(),
317 )?;
318 dest.convert_to_le();
319 Ok(())
320 }
321
322 /// Fills the specified buffer with a key derived from the seed of this tree.
323 ///
324 /// # Panics
325 ///
326 /// Panics in the same cases when [`Self::try_fill()`] returns an error.
327 pub fn fill<T: AsByteSliceMut + ?Sized>(self, dest: &mut T) {
328 self.try_fill(dest).unwrap_or_else(|err| {
329 panic!("Failed filling a buffer from `SecretTree`: {err}");
330 });
331 }
332
333 /// Tries to create a secret by instantiating a buffer and filling it with a key derived from
334 /// the seed of this tree. Essentially, this is a more high-level wrapper around
335 /// [`Self::try_fill()`].
336 ///
337 /// # Errors
338 ///
339 /// Returns an error if `T` does not have length `16..=64` bytes. Use [`Self::rng()`]
340 /// if the buffer size may be outside these bounds, or if the secret must be derived
341 /// in a more complex way.
342 pub fn try_create_secret<T>(self) -> Result<SecretBox<T>, FillError>
343 where
344 T: AsByteSliceMut + Default + Zeroize,
345 {
346 let mut result = Ok(());
347 let secret = SecretBox::init_with_mut(|secret_value| {
348 result = self.try_fill(secret_value);
349 });
350 result?;
351 Ok(secret)
352 }
353
354 /// Creates a secret by instantiating a buffer and filling it with a key derived from
355 /// the seed of this tree.
356 ///
357 /// # Panics
358 ///
359 /// Panics in the same cases when [`Self::try_create_secret()`] returns an error.
360 pub fn create_secret<T>(self) -> SecretBox<T>
361 where
362 T: AsByteSliceMut + Default + Zeroize,
363 {
364 self.try_create_secret().unwrap_or_else(|err| {
365 panic!("Failed creating a secret from `SecretTree`: {err}");
366 })
367 }
368
369 /// Produces a child with the specified string identifier.
370 pub fn child(&self, name: Name) -> Self {
371 Self::from_seed(Seed::init_with(|child_seed| {
372 derive_key(
373 child_seed,
374 Index::Bytes(name.0),
375 Self::NAME_CONTEXT,
376 self.seed.expose_secret(),
377 );
378 }))
379 }
380
381 /// Produces a child with the specified integer index.
382 pub fn index(&self, index: u64) -> Self {
383 Self::from_seed(Seed::init_with(|child_seed| {
384 derive_key(
385 child_seed,
386 Index::Number(index),
387 Self::INDEX_CONTEXT,
388 self.seed.expose_secret(),
389 );
390 }))
391 }
392
393 /// Produces a child with the specified 32-byte digest (e.g., an output of SHA-256,
394 /// SHA3-256 or Keccak256 hash functions).
395 ///
396 /// This method can be used for arbitrarily-sized keys by first digesting them
397 /// with a collision-resistant hash function.
398 pub fn digest(&self, digest: &[u8; 32]) -> Self {
399 let mut first_half_of_digest = [0_u8; SALT_LEN];
400 first_half_of_digest.copy_from_slice(&digest[0..SALT_LEN]);
401 let mut second_half_of_digest = [0_u8; SALT_LEN];
402 second_half_of_digest.copy_from_slice(&digest[SALT_LEN..]);
403
404 let intermediate_seed = Seed::init_with(|intermediate_seed| {
405 derive_key(
406 intermediate_seed,
407 Index::Bytes(first_half_of_digest),
408 Self::DIGEST_START_CONTEXT,
409 self.seed.expose_secret(),
410 );
411 });
412
413 Self::from_seed(Seed::init_with(|child_seed| {
414 derive_key(
415 child_seed,
416 Index::Bytes(second_half_of_digest),
417 Self::DIGEST_END_CONTEXT,
418 intermediate_seed.expose_secret(),
419 );
420 }))
421 }
422}
423
424/// Errors that can occur when calling [`SecretTree::try_fill()`].
425#[derive(Debug)]
426#[non_exhaustive]
427pub enum FillError {
428 /// The supplied buffer is too small to be filled.
429 BufferTooSmall {
430 /// Byte size of the supplied buffer.
431 size: usize,
432 /// Minimum byte size for supported buffers.
433 min_supported_size: usize,
434 },
435 /// The supplied buffer is too large to be filled.
436 BufferTooLarge {
437 /// Byte size of the supplied buffer.
438 size: usize,
439 /// Maximum byte size for supported buffers.
440 max_supported_size: usize,
441 },
442}
443
444impl fmt::Display for FillError {
445 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
446 match self {
447 Self::BufferTooSmall {
448 size,
449 min_supported_size,
450 } => {
451 write!(
452 formatter,
453 "supplied buffer ({size} bytes) is too small to be filled; \
454 min supported size is {min_supported_size} bytes"
455 )
456 }
457
458 Self::BufferTooLarge {
459 size,
460 max_supported_size,
461 } => {
462 write!(
463 formatter,
464 "supplied buffer ({size} bytes) is too large to be filled; \
465 max supported size is {max_supported_size} bytes"
466 )
467 }
468 }
469 }
470}
471
472#[cfg(feature = "std")]
473impl std::error::Error for FillError {}
474
475/// Name of a child [`SecretTree`].
476///
477/// Used in [`SecretTree::child()`]; see its documentation for more context.
478///
479/// An original `str` can be extracted from `Name` using [`AsRef`] / [`Display`](fmt::Display)
480/// implementations:
481///
482/// ```
483/// # use secret_tree::Name;
484/// const NAME: Name = Name::new("test_name");
485/// assert_eq!(NAME.as_ref(), "test_name");
486/// assert_eq!(NAME.to_string(), "test_name");
487/// ```
488#[derive(Clone, Copy, PartialEq, Eq, Hash)]
489pub struct Name([u8; SALT_LEN]);
490
491impl Name {
492 /// Creates a new `Name`.
493 ///
494 /// The supplied string must be no more than [`MAX_NAME_LEN`] bytes in length
495 /// and must not contain null chars `'\0'`.
496 ///
497 /// This is a constant method, which perform all relevant checks during compilation in
498 /// a constant context:
499 ///
500 /// ```
501 /// # use secret_tree::Name;
502 /// const NAME: Name = Name::new("some_name");
503 /// ```
504 ///
505 /// For example, this won't compile since the name is too long (17 chars):
506 ///
507 /// ```compile_fail
508 /// # use secret_tree::Name;
509 /// const OVERLY_LONG_NAME: Name = Name::new("Overly long name!");
510 /// ```
511 ///
512 /// ...And this won't compile because the name contains a `\0` char:
513 ///
514 /// ```compile_fail
515 /// # use secret_tree::Name;
516 /// const NAME_WITH_ZERO_CHARS: Name = Name::new("12\03");
517 /// ```
518 ///
519 /// # Panics
520 ///
521 /// Panics if `name` is overly long or contains null chars.
522 /// Use the [`FromStr`] implementation for a fallible / non-panicking alternative.
523 pub const fn new(name: &str) -> Self {
524 let bytes = name.as_bytes();
525 assert!(
526 bytes.len() <= SALT_LEN,
527 "name is too long (should be <=16 bytes)"
528 );
529
530 let mut i = 0;
531 let mut buffer = [0_u8; SALT_LEN];
532 while i < name.len() {
533 assert!(bytes[i] != 0, "name contains a null char");
534 buffer[i] = bytes[i];
535 i += 1;
536 }
537 Name(buffer)
538 }
539}
540
541impl FromStr for Name {
542 type Err = NameError;
543
544 fn from_str(name: &str) -> Result<Self, Self::Err> {
545 let byte_len = name.len();
546 if byte_len > SALT_LEN {
547 return Err(NameError::TooLong);
548 }
549 if name.as_bytes().contains(&0) {
550 return Err(NameError::NullChar);
551 }
552
553 let mut bytes = [0; SALT_LEN];
554 bytes[..byte_len].copy_from_slice(name.as_bytes());
555 Ok(Self(bytes))
556 }
557}
558
559impl AsRef<str> for Name {
560 fn as_ref(&self) -> &str {
561 let str_len = self.0.iter().position(|&ch| ch == 0).unwrap_or(SALT_LEN);
562 unsafe {
563 // SAFETY: safe by construction; we only ever create `Name`s from valid UTF-8 sequences.
564 str::from_utf8_unchecked(&self.0[..str_len])
565 }
566 }
567}
568
569impl fmt::Debug for Name {
570 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
571 formatter.debug_tuple("Name").field(&self.as_ref()).finish()
572 }
573}
574
575impl fmt::Display for Name {
576 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
577 formatter.write_str(self.as_ref())
578 }
579}
580
581/// Errors that can occur when converting a `&str` into [`Name`].
582#[derive(Debug)]
583#[non_exhaustive]
584pub enum NameError {
585 /// The string is too long. `Name`s should be 0..=16 bytes.
586 TooLong,
587 /// Name contains a null char `\0`.
588 NullChar,
589}
590
591impl fmt::Display for NameError {
592 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
593 formatter.write_str(match self {
594 Self::TooLong => "name is too long, 0..=16 bytes expected",
595 Self::NullChar => "name contains a null char",
596 })
597 }
598}
599
600#[cfg(feature = "std")]
601impl std::error::Error for NameError {}
602
603#[cfg(doctest)]
604doc_comment::doctest!("../README.md");
605
606#[cfg(test)]
607mod tests {
608 use rand::{Rng, SeedableRng};
609
610 use super::*;
611
612 #[test]
613 fn children_with_same_bytes_in_key() {
614 let name = Name::new("A");
615 let index = u64::from(b'A');
616 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
617 let named_child = tree.child(name);
618 let indexed_child = tree.index(index);
619 assert_ne!(
620 named_child.seed.expose_secret(),
621 indexed_child.seed.expose_secret()
622 );
623 }
624
625 #[test]
626 fn fill_and_rng_result_in_different_data() {
627 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
628 let mut buffer = [0_u64; 8];
629 tree.child(Name::new("foo")).fill(&mut buffer);
630 let other_buffer: [u64; 8] = tree.child(Name::new("foo")).rng().random();
631 assert_ne!(buffer, other_buffer);
632 }
633
634 #[test]
635 #[should_panic(expected = "supplied buffer (12 bytes) is too small to be filled")]
636 fn filling_undersized_key() {
637 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
638 let mut buffer = [0_u8; 12];
639 tree.fill(&mut buffer);
640 }
641
642 #[test]
643 fn error_filling_undersized_key() {
644 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
645 let mut buffer = [0_u8; 12];
646 let err = tree.try_fill(&mut buffer).unwrap_err();
647
648 assert!(matches!(
649 err,
650 FillError::BufferTooSmall {
651 size: 12,
652 min_supported_size: 16,
653 }
654 ));
655 let err = err.to_string();
656 assert!(
657 err.contains("supplied buffer (12 bytes) is too small to be filled"),
658 "{err}"
659 );
660 assert!(err.contains("min supported size is 16 bytes"), "{err}");
661 }
662
663 #[test]
664 #[should_panic(expected = "supplied buffer (80 bytes) is too large to be filled")]
665 fn filling_oversized_key() {
666 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
667 let mut buffer = [0_u64; 10];
668 tree.fill(&mut buffer);
669 }
670
671 #[test]
672 fn error_filling_oversized_key() {
673 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
674 let mut buffer = [0_u64; 10];
675 let err = tree.try_fill(&mut buffer).unwrap_err();
676
677 assert!(matches!(
678 err,
679 FillError::BufferTooLarge {
680 size: 80,
681 max_supported_size: 64,
682 }
683 ));
684 let err = err.to_string();
685 assert!(
686 err.contains("supplied buffer (80 bytes) is too large to be filled"),
687 "{err}"
688 );
689 assert!(err.contains("max supported size is 64 bytes"), "{err}");
690 }
691
692 #[test]
693 fn filling_acceptable_buffers() {
694 let mut u8_buffer = [0_u8; 40];
695 let mut i32_buffer = [0_i32; 16];
696 let mut u128_buffer = [0_u128];
697 // Using `Vec` to store secrets is usually a bad idea because of its placement in heap;
698 // here it is used just to test capabilities.
699 let mut vec_buffer = [0_u16; 24];
700
701 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
702 tree.child(Name::new("u8")).fill(&mut u8_buffer[..]);
703 tree.child(Name::new("i32")).fill(&mut i32_buffer);
704 tree.child(Name::new("u128")).fill(&mut u128_buffer);
705 tree.child(Name::new("vec")).fill(&mut vec_buffer[..]);
706 }
707
708 #[test]
709 #[should_panic(expected = "name contains a null char")]
710 fn name_with_null_chars_cannot_be_created() {
711 let _name = Name::new("some\0name");
712 }
713
714 #[test]
715 fn name_with_null_chars_error() {
716 let err = Name::from_str("some\0name").unwrap_err();
717 assert!(matches!(err, NameError::NullChar));
718 }
719
720 #[test]
721 #[should_panic(expected = "name is too long")]
722 fn overly_long_name_cannot_be_created() {
723 let _name = Name::new("Overly long name?");
724 }
725
726 #[test]
727 fn overly_long_name_error() {
728 let err = Name::from_str("Overly long name?").unwrap_err();
729 assert!(matches!(err, NameError::TooLong));
730 }
731
732 #[test]
733 fn name_new_pads_input_with_zeros() {
734 const SAMPLES: &[(Name, &[u8; MAX_NAME_LEN])] = &[
735 (Name::new(""), b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
736 (Name::new("O"), b"O\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
737 (Name::new("Ov"), b"Ov\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
738 (Name::new("Ove"), b"Ove\0\0\0\0\0\0\0\0\0\0\0\0\0"),
739 (Name::new("Over"), b"Over\0\0\0\0\0\0\0\0\0\0\0\0"),
740 (Name::new("Overl"), b"Overl\0\0\0\0\0\0\0\0\0\0\0"),
741 (Name::new("Overly"), b"Overly\0\0\0\0\0\0\0\0\0\0"),
742 (Name::new("Overly "), b"Overly \0\0\0\0\0\0\0\0\0"),
743 (Name::new("Overly l"), b"Overly l\0\0\0\0\0\0\0\0"),
744 (Name::new("Overly lo"), b"Overly lo\0\0\0\0\0\0\0"),
745 (Name::new("Overly lon"), b"Overly lon\0\0\0\0\0\0"),
746 (Name::new("Overly long"), b"Overly long\0\0\0\0\0"),
747 (Name::new("Overly long "), b"Overly long \0\0\0\0"),
748 (Name::new("Overly long n"), b"Overly long n\0\0\0"),
749 (Name::new("Overly long na"), b"Overly long na\0\0"),
750 (Name::new("Overly long nam"), b"Overly long nam\0"),
751 (Name::new("Overly long name"), b"Overly long name"),
752 ];
753
754 for (i, &(name, expected_bytes)) in SAMPLES.iter().enumerate() {
755 assert_eq!(name.0, *expected_bytes);
756 let expected_str = &"Overly long name"[..i];
757 assert_eq!(name.to_string(), expected_str);
758 assert_eq!(name.as_ref(), expected_str);
759 assert!(format!("{name:?}").contains(expected_str));
760 }
761 }
762
763 #[test]
764 fn buffers_with_different_size_should_be_unrelated() {
765 let tree = SecretTree::new(&mut ChaChaRng::seed_from_u64(123));
766 let mut bytes = [0_u8; 16];
767 tree.child(Name::new("foo")).fill(&mut bytes);
768 let mut other_bytes = [0_u8; 32];
769 tree.child(Name::new("foo")).fill(&mut other_bytes);
770 assert!(bytes.iter().zip(&other_bytes).any(|(&x, &y)| x != y));
771 }
772
773 #[test]
774 fn digest_derivation_depends_on_all_bits_of_digest() {
775 const RNG_SEED: u64 = 12345;
776
777 let mut rng = ChaChaRng::seed_from_u64(RNG_SEED);
778 let tree = SecretTree::new(&mut rng);
779 let mut digest = [0_u8; 32];
780 rng.fill_bytes(&mut digest);
781
782 let child_seed = tree.digest(&digest).seed;
783 for byte_idx in 0..32 {
784 for bit_idx in 0..8 {
785 let mut mutated_digest = digest;
786 mutated_digest[byte_idx] ^= 1 << bit_idx;
787 assert_ne!(mutated_digest, digest);
788
789 let mutated_child_seed = tree.digest(&mutated_digest).seed;
790 assert_ne!(
791 child_seed.expose_secret(),
792 mutated_child_seed.expose_secret()
793 );
794 }
795 }
796 }
797}