Expand description

Hierarchical secret derivation with Blake2b and random number generators.

How it works

This crate provides SecretTree – a structure produced from a 32-byte seed that may be converted into a secret key or a cryptographically secure pseudo-random number generator (CSPRNG). Besides that, an SecretTree can produce child trees, which are identified by a string Name or an integer index. This enables creating hierarchies of secrets (like some_secret/0, some_secret/1 and other_secret/foo/1/bar), which are ultimately derived from a single SecretTree. It’s enough to securely store the seed of this root tree (e.g., in a passphrase-encrypted form) to recreate all secrets.

The derived secrets cannot be linked; leakage of a derived secret does not compromise sibling secrets or the parent SecretTree.

Crate features

The crate is no_std-compatible. There is optional std support enabled via the std feature, which is on by default.

Implementation details

SecretTree uses the Blake2b keyed hash function to derive the following kinds of data:

  • secret key
  • CSPRNG seed (the RNG used is ChaChaRng)
  • seeds for child SecretTrees

The procedure is similar to the use of Blake2b for key derivation in libsodium:

  • Blake2b is used with a custom initialization block. The block has two customizable parameters of interest: salt and personalization (each is 16 bytes). See the table below for information how these two parameters are set for each type of derived data.
  • The key is the seed of the SecretTree instance used for derivation.
  • The message is an empty bit string.

The length of derived data is 32 bytes in all cases.

Salt and personalization

Data typeSaltPersonalization
Secret key[0; 16]b"bytes\0\0...\0"
CSPRNG seed[0; 16]b"rng\0\0...\0"
Seed for a named childname.as_bytes() (zero-padded)b"name\0\0...\0"
Seed for an indexed childLittleEndian(index)b"index\0\0...\0"
Seed for a digest child (1st iter)digest[..16]b"digest0\0\0...\0"
Seed for a digest child (2nd iter)digest[16..]b"digest1\0\0...\0"

Derivation of a secret key, CSPRNG seed and seeds for indexed children are all fully compatible with libsodium. libsodium uses the salt section in the Blake2b initialization block to store the index of a child key, and the personalization section to store its context.

For example, the CSPRNG seed can be computed as follows (if we translate libsodium API from C to Rust):

use rand::SeedableRng;
use rand_chacha::ChaChaRng;

let parent_seed: [u8; 32] = // ...
let mut rng_seed = [0; 32];
    &mut rng_seed,
    /* index */ 0,
    /* context */ b"rng\0\0\0\0\0",
    /* master_key */ &parent_seed,
let rng = ChaChaRng::from_seed(rng_seed);

In case of named and digest children, we utilize the entire salt section, while libsodium only uses the first 8 bytes.

For digest children, the derivation procedure is applied 2 times, taking the first 16 bytes and the remaining 16 bytes of the digest respectively. The 32-byte key derived on the first iteration is used as the master key input for the second iteration. Such a procedure is necessary because Blake2b only supports 16-byte salts.

Design motivations

  • We allow to derive RNGs besides keys in order to allow a richer variety of applications. RNGs can be used in more complex use cases than fixed-size byte arrays, e.g., when the length of the secret depends on previous RNG output, or RNG is used to sample a complex distribution.
  • Derivation in general (instead of using a single SeedableRng to create all secrets) allows to add new secrets or remove old ones without worrying about compatibility.
  • Child RNGs identified by an index can be used to derive secrets of the same type, the quantity of which is unbounded. As an example, they can be used to produce blinding factors for Pedersen commitments (e.g., in a privacy-focused cryptocurrency).
  • Some steps are taken to make it difficult to use SecretTree incorrectly. For example, rng() and fill() methods consume the tree instance, which makes it harder to reuse the same RNG for multiple purposes (which is not intended).


Name of a child SecretTree.

Seeded structure that can be used to produce secrets and child SecretTrees.


Errors that can occur when calling SecretTree::try_fill().

Errors that can occur when converting a &str into Name.


Maximum byte length of a Name (16).

Byte length of a Seed (32).


Converts a type to a mutable byte slice. This is used within the crate to fill secret values with the RNG output.

Type Definitions

Alias for a Secret array that contains seed bytes.