use crate::{DerivationIndex, DerivationPath, ExtendedPublicKey, PrivateKey, PublicKey}; use hmac::{Hmac, Mac}; use serde::{Deserialize, Serialize}; use sha2::Sha512; use thiserror::Error; const KEY_SIZE: usize = 256; /// Errors associated with creating or deriving Extended Private Keys. #[derive(Error, Clone, Debug)] pub enum Error { /// The seed has an unsuitable length; supported lengths are 16 bytes, 32 bytes, or 64 bytes. #[error("Seed had an unsuitable length: {0}")] BadSeedLength(usize), /// The maximum depth for key derivation has been reached. The supported maximum depth is 255. #[error("Reached maximum depth for key derivation")] Depth, /// This should never happen. HMAC keys should be able to take any size input. #[error("Invalid length for HMAC key while generating master key (report me!)")] HmacInvalidLength(#[from] hmac::digest::InvalidLength), /// An unknown error occurred while deriving a child key. #[error("Unknown error while deriving child key")] Derivation, /// The algorithm used mandates hardened derivation only. #[error("The algorithm used mandates hardened derivation only")] HardenedDerivationRequired, /// The given slice was of an inappropriate size to create a Private Key. #[error("The given slice was of an inappropriate size to create a Private Key")] InvalidSliceError(#[from] std::array::TryFromSliceError), } type Result = std::result::Result; type ChainCode = [u8; 32]; type HmacSha512 = Hmac; /// Extended private keys derived using BIP-0032. /// /// Generic over types implementing [`PrivateKey`]. #[derive(Clone, Serialize, Deserialize)] pub struct ExtendedPrivateKey { /// The internal private key data. private_key: K, depth: u8, chain_code: ChainCode, } impl std::fmt::Debug for ExtendedPrivateKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ExtendedPrivateKey") .field("private_key", &"obscured") .field("depth", &self.depth) .field("chain_code", &self.chain_code) .finish() } } impl ExtendedPrivateKey where K: PrivateKey + Clone, { /// Generate a new [`ExtendedPrivateKey`] from a seed, ideally from a 12-word or 24-word /// mnemonic, but may take 16-byte seeds. /// /// # Panics /// /// The method performs unchecked `try_into()` operations on a constant-sized slice. /// /// # Errors /// /// An error may be returned if: /// * The given seed had an incorrect length. /// * A `HmacSha512` can't be constructed - this should be impossible. pub fn new(seed: impl AsRef<[u8]>) -> Result { Self::new_internal(seed.as_ref()) } fn new_internal(seed: &[u8]) -> Result { let len = seed.len(); if ![16, 32, 64].contains(&len) { return Err(Error::BadSeedLength(len)); } let hash = HmacSha512::new_from_slice(&K::key().bytes().collect::>())? .chain_update(seed) .finalize() .into_bytes(); let (private_key, chain_code) = hash.split_at(KEY_SIZE / 8); Self::new_from_parts( private_key, 0, // Checked: chain_code is always the same length, hash is static size chain_code.try_into().expect("Invalid chain code length"), ) } pub fn new_from_parts(seed: &[u8], depth: u8, chain_code: [u8; 32]) -> Result { Ok(Self { private_key: K::from_bytes(seed.try_into()?), depth, chain_code, }) } /// Returns a reference to the [`PrivateKey`]. pub fn private_key(&self) -> &K { &self.private_key } pub fn extended_public_key(&self) -> ExtendedPublicKey { ExtendedPublicKey::new(self.public_key(), self.chain_code) } /// Return a public key for the current [`PrivateKey`]. pub fn public_key(&self) -> K::PublicKey { self.private_key.public_key() } /// Returns the current depth. pub fn depth(&self) -> u8 { self.depth } /// Returns a copy of the current chain code. pub fn chain_code(&self) -> [u8; 32] { self.chain_code } /// Derive a child using the given [`DerivationPath`]. /// /// # Errors /// /// An error may be returned under the same circumstances as /// [`ExtendedPrivateKey::derive_child`]. pub fn derive_path(&self, path: &DerivationPath) -> Result { if path.path.is_empty() { Ok(self.clone()) } else { path.iter() .try_fold(self.clone(), |key, index| key.derive_child(index)) } } /// Derive a child with a given [`DerivationIndex`]. /// /// # Panics /// /// The method performs unchecked `try_into()` operations on constant-sized slice. /// /// # Errors /// /// An error may be returned if: /// /// * The depth exceeds the maximum depth [`u8::MAX`]. /// * A `HmacSha512` can't be constructed - this should be impossible. /// * Deriving a child key fails. Check the documentation for your [`PrivateKey`]. pub fn derive_child(&self, index: &DerivationIndex) -> Result { let depth = self.depth.checked_add(1).ok_or(Error::Depth)?; let mut hmac = HmacSha512::new_from_slice(&self.chain_code).map_err(Error::HmacInvalidLength)?; if index.is_hardened() { hmac.update(&[0]); hmac.update(&self.private_key.to_bytes()); } else if !K::requires_hardened_derivation() { hmac.update(&self.private_key.public_key().to_bytes()); } else { return Err(Error::HardenedDerivationRequired); } hmac.update(&index.to_bytes()); let result = hmac.finalize().into_bytes(); let (private_key, chain_code) = result.split_at(KEY_SIZE / 8); let private_key = self .private_key .derive_child( &private_key .try_into() .expect("Invalid length for private key"), ) .map_err(|_| Error::Derivation)?; Ok(Self { private_key, depth, chain_code: chain_code .try_into() .expect("Invalid length for chain code"), }) } }