secret_tree/
kdf.rs

1//! `libsodium`-compatible generic key derivation.
2
3use blake2::{
4    digest::{
5        core_api::{Buffer, UpdateCore, VariableOutputCore},
6        Output,
7    },
8    Blake2bVarCore,
9};
10
11use crate::FillError;
12
13/// Byte length of a [`Seed`](crate::Seed) (32).
14// Blake2b specification states that it produces outputs in range 1..=64 bytes;
15// libsodium supports 16..=64 byte outputs. We only use 32-byte outputs; this
16// is the size of the `ChaChaRng` seed.
17pub const SEED_LEN: usize = 32;
18
19/// Byte length of a context variable.
20// This length is half of what is supported by Blake2b (16 bytes),
21// but is compatible with the key derivation in `libsodium`. We don’t
22// need more internally and do not expose context to users.
23pub(crate) const CONTEXT_LEN: usize = 8;
24
25/// Byte length of salt in the Blake2b initialization block.
26pub(crate) const SALT_LEN: usize = 16;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub(crate) enum Index {
30    None,
31    Number(u64),
32    Bytes([u8; SALT_LEN]),
33}
34
35impl Index {
36    fn to_salt(self) -> [u8; 16] {
37        match self {
38            Index::None => [0; 16],
39            Index::Number(i) => {
40                let mut bytes = [0_u8; 16];
41                bytes[..8].copy_from_slice(&i.to_le_bytes());
42                bytes
43            }
44            Index::Bytes(bytes) => bytes,
45        }
46    }
47}
48
49pub(crate) fn try_derive_key(
50    output: &mut [u8],
51    index: Index,
52    context: [u8; CONTEXT_LEN],
53    key: &[u8; SEED_LEN],
54) -> Result<(), FillError> {
55    const MIN_SUPPORTED_SIZE: usize = 16;
56    const MAX_SUPPORTED_SIZE: usize = 64;
57
58    if output.len() < MIN_SUPPORTED_SIZE {
59        return Err(FillError::BufferTooSmall {
60            size: output.len(),
61            min_supported_size: MIN_SUPPORTED_SIZE,
62        });
63    }
64    if output.len() > MAX_SUPPORTED_SIZE {
65        return Err(FillError::BufferTooLarge {
66            size: output.len(),
67            max_supported_size: MAX_SUPPORTED_SIZE,
68        });
69    }
70
71    let mut buffer = Buffer::<Blake2bVarCore>::default();
72    let mut core =
73        Blake2bVarCore::new_with_params(&index.to_salt(), &context, SEED_LEN, output.len());
74    buffer.digest_blocks(key, |blocks| core.update_blocks(blocks));
75    // Pad the key with 3 * 32 = 96 bytes so that it occupies 2 entire blocks
76    buffer.digest_blocks(&[0; 3 * SEED_LEN], |blocks| core.update_blocks(blocks));
77
78    let mut full_output = Output::<Blake2bVarCore>::default();
79    core.finalize_variable_core(&mut buffer, &mut full_output);
80    output.copy_from_slice(&full_output[..output.len()]);
81    Ok(())
82}
83
84pub(crate) fn derive_key(
85    output: &mut [u8],
86    index: Index,
87    context: [u8; CONTEXT_LEN],
88    key: &[u8; SEED_LEN],
89) {
90    try_derive_key(output, index, context, key).unwrap();
91}
92
93#[test]
94fn sodium_test_vectors_64byte_output() {
95    use std::convert::TryFrom;
96
97    use const_decoder::Decoder::Hex;
98
99    const CTX: [u8; CONTEXT_LEN] = *b"KDF test";
100    const EXP: &[[u8; 64]] = &[
101        Hex.decode(
102            b"a0c724404728c8bb95e5433eb6a9716171144d61efb23e74b873fcbeda51d807\
103              1b5d70aae12066dfc94ce943f145aa176c055040c3dd73b0a15e36254d450614",
104        ),
105        Hex.decode(
106            b"02507f144fa9bf19010bf7c70b235b4c2663cc00e074f929602a5e2c10a78075\
107              7d2a3993d06debc378a90efdac196dd841817b977d67b786804f6d3cd585bab5",
108        ),
109        Hex.decode(
110            b"1944da61ff18dc2028c3578ac85be904931b83860896598f62468f1cb5471c6a\
111              344c945dbc62c9aaf70feb62472d17775ea5db6ed5494c68b7a9a59761f39614",
112        ),
113        Hex.decode(
114            b"131c0ca1633ed074986215b264f6e0474f362c52b029effc7b0f75977ee89cc9\
115              5d85c3db87f7e399197a25411592beeeb7e5128a74646a460ecd6deb4994b71e",
116        ),
117        Hex.decode(
118            b"a7023a0bf9be245d078aed26bcde0465ff0cc0961196a5482a0ff4ff8b401597\
119              1e13611f50529cb408f5776b14a90e7c3dd9160a22211db64ff4b5c0b9953680",
120        ),
121        Hex.decode(
122            b"50f49313f3a05b2e565c13feedb44daa675cafd42c2b2cf9edbce9c949fbfc3f\
123              175dcb738671509ae2ea66fb85e552394d479afa7fa3affe8791744796b94176",
124        ),
125        Hex.decode(
126            b"13b58d6d69780089293862cd59a1a8a4ef79bb850e3f3ba41fb22446a7dd1dc4\
127              da4667d37b33bf1225dcf8173c4c349a5d911c5bd2db9c5905ed70c11e809e3b",
128        ),
129        Hex.decode(
130            b"15d44b4b44ffa006eeceeb508c98a970aaa573d65905687b9e15854dec6d49c6\
131              12757e149f78268f727660dedf9abce22a9691feb20a01b0525f4b47a3cf19db",
132        ),
133        Hex.decode(
134            b"9aebba11c5428ae8225716369e30a48943be39159a899f804e9963ef78822e18\
135             6c21fe95bb0b85e60ef03a6f58d0b9d06e91f79d0ab998450b8810c73ca935b4",
136        ),
137        Hex.decode(
138            b"70f9b83e463fb441e7a4c43275125cd5b19d8e2e4a5d179a39f5db10bbce745a\
139              199104563d308cf8d4c6b27bbb759ded232f5bdb7c367dd632a9677320dfe416",
140        ),
141    ];
142
143    let mut key = [0_u8; SEED_LEN];
144    for (i, byte) in key.iter_mut().enumerate() {
145        *byte = u8::try_from(i).unwrap();
146    }
147
148    let mut output = [0_u8; 64];
149    for (i, exp) in EXP.iter().enumerate() {
150        derive_key(&mut output, Index::Number(i as u64), CTX, &key);
151        assert_eq!(&output as &[u8], exp as &[u8]);
152    }
153}
154
155#[test]
156fn sodium_test_vectors_varying_len_output() {
157    use std::{convert::TryFrom, vec};
158
159    use const_decoder::Decoder::Hex;
160
161    const CTX: [u8; CONTEXT_LEN] = *b"KDF test";
162    const EXP: &[&[u8]] = &[
163        &Hex.decode::<16>(b"a529216624ef9161e4cf117272aafff2"),
164        &Hex.decode::<20>(b"268214dc9477a2e3c1022829f934ab992a5a3d84"),
165        &Hex.decode::<24>(b"94d678717625e011995c7355f2092267dee47bf0722dd380"),
166        &Hex.decode::<28>(b"22c134b9d664e1bdb14dc309a936bf1512b19e4f5175642efb1a0df7"),
167        &Hex.decode::<32>(b"154b291f11196737f8b7f491e4ca11764e0227d34f94295408a869f007aa8618"),
168        &Hex.decode::<36>(
169            b"20790290347b9b0f413a954f40e52e270b3b45417e96c8733161672188701c08dd76cc3d",
170        ),
171        &Hex.decode::<40>(
172            b"66efa5dfe3efd4cc8ca25f2d622c97a20a192d7add965f26b002b7eb81aae4203c0e5f07fd945845",
173        ),
174    ];
175
176    let mut key = [0_u8; SEED_LEN];
177    for (i, byte) in key.iter_mut().enumerate() {
178        *byte = u8::try_from(i).unwrap();
179    }
180
181    for &exp in EXP {
182        let byte_size = exp.len();
183        let mut output = vec![0; byte_size];
184        derive_key(&mut output, Index::Number(byte_size as u64), CTX, &key);
185        assert_eq!(output.as_slice(), exp);
186    }
187}