ethers_wallet_rs/hd_wallet/
bip32.rs

1use std::io::Write;
2
3use ethers_hash_rs::pbkdf2::pbkdf2_hmac_array;
4use hmac::{Hmac, Mac};
5use k256::ecdsa::SigningKey;
6use sha2::Sha512;
7use thiserror::Error;
8
9use super::bip44::{Bip44Error, Bip44Node, Bip44Path};
10
11#[derive(Debug, Error)]
12pub enum Bip32Error {
13    #[error("Parse bip44 path error,{0}")]
14    Bip44(Bip44Error),
15}
16
17fn mnemonic_to_send<M, P>(mnemonic: M, password: P) -> [u8; 64]
18where
19    M: AsRef<[u8]>,
20    P: AsRef<[u8]>,
21{
22    let mut salt = "mnemonic".as_bytes().to_vec();
23
24    salt.append(&mut password.as_ref().to_vec());
25
26    pbkdf2_hmac_array::<Sha512, 64>(mnemonic.as_ref(), salt.as_ref(), 2048)
27}
28
29pub struct DriveKey {
30    pub public_key: [u8; 33],
31    pub private_key: [u8; 32],
32    pub chain_code: [u8; 32],
33    pub node: Option<Bip44Node>,
34}
35
36type HmacSha512 = Hmac<Sha512>;
37
38impl DriveKey {
39    pub fn new<M, P>(mnemonic: M, password: P) -> Self
40    where
41        M: AsRef<[u8]>,
42        P: AsRef<[u8]>,
43    {
44        let seed = mnemonic_to_send(mnemonic, password);
45
46        Self::new_master_key(seed)
47    }
48
49    pub(crate) fn new_master_key(seed: [u8; 64]) -> Self {
50        let mut h = HmacSha512::new_from_slice(b"Bitcoin seed").expect("Create HmacSha512");
51
52        h.write(&seed).expect("Write hmac data");
53
54        let intermediary = h.finalize().into_bytes();
55
56        let key_data = &intermediary[..32];
57        let chain_code = &intermediary[32..];
58
59        let key = SigningKey::from_bytes(key_data).expect("Create ecdsa signing key");
60
61        let public_key = key.verifying_key().to_encoded_point(true);
62
63        DriveKey {
64            public_key: public_key
65                .as_bytes()
66                .try_into()
67                .expect("Convert public key"),
68            private_key: key_data.try_into().expect("Convert private key"),
69            chain_code: chain_code.try_into().expect("Convert chain code"),
70            node: None,
71        }
72    }
73
74    pub(crate) fn child_key(&self, node: Bip44Node) -> Self {
75        let mut h = HmacSha512::new_from_slice(&self.chain_code).expect("HmacSha512 new");
76
77        match node {
78            Bip44Node::Hardened(_) => {
79                h.write(&[0]).expect("Hmac write");
80
81                h.write(&self.private_key).expect("Hmac write");
82            }
83            Bip44Node::Normal(_) => {
84                h.write(&self.public_key).expect("Hmac write");
85            }
86        }
87
88        let index_bytes = (u64::from(&node) as u32).to_be_bytes();
89
90        h.write(&index_bytes).expect("Hmac write");
91
92        let intermediary = h.finalize().into_bytes();
93
94        let key_data = &intermediary[..32];
95        let chain_code = &intermediary[32..];
96
97        let key = SigningKey::from_bytes(key_data).expect("Create ecdsa signing key");
98
99        let scalar = key.as_nonzero_scalar().add(
100            SigningKey::from_bytes(&self.private_key)
101                .expect("Create ecdsa signing key")
102                .as_nonzero_scalar(),
103        );
104
105        let key = SigningKey::from_bytes(&scalar.to_bytes()).expect("Create ecdsa signing key");
106
107        let public_key = key.verifying_key().to_encoded_point(true);
108
109        DriveKey {
110            public_key: public_key
111                .as_bytes()
112                .try_into()
113                .expect("Convert public key"),
114            private_key: key.to_bytes().try_into().expect("Convert private key"),
115            chain_code: chain_code.try_into().expect("Convert chain code"),
116            node: Some(node),
117        }
118    }
119
120    pub fn drive<P>(&self, path: P) -> Result<DriveKey, Bip32Error>
121    where
122        P: AsRef<str>,
123    {
124        let path: Bip44Path = path
125            .as_ref()
126            .parse()
127            .map_err(|err| Bip32Error::Bip44(err))?;
128
129        Ok(self
130            .child_key(path.purpose)
131            .child_key(path.coin)
132            .child_key(path.account)
133            .child_key(path.change)
134            .child_key(path.address))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use ethers_types_rs::{bytes::bytes_to_string, Address, AddressEx, Eip55};
141
142    use super::{mnemonic_to_send, DriveKey};
143
144    #[test]
145    fn test_gen_seed() {
146        let seed = mnemonic_to_send(
147            "canal walnut regular license dust liberty story expect repeat design picture medal",
148            "",
149        );
150
151        assert_eq!("0x15cba277c500b4e0c777d563278130f4c24b52532b3c8c45e051d417bebc5c007243c07d2e341a2d7c17bbd3880b968ca60869edab8f015be30674ad4d3d260f",bytes_to_string(&seed));
152    }
153
154    #[test]
155    fn test_hardhat_default_accounts() {
156        let _ = pretty_env_logger::try_init();
157
158        let drive_key = DriveKey::new(
159            "test test test test test test test test test test test junk",
160            "",
161        );
162
163        fn check_drive(drive_key: &DriveKey, id: usize, pk: &str, expect_address: &str) {
164            let key = drive_key
165                .drive(format!("m/44'/60'/0'/0/{}", id))
166                .expect("Bip32 drive key");
167
168            assert_eq!(bytes_to_string(&key.private_key), pk);
169
170            let address = Address::from_pub_key_compressed(key.public_key).expect("Parse address");
171
172            assert_eq!(address.to_checksum_string(), expect_address);
173        }
174
175        check_drive(
176            &drive_key,
177            0,
178            "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
179            "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
180        );
181
182        check_drive(
183            &drive_key,
184            19,
185            "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e",
186            "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199",
187        );
188    }
189}