Skip to content

Commit c6b4711

Browse files
authored
bip32: add PrivateKey::derive_tweak() and PublicKey::derive_tweak() (#1186)
Looks good, thanks!
1 parent f98d4cc commit c6b4711

File tree

4 files changed

+106
-41
lines changed

4 files changed

+106
-41
lines changed

bip32/src/extended_key/private_key.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,11 @@ where
8888
/// Derive a child key for a particular [`ChildNumber`].
8989
pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self> {
9090
let depth = self.attrs.depth.checked_add(1).ok_or(Error::Depth)?;
91+
let (tweak, chain_code) = self
92+
.private_key
93+
.derive_tweak(&self.attrs.chain_code, child_number)?;
9194

92-
let mut hmac =
93-
HmacSha512::new_from_slice(&self.attrs.chain_code).map_err(|_| Error::Crypto)?;
94-
95-
if child_number.is_hardened() {
96-
hmac.update(&[0]);
97-
hmac.update(&self.private_key.to_bytes());
98-
} else {
99-
hmac.update(&self.private_key.public_key().to_bytes());
100-
}
101-
102-
hmac.update(&child_number.to_bytes());
103-
104-
let result = hmac.finalize().into_bytes();
105-
let (child_key, chain_code) = result.split_at(KEY_SIZE);
106-
107-
// We should technically loop here if a `secret_key` is zero or overflows
95+
// We should technically loop here if the tweak is zero or overflows
10896
// the order of the underlying elliptic curve group, incrementing the
10997
// index, however per "Child key derivation (CKD) functions":
11098
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
@@ -113,12 +101,12 @@ where
113101
//
114102
// ...so instead, we simply return an error if this were ever to happen,
115103
// as the chances of it happening are vanishingly small.
116-
let private_key = self.private_key.derive_child(child_key.try_into()?)?;
104+
let private_key = self.private_key.derive_child(tweak)?;
117105

118106
let attrs = ExtendedKeyAttrs {
119107
parent_fingerprint: self.private_key.public_key().fingerprint(),
120108
child_number,
121-
chain_code: chain_code.try_into()?,
109+
chain_code,
122110
depth,
123111
};
124112

bip32/src/extended_key/public_key.rs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
//! Extended public keys
22
33
use crate::{
4-
ChildNumber, Error, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, HmacSha512,
5-
KeyFingerprint, Prefix, PrivateKey, PublicKey, PublicKeyBytes, Result, KEY_SIZE,
4+
ChildNumber, Error, ExtendedKey, ExtendedKeyAttrs, ExtendedPrivateKey, KeyFingerprint, Prefix,
5+
PrivateKey, PublicKey, PublicKeyBytes, Result,
66
};
77
use core::str::FromStr;
8-
use hmac::Mac;
98

109
#[cfg(feature = "alloc")]
1110
use alloc::string::{String, ToString};
@@ -55,27 +54,26 @@ where
5554

5655
/// Derive a child key for a particular [`ChildNumber`].
5756
pub fn derive_child(&self, child_number: ChildNumber) -> Result<Self> {
58-
if child_number.is_hardened() {
59-
// Cannot derive child public keys for hardened `ChildNumber`s
60-
return Err(Error::ChildNumber);
61-
}
62-
6357
let depth = self.attrs.depth.checked_add(1).ok_or(Error::Depth)?;
64-
65-
let mut hmac =
66-
HmacSha512::new_from_slice(&self.attrs.chain_code).map_err(|_| Error::Crypto)?;
67-
68-
hmac.update(&self.public_key.to_bytes());
69-
hmac.update(&child_number.to_bytes());
70-
71-
let result = hmac.finalize().into_bytes();
72-
let (child_key, chain_code) = result.split_at(KEY_SIZE);
73-
let public_key = self.public_key.derive_child(child_key.try_into()?)?;
58+
let (tweak, chain_code) = self
59+
.public_key
60+
.derive_tweak(&self.attrs.chain_code, child_number)?;
61+
62+
// We should technically loop here if the tweak is zero or overflows
63+
// the order of the underlying elliptic curve group, incrementing the
64+
// index, however per "Child key derivation (CKD) functions":
65+
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#child-key-derivation-ckd-functions
66+
//
67+
// > "Note: this has probability lower than 1 in 2^127."
68+
//
69+
// ...so instead, we simply return an error if this were ever to happen,
70+
// as the chances of it happening are vanishingly small.
71+
let public_key = self.public_key.derive_child(tweak)?;
7472

7573
let attrs = ExtendedKeyAttrs {
7674
parent_fingerprint: self.public_key.fingerprint(),
7775
child_number,
78-
chain_code: chain_code.try_into()?,
76+
chain_code,
7977
depth,
8078
};
8179

bip32/src/private_key.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! Trait for deriving child keys on a given type.
22
3-
use crate::{PublicKey, Result, KEY_SIZE};
3+
use crate::{ChainCode, ChildNumber, Error, HmacSha512, PublicKey, Result, KEY_SIZE};
4+
use hmac::Mac;
45

56
#[cfg(feature = "secp256k1")]
6-
use crate::{Error, XPrv};
7+
use crate::XPrv;
78

89
/// Bytes which represent a private key.
910
pub type PrivateKeyBytes = [u8; KEY_SIZE];
@@ -26,6 +27,43 @@ pub trait PrivateKey: Sized {
2627

2728
/// Get the [`Self::PublicKey`] that corresponds to this private key.
2829
fn public_key(&self) -> Self::PublicKey;
30+
31+
/// Derive a tweak value that can be used to generate the child key (see [`derive_child`]).
32+
///
33+
/// The `chain_code` is either a newly initialized one,
34+
/// or one obtained from the previous invocation of `derive_tweak()`
35+
/// (for a multi-level derivation).
36+
///
37+
/// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so
38+
/// in a cryptographically safe way.
39+
/// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]).
40+
fn derive_tweak(
41+
&self,
42+
chain_code: &ChainCode,
43+
child_number: ChildNumber,
44+
) -> Result<(PrivateKeyBytes, ChainCode)> {
45+
let mut hmac = HmacSha512::new_from_slice(chain_code).map_err(|_| Error::Crypto)?;
46+
47+
if child_number.is_hardened() {
48+
hmac.update(&[0]);
49+
hmac.update(&self.to_bytes());
50+
} else {
51+
hmac.update(&self.public_key().to_bytes());
52+
}
53+
54+
hmac.update(&child_number.to_bytes());
55+
56+
let result = hmac.finalize().into_bytes();
57+
let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE);
58+
59+
// Note that at this point we are only asserting that `tweak_bytes` have the expected size.
60+
// Checking if it actually fits the curve scalar happens in `derive_child()`.
61+
let tweak = tweak_bytes.try_into()?;
62+
63+
let chain_code = chain_code_bytes.try_into()?;
64+
65+
Ok((tweak, chain_code))
66+
}
2967
}
3068

3169
#[cfg(feature = "secp256k1")]

bip32/src/public_key.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
//! Trait for deriving child keys on a given type.
22
3-
use crate::{KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE};
3+
use crate::{
4+
ChainCode, ChildNumber, Error, HmacSha512, KeyFingerprint, PrivateKeyBytes, Result, KEY_SIZE,
5+
};
6+
use hmac::Mac;
47
use ripemd::Ripemd160;
58
use sha2::{Digest, Sha256};
69

710
#[cfg(feature = "secp256k1")]
811
use {
9-
crate::{Error, XPub},
12+
crate::XPub,
1013
k256::elliptic_curve::{group::prime::PrimeCurveAffine, sec1::ToEncodedPoint},
1114
};
1215

@@ -33,6 +36,44 @@ pub trait PublicKey: Sized {
3336
let digest = Ripemd160::digest(Sha256::digest(self.to_bytes()));
3437
digest[..4].try_into().expect("digest truncated")
3538
}
39+
40+
/// Derive a tweak value that can be used to generate the child key (see [`derive_child`]).
41+
///
42+
/// The `chain_code` is either a newly initialized one,
43+
/// or one obtained from the previous invocation of `derive_tweak()`
44+
/// (for a multi-level derivation).
45+
///
46+
/// **Warning:** make sure that if you are creating a new `chain_code`, you are doing so
47+
/// in a cryptographically safe way.
48+
/// Normally this would be done according to BIP-39 (within [`ExtendedPrivateKey::new`]).
49+
///
50+
/// **Note:** `child_number` cannot be a hardened one (will result in an error).
51+
fn derive_tweak(
52+
&self,
53+
chain_code: &ChainCode,
54+
child_number: ChildNumber,
55+
) -> Result<(PrivateKeyBytes, ChainCode)> {
56+
if child_number.is_hardened() {
57+
// Cannot derive child public keys for hardened `ChildNumber`s
58+
return Err(Error::ChildNumber);
59+
}
60+
61+
let mut hmac = HmacSha512::new_from_slice(chain_code).map_err(|_| Error::Crypto)?;
62+
63+
hmac.update(&self.to_bytes());
64+
hmac.update(&child_number.to_bytes());
65+
66+
let result = hmac.finalize().into_bytes();
67+
let (tweak_bytes, chain_code_bytes) = result.split_at(KEY_SIZE);
68+
69+
// Note that at this point we are only asserting that `tweak_bytes` have the expected size.
70+
// Checking if it actually fits the curve scalar happens in `derive_child()`.
71+
let tweak = tweak_bytes.try_into()?;
72+
73+
let chain_code = chain_code_bytes.try_into()?;
74+
75+
Ok((tweak, chain_code))
76+
}
3677
}
3778

3879
#[cfg(feature = "secp256k1")]

0 commit comments

Comments
 (0)