Add derive_* methods to Extended*Key

- Add derive_pub to ExtendedPubKey
- Add derive_priv to ExtendedPrivKey
- Removed from_path from ExtendedPrivKey as it is superseded by
  derive_priv
- Add checking of derive_pub and derive_priv to test_path
- Add checking of correct error when invoking ckd_pub on a hardened
  ChildNumber
- Add test vector 3 from BIP32 specification
This commit is contained in:
Carl Dong 2018-08-12 17:01:14 -07:00
parent bc7125e955
commit 60577f286d
1 changed files with 67 additions and 7 deletions

View File

@ -246,12 +246,15 @@ impl ExtendedPrivKey {
}) })
} }
/// Creates a privkey from a path /// Attempts to derive an extended private key from a path.
pub fn from_path(secp: &Secp256k1, master: &ExtendedPrivKey, path: &[ChildNumber]) pub fn derive_priv(
-> Result<ExtendedPrivKey, Error> { &self,
let mut sk = *master; secp: &Secp256k1,
for &num in path.iter() { cnums: &[ChildNumber],
sk = sk.ckd_priv(secp, num)?; ) -> Result<ExtendedPrivKey, Error> {
let mut sk: ExtendedPrivKey = *self;
for cnum in cnums {
sk = sk.ckd_priv(secp, *cnum)?;
} }
Ok(sk) Ok(sk)
} }
@ -326,6 +329,19 @@ impl ExtendedPubKey {
} }
} }
/// Attempts to derive an extended public key from a path.
pub fn derive_pub(
&self,
secp: &Secp256k1,
cnums: &[ChildNumber],
) -> Result<ExtendedPubKey, Error> {
let mut pk: ExtendedPubKey = *self;
for cnum in cnums {
pk = pk.ckd_pub(secp, *cnum)?
}
Ok(pk)
}
/// Compute the scalar tweak added to this key to get a child key /// Compute the scalar tweak added to this key to get a child key
pub fn ckd_pub_tweak(&self, secp: &Secp256k1, i: ChildNumber) -> Result<(SecretKey, ChainCode), Error> { pub fn ckd_pub_tweak(&self, secp: &Secp256k1, i: ChildNumber) -> Result<(SecretKey, ChainCode), Error> {
match i { match i {
@ -502,6 +518,7 @@ mod tests {
use super::{ChildNumber, ExtendedPrivKey, ExtendedPubKey}; use super::{ChildNumber, ExtendedPrivKey, ExtendedPubKey};
use super::ChildNumber::{Hardened, Normal}; use super::ChildNumber::{Hardened, Normal};
use super::Error;
fn test_path(secp: &Secp256k1, fn test_path(secp: &Secp256k1,
network: Network, network: Network,
@ -512,7 +529,28 @@ mod tests {
let mut sk = ExtendedPrivKey::new_master(secp, network, seed).unwrap(); let mut sk = ExtendedPrivKey::new_master(secp, network, seed).unwrap();
let mut pk = ExtendedPubKey::from_private(secp, &sk); let mut pk = ExtendedPubKey::from_private(secp, &sk);
// Derive keys, checking hardened and non-hardened derivation
// Check derivation convenience method for ExtendedPrivKey
assert_eq!(
&sk.derive_priv(secp, path).unwrap().to_string()[..],
expected_sk
);
// Check derivation convenience method for ExtendedPubKey, should error
// appropriately if any ChildNumber is hardened
if path.iter().any(|cnum| cnum.is_hardened()) {
assert_eq!(
pk.derive_pub(secp, path),
Err(Error::CannotDeriveFromHardenedKey)
);
} else {
assert_eq!(
&pk.derive_pub(secp, path).unwrap().to_string()[..],
expected_pk
);
}
// Derive keys, checking hardened and non-hardened derivation one-by-one
for &num in path.iter() { for &num in path.iter() {
sk = sk.ckd_priv(secp, num).unwrap(); sk = sk.ckd_priv(secp, num).unwrap();
match num { match num {
@ -522,6 +560,10 @@ mod tests {
assert_eq!(pk, pk2); assert_eq!(pk, pk2);
} }
Hardened {..} => { Hardened {..} => {
assert_eq!(
pk.ckd_pub(secp, num),
Err(Error::CannotDeriveFromHardenedKey)
);
pk = ExtendedPubKey::from_private(secp, &sk); pk = ExtendedPubKey::from_private(secp, &sk);
} }
} }
@ -541,6 +583,7 @@ mod tests {
fn test_vector_1() { fn test_vector_1() {
let secp = Secp256k1::new(); let secp = Secp256k1::new();
let seed = hex_decode("000102030405060708090a0b0c0d0e0f").unwrap(); let seed = hex_decode("000102030405060708090a0b0c0d0e0f").unwrap();
// m // m
test_path(&secp, Bitcoin, &seed, &[], test_path(&secp, Bitcoin, &seed, &[],
"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
@ -608,6 +651,23 @@ mod tests {
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"); "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt");
} }
#[test]
fn test_vector_3() {
let secp = Secp256k1::new();
let seed = hex_decode("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be").unwrap();
// m
test_path(&secp, Bitcoin, &seed, &[],
"xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
"xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13");
// m/0h
test_path(&secp, Bitcoin, &seed, &[ChildNumber::from_hardened_idx(0)],
"xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
"xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y");
}
#[test] #[test]
pub fn encode_decode_childnumber() { pub fn encode_decode_childnumber() {
serde_round_trip!(ChildNumber::from_normal_idx(0)); serde_round_trip!(ChildNumber::from_normal_idx(0));