Skip to content

Commit 5731d5d

Browse files
committed
Refactor to use "flat" path representation
Previously `Path` was represented as a slice of byte slices (i.e. `&[&[u8]]`) and `PathBuf` as a `Vec<Vec<u8>>`. These sorts of representations of paths preclude ever being able to impl safe, non-allocating wrapper types, because there is not a safe reference conversion from nested `Vec`s to nested slices. This commit switches to using a 1-byte length prefix on each of the components. This has the advantages of making reference types easier to work with and also ensures a canonical wire serialization. It places a max limit of 256-bytes on each path component, but this is a reasonable upper bound. The downside is this temporarily relies on an unsafe pointer conversion to make everything work, however there is some discussion of how to make this particular kind of reference conversion safe by means of safe casting from reference types to transparent wrappers around them: <https://internals.rust-lang.org/t/pre-rfc-patterns-allowing-transparent-wrapper-types/10229>
1 parent 23eb5c9 commit 5731d5d

File tree

7 files changed

+778
-334
lines changed

7 files changed

+778
-334
lines changed

hkd32/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ features = ["zeroize_derive"]
3333

3434
[features]
3535
default = ["alloc", "getrandom"]
36-
alloc = []
37-
mnemonic = ["tiny-bip39"]
36+
alloc = ["zeroize/alloc"]
37+
mnemonic = ["alloc", "tiny-bip39"]
3838

3939
[package.metadata.docs.rs]
4040
all-features = true

hkd32/src/key_material.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//! Cryptographic key material.
2+
//!
3+
//! Unlike traditional KDFs and XOFs, HKD32 acts on fixed-sized
4+
//! 256-bit (32-byte) keys.
5+
//!
6+
//! The `KeyMaterial` type is used to represent both input and output key
7+
//! material, and is the primary type useful for deriving other keys.
8+
9+
#[cfg(feature = "mnemonic")]
10+
use crate::mnemonic;
11+
use crate::{path::Path, Error, KEY_SIZE};
12+
use core::convert::TryFrom;
13+
#[cfg(feature = "getrandom")]
14+
use getrandom::getrandom;
15+
use hmac::{Hmac, Mac};
16+
use sha2::Sha512;
17+
use zeroize::Zeroize;
18+
19+
/// Cryptographic key material: 256-bit (32-byte) uniformly random bytestring
20+
/// generated either via a CSRNG or via hierarchical derivation.
21+
///
22+
/// This type provides the main key derivation functionality and is used to
23+
/// represent both input and output key material.
24+
#[derive(Clone, Zeroize)]
25+
#[zeroize(drop)]
26+
pub struct KeyMaterial([u8; KEY_SIZE]);
27+
28+
impl KeyMaterial {
29+
/// Create random key material using the operating system CSRNG
30+
#[cfg(feature = "getrandom")]
31+
pub fn random() -> Self {
32+
let mut bytes = [0u8; KEY_SIZE];
33+
getrandom(&mut bytes).expect("getrandom failure!");
34+
Self::new(bytes)
35+
}
36+
37+
/// Create key material from a 24-word BIP39 mnemonic phrase
38+
#[cfg(feature = "mnemonic")]
39+
pub fn from_mnemonic<S>(phrase: S, language: mnemonic::Language) -> Result<Self, Error>
40+
where
41+
S: AsRef<str>,
42+
{
43+
Ok(mnemonic::Phrase::new(phrase, language)?.into())
44+
}
45+
46+
/// Create new key material from a byte slice.
47+
///
48+
/// Byte slice is expected to have been generated by a cryptographically
49+
/// secure random number generator.
50+
pub fn from_bytes(slice: &[u8]) -> Result<Self, Error> {
51+
if slice.len() == KEY_SIZE {
52+
let mut bytes = [0u8; KEY_SIZE];
53+
bytes.copy_from_slice(slice);
54+
Ok(Self::new(bytes))
55+
} else {
56+
Err(Error)
57+
}
58+
}
59+
60+
/// Import existing key material - must be uniformly random!
61+
pub fn new(bytes: [u8; KEY_SIZE]) -> KeyMaterial {
62+
KeyMaterial(bytes)
63+
}
64+
65+
/// Borrow the key material as a byte slice
66+
pub fn as_bytes(&self) -> &[u8] {
67+
&self.0
68+
}
69+
70+
/// Derive an output key from the given input key material
71+
pub fn derive_subkey<P>(self, path: P) -> Self
72+
where
73+
P: AsRef<Path>,
74+
{
75+
let component_count = path.as_ref().components().count();
76+
77+
path.as_ref()
78+
.components()
79+
.enumerate()
80+
.fold(self, |parent_key, (i, component)| {
81+
let mut hmac = Hmac::<Sha512>::new_varkey(parent_key.as_bytes()).unwrap();
82+
hmac.input(component.as_bytes());
83+
84+
let mut hmac_result = hmac.result().code();
85+
let (secret_key, chain_code) = hmac_result.split_at_mut(KEY_SIZE);
86+
let mut child_key = [0u8; KEY_SIZE];
87+
88+
if i < component_count - 1 {
89+
// Use chain code for all but the last element
90+
child_key.copy_from_slice(chain_code);
91+
} else {
92+
// Use secret key for the last element
93+
child_key.copy_from_slice(secret_key);
94+
}
95+
96+
secret_key.zeroize();
97+
chain_code.zeroize();
98+
99+
KeyMaterial(child_key)
100+
})
101+
}
102+
103+
/// Serialize this `KeyMaterial` as a BIP39 mnemonic phrase
104+
#[cfg(feature = "mnemonic")]
105+
pub fn to_mnemonic(&self, language: mnemonic::Language) -> mnemonic::Phrase {
106+
mnemonic::Phrase::from_key_material(self, language)
107+
}
108+
}
109+
110+
impl From<[u8; KEY_SIZE]> for KeyMaterial {
111+
fn from(bytes: [u8; KEY_SIZE]) -> Self {
112+
Self::new(bytes)
113+
}
114+
}
115+
116+
impl<'a> TryFrom<&'a [u8]> for KeyMaterial {
117+
type Error = Error;
118+
119+
fn try_from(slice: &'a [u8]) -> Result<Self, Error> {
120+
Self::from_bytes(slice)
121+
}
122+
}

0 commit comments

Comments
 (0)