diff --git a/bip32/src/extended_key/private_key.rs b/bip32/src/extended_key/private_key.rs index dc7ddcd3..cd8f7ff6 100644 --- a/bip32/src/extended_key/private_key.rs +++ b/bip32/src/extended_key/private_key.rs @@ -88,23 +88,11 @@ where /// Derive a child key for a particular [`ChildNumber`]. pub fn derive_child(&self, child_number: ChildNumber) -> Result { let depth = self.attrs.depth.checked_add(1).ok_or(Error::Depth)?; + let (tweak, chain_code) = self + .private_key + .derive_tweak(&self.attrs.chain_code, child_number)?; - let mut hmac = - HmacSha512::new_from_slice(&self.attrs.chain_code).map_err(|_| Error::Crypto)?; - - if child_number.is_hardened() { - hmac.update(&[0]); - hmac.update(&self.private_key.to_bytes()); - } else { - hmac.update(&self.private_key.public_key().to_bytes()); - } - - hmac.update(&child_number.to_bytes()); - - let result = hmac.finalize().into_bytes(); - let (child_key, chain_code) = result.split_at(KEY_SIZE); - - // We should technically loop here if a `secret_key` is zero or overflows + // We should technically loop here if the tweak is zero or overflows // the order of the underlying elliptic curve group, incrementing the // index, however per "Child key derivation (CKD) functions": // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions @@ -113,12 +101,12 @@ where // // ...so instead, we simply return an error if this were ever to happen, // as the chances of it happening are vanishingly small. - let private_key = self.private_key.derive_child(child_key.try_into()?)?; + let private_key = self.private_key.derive_child(tweak)?; let attrs = ExtendedKeyAttrs { parent_fingerprint: self.private_key.public_key().fingerprint(), child_number, - chain_code: chain_code.try_into()?, + chain_code, depth, }; diff --git a/bip32/src/extended_key/public_key.rs b/bip32/src/extended_key/public_key.rs index 50de4ae7..021da47c 100644 --- a/bip32/src/extended_key/public_key.rs +++ b/bip32/src/extended_key/public_key.rs @@ -1,11 +1,10 @@ //! Extended public keys use crate::{ - ChildNumber, Error, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, HmacSha512, - KeyFingerprint, Prefix, PrivateKey, PublicKey, PublicKeyBytes, Result, KEY_SIZE, + ChildNumber, Error, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, KeyFingerprint, Prefix, + PrivateKey, PublicKey, PublicKeyBytes, Result, }; use core::str::FromStr; -use hmac::Mac; #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; @@ -55,27 +54,26 @@ where /// Derive a child key for a particular [`ChildNumber`]. pub fn derive_child(&self, child_number: ChildNumber) -> Result { - if child_number.is_hardened() { - // Cannot derive child public keys for hardened `ChildNumber`s - return Err(Error::ChildNumber); - } - let depth = self.attrs.depth.checked_add(1).ok_or(Error::Depth)?; - - let mut hmac = - HmacSha512::new_from_slice(&self.attrs.chain_code).map_err(|_| Error::Crypto)?; - - hmac.update(&self.public_key.to_bytes()); - hmac.update(&child_number.to_bytes()); - - let result = hmac.finalize().into_bytes(); - let (child_key, chain_code) = result.split_at(KEY_SIZE); - let public_key = self.public_key.derive_child(child_key.try_into()?)?; + let (tweak, chain_code) = self + .public_key + .derive_tweak(&self.attrs.chain_code, child_number)?; + + // We should technically loop here if the tweak is zero or overflows + // the order of the underlying elliptic curve group, incrementing the + // index, however per "Child key derivation (CKD) functions": + // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions + // + // > "Note: this has probability lower than 1 in 2^127." + // + // ...so instead, we simply return an error if this were ever to happen, + // as the chances of it happening are vanishingly small. + let public_key = self.public_key.derive_child(tweak)?; let attrs = ExtendedKeyAttrs { parent_fingerprint: self.public_key.fingerprint(), child_number, - chain_code: chain_code.try_into()?, + chain_code, depth, }; diff --git a/bip32/src/private_key.rs b/bip32/src/private_key.rs index 8af0f52e..95b5a900 100644 --- a/bip32/src/private_key.rs +++ b/bip32/src/private_key.rs @@ -1,9 +1,10 @@ //! Trait for deriving child keys on a given type. -use crate::{PublicKey, Result, KEY_SIZE}; +use crate::{ChainCode, ChildNumber, Error, HmacSha512, PublicKey, Result, KEY_SIZE}; +use hmac::Mac; #[cfg(feature = "secp256k1")] -use crate::{Error, XPrv}; +use crate::XPrv; /// Bytes which represent a private key. pub type PrivateKeyBytes = [u8; KEY_SIZE]; @@ -26,6 +27,43 @@ pub trait PrivateKey: Sized { /// Get the [`Self::PublicKey`] that corresponds to this private key. fn public_key(&self) -> Self::PublicKey; + + /// Derive a tweak value that can be used to generate the child key (see [`derive_child`]). + /// + /// The `chain_code` is either a newly initialized one, + /// or one obtained from the previous invocation of `derive_tweak()` + /// (for a multi-level derivation). + /// + /// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so + /// in a cryptographically safe way. + /// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]). + fn derive_tweak( + &self, + chain_code: &ChainCode, + child_number: ChildNumber, + ) -> Result<(PrivateKeyBytes, ChainCode)> { + let mut hmac = HmacSha512::new_from_slice(chain_code).map_err(|_| Error::Crypto)?; + + if child_number.is_hardened() { + hmac.update(&[0]); + hmac.update(&self.to_bytes()); + } else { + hmac.update(&self.public_key().to_bytes()); + } + + hmac.update(&child_number.to_bytes()); + + let result = hmac.finalize().into_bytes(); + let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE); + + // Note that at this point we are only asserting that `tweak_bytes` have the expected size. + // Checking if it actually fits the curve scalar happens in `derive_child()`. + let tweak = tweak_bytes.try_into()?; + + let chain_code = chain_code_bytes.try_into()?; + + Ok((tweak, chain_code)) + } } #[cfg(feature = "secp256k1")] diff --git a/bip32/src/public_key.rs b/bip32/src/public_key.rs index 6e3de796..a6e0121a 100644 --- a/bip32/src/public_key.rs +++ b/bip32/src/public_key.rs @@ -1,12 +1,15 @@ //! Trait for deriving child keys on a given type. -use crate::{KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE}; +use crate::{ + ChainCode, ChildNumber, Error, HmacSha512, KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE, +}; +use hmac::Mac; use ripemd::Ripemd160; use sha2::{Digest, Sha256}; #[cfg(feature = "secp256k1")] use { - crate::{Error, XPub}, + crate::XPub, k256::elliptic_curve::{group::prime::PrimeCurveAffine, sec1::ToEncodedPoint}, }; @@ -33,6 +36,44 @@ pub trait PublicKey: Sized { let digest = Ripemd160::digest(Sha256::digest(self.to_bytes())); digest[..4].try_into().expect("digest truncated") } + + /// Derive a tweak value that can be used to generate the child key (see [`derive_child`]). + /// + /// The `chain_code` is either a newly initialized one, + /// or one obtained from the previous invocation of `derive_tweak()` + /// (for a multi-level derivation). + /// + /// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so + /// in a cryptographically safe way. + /// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]). + /// + /// **Note:** `child_number` cannot be a hardened one (will result in an error). + fn derive_tweak( + &self, + chain_code: &ChainCode, + child_number: ChildNumber, + ) -> Result<(PrivateKeyBytes, ChainCode)> { + if child_number.is_hardened() { + // Cannot derive child public keys for hardened `ChildNumber`s + return Err(Error::ChildNumber); + } + + let mut hmac = HmacSha512::new_from_slice(chain_code).map_err(|_| Error::Crypto)?; + + hmac.update(&self.to_bytes()); + hmac.update(&child_number.to_bytes()); + + let result = hmac.finalize().into_bytes(); + let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE); + + // Note that at this point we are only asserting that `tweak_bytes` have the expected size. + // Checking if it actually fits the curve scalar happens in `derive_child()`. + let tweak = tweak_bytes.try_into()?; + + let chain_code = chain_code_bytes.try_into()?; + + Ok((tweak, chain_code)) + } } #[cfg(feature = "secp256k1")]