hd_wallet/
edwards.rs

1//! Edwards HD derivation
2//!
3//! This module provides [`Edwards`] derivation as well as aliases for calling
4//! `<Edwards as HdWallet<_>>::*` methods for convenience when you don't need to support
5//! generic HD derivation algorithm.
6//!
7//! See [`Edwards`] docs to learn more about the derivation method.
8
9use generic_ec::{curves, Point, Scalar};
10use hmac::Mac;
11
12use crate::{
13    DeriveShift, DerivedShift, ExtendedKeyPair, ExtendedPublicKey, HardenedIndex, NonHardenedIndex,
14};
15
16type HmacSha512 = hmac::Hmac<sha2::Sha512>;
17
18/// HD derivation for Ed25519 curve
19///
20/// This type of derivation isn't defined in any known to us standards, but it can be often
21/// found in other libraries. It is secure and efficient.
22///
23/// ## Example
24/// ```rust
25/// use hd_wallet::{HdWallet, Edwards, curves::Ed25519};
26///
27/// # fn load_key() -> hd_wallet::ExtendedKeyPair<Ed25519> {
28/// #     hd_wallet::ExtendedSecretKey {
29/// #         secret_key: generic_ec::SecretScalar::random(&mut rand::rngs::OsRng),
30/// #         chain_code: rand::Rng::gen(&mut rand::rngs::OsRng),
31/// #     }.into()
32/// # }
33/// #
34/// let parent_key: hd_wallet::ExtendedKeyPair<Ed25519> = load_key();
35///
36/// let child_key_pair = Edwards::derive_child_key_pair_with_path(
37///     &parent_key,
38///     [1 + hd_wallet::H, 10],
39/// );
40/// # Ok::<(), Box<dyn std::error::Error>>(())
41/// ```
42pub struct Edwards;
43
44impl DeriveShift<curves::Ed25519> for Edwards {
45    fn derive_public_shift(
46        parent_public_key: &ExtendedPublicKey<curves::Ed25519>,
47        child_index: NonHardenedIndex,
48    ) -> DerivedShift<curves::Ed25519> {
49        let hmac = HmacSha512::new_from_slice(&parent_public_key.chain_code)
50            .expect("this never fails: hmac can handle keys of any size");
51        let i = hmac
52            .chain_update(parent_public_key.public_key.to_bytes(true))
53            // we append 0 byte to the public key for compatibility with other libs
54            .chain_update([0x00])
55            .chain_update(child_index.to_be_bytes())
56            .finalize()
57            .into_bytes();
58        Self::calculate_shift(parent_public_key, i)
59    }
60
61    fn derive_hardened_shift(
62        parent_key: &ExtendedKeyPair<curves::Ed25519>,
63        child_index: HardenedIndex,
64    ) -> DerivedShift<curves::Ed25519> {
65        let hmac = HmacSha512::new_from_slice(parent_key.chain_code())
66            .expect("this never fails: hmac can handle keys of any size");
67        let i = hmac
68            .chain_update([0x00])
69            .chain_update(parent_key.secret_key.secret_key.as_ref().to_be_bytes())
70            .chain_update(child_index.to_be_bytes())
71            .finalize()
72            .into_bytes();
73        Self::calculate_shift(&parent_key.public_key, i)
74    }
75}
76
77impl Edwards {
78    fn calculate_shift(
79        parent_public_key: &ExtendedPublicKey<curves::Ed25519>,
80        i: hmac::digest::Output<HmacSha512>,
81    ) -> DerivedShift<curves::Ed25519> {
82        let (i_left, i_right) = split_into_two_halves(&i);
83
84        let shift = Scalar::from_be_bytes_mod_order(i_left);
85        let child_pk = parent_public_key.public_key + Point::generator() * shift;
86
87        DerivedShift {
88            shift,
89            child_public_key: ExtendedPublicKey {
90                public_key: child_pk,
91                chain_code: (*i_right).into(),
92            },
93        }
94    }
95}
96
97/// Splits array `I` of 64 bytes into two arrays `I_L = I[..32]` and `I_R = I[32..]`
98fn split_into_two_halves(
99    i: &generic_array::GenericArray<u8, generic_array::typenum::U64>,
100) -> (
101    &generic_array::GenericArray<u8, generic_array::typenum::U32>,
102    &generic_array::GenericArray<u8, generic_array::typenum::U32>,
103) {
104    generic_array::sequence::Split::split(i)
105}
106
107super::create_aliases!(Edwards, edwards, hd_wallet::curves::Ed25519);