diff --git a/Cargo.toml b/Cargo.toml index 98f85ab..e160814 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "hd-wallet" version = "0.5.0" edition = "2021" license = "MIT OR Apache-2.0" -description = "HD deteministic wallets derivation, supports BIP32, SLIP10, and other standards" +description = "HD deteministic wallets derivation" repository = "https://github.com/dfns/slip-10" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/bip32.rs b/src/bip32.rs deleted file mode 100644 index 8b8e63c..0000000 --- a/src/bip32.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! BIP32-specific functions - -use generic_ec::core::Reduce; -use hmac::Mac as _; - -/// Derives a master key from the seed -/// -/// Seed must be 16-64 bytes long, otherwise an error is returned -pub fn derive_master_key( - seed: &[u8], -) -> Result, crate::errors::InvalidLength> { - if !(16 <= seed.len() && seed.len() <= 64) { - return Err(crate::errors::InvalidLength); - } - - let i = crate::HmacSha512::new_from_slice(b"Bitcoin seed") - .expect("this never fails: hmac can handle keys of any size") - .chain_update(seed) - .finalize() - .into_bytes(); - let (i_left, i_right) = crate::split_into_two_halves(&i); - let i_left: [u8; 32] = (*i_left).into(); - Ok(crate::ExtendedSecretKey { - secret_key: generic_ec::SecretScalar::new( - &mut generic_ec::Scalar::from_be_array_mod_order(&i_left), - ), - chain_code: (*i_right).into(), - }) -} diff --git a/src/errors.rs b/src/errors.rs index d17ec78..4c003e3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -55,15 +55,3 @@ impl std::error::Error for ParseChildIndexError { } } } - -/// Error indicating that HD derivation is not defined for given parent key and child path -/// -/// This error may occur in [Bip32](crate::Bip32) derivation, only with negligible probability -#[derive(Debug)] -pub struct UndefinedChildKey; - -impl fmt::Display for UndefinedChildKey { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("child key is not defined for given parent key and child path") - } -} diff --git a/src/lib.rs b/src/lib.rs index f374539..adeaa5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ //! # HD wallets derivation //! -//! This crate supports several standards for HD wallet derivation: -//! * [BIP32][bip32-spec], see [`Bip32`] -//! * [SLIP10][slip10-spec], see [slip10] module +//! This crate supports the following ways of HD derivation: +//! * [SLIP10][slip10-spec] (compatible with [BIP32][bip32-spec]), see [slip10] module //! * Non-standard [`Edwards`] derivation for ed25519 curve //! //! To perform HD derivation, use [`HdWallet`] trait. @@ -20,15 +19,15 @@ //! let child_key_pair = Slip10::derive_child_key_pair_with_path( //! &master_key_pair, //! [1 + hd_wallet::H, 10], -//! )?; +//! ); //! # Ok::<(), Box>(()) +//! ``` //! //! ### Features //! * `std`: enables std library support (mainly, it just implements [`Error`](std::error::Error) //! trait for the error types) //! * `curve-secp256k1`, `curve-secp256r1`, `curve-ed25519` add curve implementation into the crate //! [curves] module -//! ``` //! //! [slip10-spec]: https://github.com/satoshilabs/slips/blob/master/slip-0010.md //! [bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki @@ -53,7 +52,6 @@ use hmac::Mac as _; ))] pub use generic_ec::curves; -pub mod bip32; pub mod errors; pub mod slip10; @@ -318,7 +316,7 @@ impl<'de, E: Curve> serde::Deserialize<'de> for ExtendedKeyPair { } } -/// Type of HD wallet, like [BIP32] or [SLIP10] +/// Type of HD wallet, like [`Slip10`] pub trait HdWallet: DeriveShift { /// Derives child extended public key from parent extended public key /// @@ -336,14 +334,14 @@ pub trait HdWallet: DeriveShift { /// let derived_key = hd_wallet::Slip10::derive_child_public_key( /// &master_public_key, /// 1.try_into()?, - /// )?; + /// ); /// # Ok::<(), Box>(()) /// ``` fn derive_child_public_key( parent_public_key: &ExtendedPublicKey, child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr> { - Self::derive_public_shift(parent_public_key, child_index).map(|c| c.child_public_key) + ) -> ExtendedPublicKey { + Self::derive_public_shift(parent_public_key, child_index).child_public_key } /// Derives child key pair (extended secret key + public key) from parent key pair @@ -368,21 +366,21 @@ pub trait HdWallet: DeriveShift { fn derive_child_key_pair( parent_key: &ExtendedKeyPair, child_index: impl Into, - ) -> Result, Self::DeriveErr> { + ) -> ExtendedKeyPair { let child_index = child_index.into(); let shift = match child_index { - ChildIndex::Hardened(i) => Self::derive_hardened_shift(parent_key, i)?, - ChildIndex::NonHardened(i) => Self::derive_public_shift(&parent_key.public_key, i)?, + ChildIndex::Hardened(i) => Self::derive_hardened_shift(parent_key, i), + ChildIndex::NonHardened(i) => Self::derive_public_shift(&parent_key.public_key, i), }; let mut child_sk = &parent_key.secret_key.secret_key + shift.shift; let child_sk = SecretScalar::new(&mut child_sk); - Ok(ExtendedKeyPair { + ExtendedKeyPair { secret_key: ExtendedSecretKey { secret_key: child_sk, chain_code: shift.child_public_key.chain_code, }, public_key: shift.child_public_key, - }) + } } /// Derives a child key pair with specified derivation path from parent key pair @@ -408,21 +406,18 @@ pub trait HdWallet: DeriveShift { /// let child_key = hd_wallet::Slip10::try_derive_child_key_pair_with_path( /// &master_key_pair, /// child_indexes, - /// )??; + /// )?; /// # Ok::<_, Box>(()) /// ``` fn try_derive_child_key_pair_with_path( parent_key: &ExtendedKeyPair, path: impl IntoIterator, Err>>, - ) -> Result, Self::DeriveErr>, Err> { + ) -> Result, Err> { let mut derived_key = parent_key.clone(); for child_index in path { - derived_key = match Self::derive_child_key_pair(&derived_key, child_index?) { - Ok(k) => k, - Err(err) => return Ok(Err(err)), - }; + derived_key = Self::derive_child_key_pair(&derived_key, child_index?); } - Ok(Ok(derived_key)) + Ok(derived_key) } /// Derives a child key pair with specified derivation path from parent key pair @@ -444,13 +439,13 @@ pub trait HdWallet: DeriveShift { /// let child_key = hd_wallet::Slip10::derive_child_key_pair_with_path( /// &master_key_pair, /// [1, 10, 1 + hd_wallet::H], - /// )?; + /// ); /// # Ok::<(), Box>(()) /// ``` fn derive_child_key_pair_with_path( parent_key: &ExtendedKeyPair, path: impl IntoIterator>, - ) -> Result, Self::DeriveErr> { + ) -> ExtendedKeyPair { let result = Self::try_derive_child_key_pair_with_path( parent_key, path.into_iter().map(Ok::<_, core::convert::Infallible>), @@ -484,21 +479,18 @@ pub trait HdWallet: DeriveShift { /// let child_key = hd_wallet::Slip10::try_derive_child_public_key_with_path( /// &master_public_key, /// child_indexes, - /// )??; + /// )?; /// # Ok::<_, Box>(()) /// ``` fn try_derive_child_public_key_with_path( parent_public_key: &ExtendedPublicKey, path: impl IntoIterator>, - ) -> Result, Self::DeriveErr>, Err> { + ) -> Result, Err> { let mut derived_key = *parent_public_key; for child_index in path { - derived_key = match Self::derive_child_public_key(&derived_key, child_index?) { - Ok(k) => k, - Err(index_err) => return Ok(Err(index_err)), - }; + derived_key = Self::derive_child_public_key(&derived_key, child_index?); } - Ok(Ok(derived_key)) + Ok(derived_key) } /// Derives a child public key with specified derivation path @@ -520,13 +512,13 @@ pub trait HdWallet: DeriveShift { /// let child_key = hd_wallet::Slip10::derive_child_public_key_with_path( /// &master_public_key, /// [1.try_into()?, 10.try_into()?], - /// )?; + /// ); /// # Ok::<(), Box>(()) /// ``` fn derive_child_public_key_with_path( parent_public_key: &ExtendedPublicKey, path: impl IntoIterator, - ) -> Result, Self::DeriveErr> { + ) -> ExtendedPublicKey { let result = Self::try_derive_child_public_key_with_path( parent_public_key, path.into_iter().map(Ok::<_, core::convert::Infallible>), @@ -542,85 +534,21 @@ impl> HdWallet for S {} /// Core functionality of HD wallet derivation, everything is defined on top of it pub trait DeriveShift { - /// Shift derivation error - type DeriveErr; - /// Derives a shift for non-hardened child /// - /// Returns error if child key is not defined for given child index + /// We support only HD derivations that are always defined. This function may not panic. fn derive_public_shift( parent_public_key: &ExtendedPublicKey, child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr>; + ) -> DerivedShift; /// Derive a shift for hardened child /// - /// Returns error if child key is not defined for given child index + /// We support only HD derivations that are always defined. This function may not panic. fn derive_hardened_shift( parent_key: &ExtendedKeyPair, child_index: HardenedIndex, - ) -> Result, Self::DeriveErr>; -} - -/// BIP32 HD-wallet derivation -/// -/// [bip32-spec]: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki -pub struct Bip32; - -impl DeriveShift for Bip32 { - type DeriveErr = errors::UndefinedChildKey; - - fn derive_public_shift( - parent_public_key: &ExtendedPublicKey, - child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr> { - let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code) - .expect("this never fails: hmac can handle keys of any size"); - let i = hmac - .clone() - .chain_update(&parent_public_key.public_key.to_bytes(true)) - .chain_update(child_index.to_be_bytes()) - .finalize() - .into_bytes(); - Self::calculate_shift(&parent_public_key.public_key, i) - } - - fn derive_hardened_shift( - parent_key: &ExtendedKeyPair, - child_index: HardenedIndex, - ) -> Result, Self::DeriveErr> { - let hmac = HmacSha512::new_from_slice(parent_key.chain_code()) - .expect("this never fails: hmac can handle keys of any size"); - let i = hmac - .clone() - .chain_update([0x00]) - .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes()) - .chain_update(child_index.to_be_bytes()) - .finalize() - .into_bytes(); - Self::calculate_shift(&parent_key.public_key.public_key, i) - } -} - -impl Bip32 { - fn calculate_shift( - parent_public_key: &Point, - i: hmac::digest::Output, - ) -> Result, errors::UndefinedChildKey> { - let (i_left, i_right) = split_into_two_halves(&i); - let shift = Scalar::from_be_bytes(i_left).map_err(|_| errors::UndefinedChildKey)?; - let child_pk = - generic_ec::NonZero::from_point(parent_public_key + Point::generator() * shift) - .ok_or(errors::UndefinedChildKey)?; - - Ok(DerivedShift { - shift, - child_public_key: ExtendedPublicKey { - public_key: child_pk.into_inner(), - chain_code: (*i_right).into(), - }, - }) - } + ) -> DerivedShift; } /// SLIP10-like HD wallet derivation @@ -647,13 +575,10 @@ impl Bip32 { pub struct Slip10Like; impl DeriveShift for Slip10Like { - /// Slip10 derivation is always defined - type DeriveErr = core::convert::Infallible; - fn derive_public_shift( parent_public_key: &ExtendedPublicKey, child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code) .expect("this never fails: hmac can handle keys of any size"); let i = hmac @@ -662,18 +587,13 @@ impl DeriveShift for Slip10Like { .chain_update(child_index.to_be_bytes()) .finalize() .into_bytes(); - Ok(Self::calculate_shift( - &hmac, - parent_public_key, - *child_index, - i, - )) + Self::calculate_shift(&hmac, parent_public_key, *child_index, i) } fn derive_hardened_shift( parent_key: &ExtendedKeyPair, child_index: HardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { let hmac = HmacSha512::new_from_slice(parent_key.chain_code()) .expect("this never fails: hmac can handle keys of any size"); let i = hmac @@ -683,12 +603,7 @@ impl DeriveShift for Slip10Like { .chain_update(child_index.to_be_bytes()) .finalize() .into_bytes(); - Ok(Self::calculate_shift( - &hmac, - &parent_key.public_key, - *child_index, - i, - )) + Self::calculate_shift(&hmac, &parent_key.public_key, *child_index, i) } } @@ -735,33 +650,31 @@ pub struct Slip10; #[cfg(feature = "curve-secp256k1")] impl DeriveShift for Slip10 { - type DeriveErr = core::convert::Infallible; fn derive_public_shift( parent_public_key: &ExtendedPublicKey, child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { Slip10Like::derive_public_shift(parent_public_key, child_index) } fn derive_hardened_shift( parent_key: &ExtendedKeyPair, child_index: HardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { Slip10Like::derive_hardened_shift(parent_key, child_index) } } #[cfg(feature = "curve-secp256r1")] impl DeriveShift for Slip10 { - type DeriveErr = core::convert::Infallible; fn derive_public_shift( parent_public_key: &ExtendedPublicKey, child_index: NonHardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { Slip10Like::derive_public_shift(parent_public_key, child_index) } fn derive_hardened_shift( parent_key: &ExtendedKeyPair, child_index: HardenedIndex, - ) -> Result, Self::DeriveErr> { + ) -> DerivedShift { Slip10Like::derive_hardened_shift(parent_key, child_index) } } diff --git a/tests/slip10_test_vector.rs b/tests/slip10_test_vector.rs index d7ae440..c8cdb15 100644 --- a/tests/slip10_test_vector.rs +++ b/tests/slip10_test_vector.rs @@ -350,8 +350,7 @@ fn run_vector(v: &TestVector) { let key = hd_wallet::Slip10Like::derive_child_key_pair_with_path( &master_key_pair, derivation.path.iter().copied(), - ) - .unwrap_or_else(|x| match x {}); + ); assert_eq!(key.chain_code(), &derivation.expected_chain_code); assert_eq!(