Skip to content

Commit

Permalink
Add ExtendedPrivateKey::new_from_private_key()
Browse files Browse the repository at this point in the history
  • Loading branch information
fjarri committed Jul 12, 2024
1 parent f98d4cc commit 880b236
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 46 deletions.
31 changes: 5 additions & 26 deletions bip32/src/extended_key/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,37 +88,16 @@ where
/// Derive a child key for a particular [`ChildNumber`].
pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self> {
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
// 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 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,
};

Expand Down
25 changes: 7 additions & 18 deletions bip32/src/extended_key/public_key.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -55,27 +54,17 @@ where

/// Derive a child key for a particular [`ChildNumber`].
pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self> {
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 (tweak, chain_code) = self
.public_key
.derive_tweak(&self.attrs.chain_code, child_number)?;

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 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,
};

Expand Down
49 changes: 48 additions & 1 deletion bip32/src/private_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Trait for deriving child keys on a given type.
use crate::{PublicKey, Result, KEY_SIZE};
use crate::{ChainCode, ChildNumber, HmacSha512, PublicKey, Result, KEY_SIZE};
use hmac::Mac;

#[cfg(feature = "secp256k1")]
use crate::{Error, XPrv};
Expand All @@ -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")]
Expand All @@ -41,6 +79,15 @@ impl PrivateKey for k256::SecretKey {
}

fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self> {
// We should technically loop here if a `secret_key` 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 child_scalar =
Option::<k256::NonZeroScalar>::from(k256::NonZeroScalar::from_repr(other.into()))
.ok_or(Error::Crypto)?;
Expand Down
52 changes: 51 additions & 1 deletion bip32/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Trait for deriving child keys on a given type.
use crate::{KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE};
use crate::{
ChainCode, ChildNumber, HmacSha512, KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE,
};
use hmac::Mac;
use ripemd::Ripemd160;
use sha2::{Digest, Sha256};

Expand Down Expand Up @@ -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")]
Expand All @@ -49,6 +90,15 @@ impl PublicKey for k256::PublicKey {
}

fn derive_child(&self, other: PrivateKeyBytes) -> Result<Self> {
// We should technically loop here if a `secret_key` 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 child_scalar =
Option::<k256::NonZeroScalar>::from(k256::NonZeroScalar::from_repr(other.into()))
.ok_or(Error::Crypto)?;
Expand Down

0 comments on commit 880b236

Please sign in to comment.