Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bip32: add PrivateKey::derive_tweak() and PublicKey::derive_tweak() #1186

Merged
merged 1 commit into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 6 additions & 18 deletions bip32/src/extended_key/private_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,11 @@ 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
// 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
Expand All @@ -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,
};

Expand Down
36 changes: 17 additions & 19 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,26 @@ 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 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,
};

Expand Down
42 changes: 40 additions & 2 deletions bip32/src/private_key.rs
Original file line number Diff line number Diff line change
@@ -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];
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 Down
45 changes: 43 additions & 2 deletions bip32/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -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},
};

Expand All @@ -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 Down
Loading