use crate::{path::Path, Error, KEY_SIZE};
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::Sha512;
use zeroize::Zeroize;
#[cfg(feature = "bech32")]
use {alloc::string::String, subtle_encoding::bech32::Bech32, zeroize::Zeroizing};
#[cfg(feature = "mnemonic")]
use crate::mnemonic;
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct KeyMaterial([u8; KEY_SIZE]);
impl KeyMaterial {
pub fn random(mut rng: impl RngCore + CryptoRng) -> Self {
let mut bytes = [0u8; KEY_SIZE];
rng.fill_bytes(&mut bytes);
Self::new(bytes)
}
#[cfg(feature = "bech32")]
pub fn from_bech32<S>(encoded: S) -> Result<(String, Self), Error>
where
S: AsRef<str>,
{
let (hrp, mut key_bytes) = Bech32::default().decode(encoded).map_err(|_| Error)?;
let key_result = Self::from_bytes(&key_bytes);
key_bytes.zeroize();
key_result.map(|key| (hrp, key))
}
#[cfg(feature = "mnemonic")]
pub fn from_mnemonic<S>(phrase: S, language: mnemonic::Language) -> Result<Self, Error>
where
S: AsRef<str>,
{
Ok(mnemonic::Phrase::new(phrase, language)?.into())
}
pub fn from_bytes(slice: &[u8]) -> Result<Self, Error> {
if slice.len() == KEY_SIZE {
let mut bytes = [0u8; KEY_SIZE];
bytes.copy_from_slice(slice);
Ok(Self::new(bytes))
} else {
Err(Error)
}
}
pub fn new(bytes: [u8; KEY_SIZE]) -> KeyMaterial {
KeyMaterial(bytes)
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn derive_subkey<P>(self, path: P) -> Self
where
P: AsRef<Path>,
{
let component_count = path.as_ref().components().count();
path.as_ref()
.components()
.enumerate()
.fold(self, |parent_key, (i, component)| {
let mut hmac = Hmac::<Sha512>::new_from_slice(parent_key.as_bytes())
.expect("HMAC key size incorrect");
hmac.update(component.as_bytes());
let mut hmac_result = hmac.finalize().into_bytes();
let (secret_key, chain_code) = hmac_result.split_at_mut(KEY_SIZE);
let mut child_key = [0u8; KEY_SIZE];
if i < component_count - 1 {
child_key.copy_from_slice(chain_code);
} else {
child_key.copy_from_slice(secret_key);
}
secret_key.zeroize();
chain_code.zeroize();
KeyMaterial(child_key)
})
}
#[cfg(feature = "bech32")]
pub fn to_bech32<S>(&self, hrp: S) -> Zeroizing<String>
where
S: AsRef<str>,
{
let b32 = Bech32::default().encode(hrp, self.as_bytes());
Zeroizing::new(b32)
}
}
impl From<[u8; KEY_SIZE]> for KeyMaterial {
fn from(bytes: [u8; KEY_SIZE]) -> Self {
Self::new(bytes)
}
}
impl<'a> TryFrom<&'a [u8]> for KeyMaterial {
type Error = Error;
fn try_from(slice: &'a [u8]) -> Result<Self, Error> {
Self::from_bytes(slice)
}
}