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}