wagyu_ethereum/
extended_private_key.rs

1use crate::address::EthereumAddress;
2use crate::derivation_path::EthereumDerivationPath;
3use crate::extended_public_key::EthereumExtendedPublicKey;
4use crate::format::EthereumFormat;
5use crate::network::EthereumNetwork;
6use crate::private_key::EthereumPrivateKey;
7use crate::public_key::EthereumPublicKey;
8use wagyu_model::{
9    crypto::{checksum, hash160},
10    AddressError, ChildIndex, DerivationPath, ExtendedPrivateKey, ExtendedPrivateKeyError, ExtendedPublicKey,
11    PrivateKey,
12};
13
14use base58::{FromBase58, ToBase58};
15use hmac::{Hmac, Mac};
16use secp256k1::{PublicKey, Secp256k1, SecretKey};
17use sha2::Sha512;
18use std::{convert::TryFrom, fmt, fmt::Display, marker::PhantomData, str::FromStr};
19
20type HmacSha512 = Hmac<Sha512>;
21
22/// Represents a Ethereum Extended Private Key
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24pub struct EthereumExtendedPrivateKey<N> {
25    /// The depth of key derivation, e.g. 0x00 for master nodes, 0x01 for level-1 derived keys, ...
26    pub(super) depth: u8,
27    /// The first 32 bits of the key identifier (hash160(ECDSA_public_key))
28    pub(super) parent_fingerprint: [u8; 4],
29    /// The child index of the key (0 for master key)
30    pub(super) child_index: ChildIndex,
31    /// The chain code for this extended private key
32    pub(super) chain_code: [u8; 32],
33    /// The Ethereum private key
34    private_key: EthereumPrivateKey,
35    /// PhantomData
36    _network: PhantomData<N>,
37}
38
39impl<N: EthereumNetwork> ExtendedPrivateKey for EthereumExtendedPrivateKey<N> {
40    type Address = EthereumAddress;
41    type DerivationPath = EthereumDerivationPath<N>;
42    type ExtendedPublicKey = EthereumExtendedPublicKey<N>;
43    type Format = EthereumFormat;
44    type PrivateKey = EthereumPrivateKey;
45    type PublicKey = EthereumPublicKey;
46
47    /// Returns a new Ethereum extended private key.
48    fn new(seed: &[u8], _format: &Self::Format, path: &Self::DerivationPath) -> Result<Self, ExtendedPrivateKeyError> {
49        Ok(Self::new_master(seed, _format)?.derive(path)?)
50    }
51
52    /// Returns a new Ethereum extended private key.
53    fn new_master(seed: &[u8], _format: &Self::Format) -> Result<Self, ExtendedPrivateKeyError> {
54        let mut mac = HmacSha512::new_varkey(b"Bitcoin seed")?; // This is correct. Ethereum uses BIP32.
55        mac.input(seed);
56        let hmac = mac.result().code();
57        let private_key = Self::PrivateKey::from_secp256k1_secret_key(SecretKey::from_slice(&hmac[0..32])?);
58
59        let mut chain_code = [0u8; 32];
60        chain_code[0..32].copy_from_slice(&hmac[32..]);
61
62        Ok(Self {
63            depth: 0,
64            parent_fingerprint: [0u8; 4],
65            child_index: ChildIndex::Normal(0),
66            chain_code,
67            private_key,
68            _network: PhantomData,
69        })
70    }
71
72    /// Returns the extended private key of the given derivation path.
73    fn derive(&self, path: &Self::DerivationPath) -> Result<Self, ExtendedPrivateKeyError> {
74        if self.depth == 255 {
75            return Err(ExtendedPrivateKeyError::MaximumChildDepthReached(self.depth));
76        }
77
78        let mut extended_private_key = self.clone();
79
80        for index in path.to_vec()?.into_iter() {
81            let public_key = &PublicKey::from_secret_key(
82                &Secp256k1::new(),
83                &extended_private_key.private_key.to_secp256k1_secret_key(),
84            )
85            .serialize()[..];
86
87            let mut mac = HmacSha512::new_varkey(&extended_private_key.chain_code)?;
88            match index {
89                // HMAC-SHA512(Key = cpar, Data = serP(point(kpar)) || ser32(i)).
90                ChildIndex::Normal(_) => mac.input(public_key),
91                // HMAC-SHA512(Key = cpar, Data = 0x00 || ser256(kpar) || ser32(i))
92                // (Note: The 0x00 pads the private key to make it 33 bytes long.)
93                ChildIndex::Hardened(_) => {
94                    mac.input(&[0u8]);
95                    mac.input(&extended_private_key.private_key.to_secp256k1_secret_key()[..]);
96                }
97            }
98            // Append the child index in big-endian format
99            mac.input(&u32::from(index).to_be_bytes());
100            let hmac = mac.result().code();
101
102            let mut secret_key = SecretKey::from_slice(&hmac[0..32])?;
103            secret_key.add_assign(&extended_private_key.private_key.to_secp256k1_secret_key()[..])?;
104            let private_key = Self::PrivateKey::from_secp256k1_secret_key(secret_key);
105
106            let mut chain_code = [0u8; 32];
107            chain_code[0..32].copy_from_slice(&hmac[32..]);
108
109            let mut parent_fingerprint = [0u8; 4];
110            parent_fingerprint.copy_from_slice(&hash160(public_key)[0..4]);
111
112            extended_private_key = Self {
113                depth: extended_private_key.depth + 1,
114                parent_fingerprint,
115                child_index: index,
116                chain_code,
117                private_key,
118                _network: PhantomData,
119            }
120        }
121
122        Ok(extended_private_key)
123    }
124
125    /// Returns the extended public key of the corresponding extended private key.
126    fn to_extended_public_key(&self) -> Self::ExtendedPublicKey {
127        Self::ExtendedPublicKey::from_extended_private_key(&self)
128    }
129
130    /// Returns the private key of the corresponding extended private key.
131    fn to_private_key(&self) -> Self::PrivateKey {
132        self.private_key.clone()
133    }
134
135    /// Returns the public key of the corresponding extended private key.
136    fn to_public_key(&self) -> Self::PublicKey {
137        self.private_key.to_public_key()
138    }
139
140    /// Returns the address of the corresponding extended private key.
141    fn to_address(&self, _format: &Self::Format) -> Result<Self::Address, AddressError> {
142        self.private_key.to_address(_format)
143    }
144}
145
146impl<N: EthereumNetwork> FromStr for EthereumExtendedPrivateKey<N> {
147    type Err = ExtendedPrivateKeyError;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        let data = s.from_base58()?;
151        if data.len() != 82 {
152            return Err(ExtendedPrivateKeyError::InvalidByteLength(data.len()));
153        }
154
155        // Ethereum xkeys are mainnet only
156        if &data[0..4] != [0x04u8, 0x88, 0xAD, 0xE4] {
157            return Err(ExtendedPrivateKeyError::InvalidVersionBytes(data[0..4].to_vec()));
158        };
159
160        let depth = data[4] as u8;
161
162        let mut parent_fingerprint = [0u8; 4];
163        parent_fingerprint.copy_from_slice(&data[5..9]);
164
165        let child_index = ChildIndex::from(u32::from_be_bytes(<[u8; 4]>::try_from(&data[9..13])?));
166
167        let mut chain_code = [0u8; 32];
168        chain_code.copy_from_slice(&data[13..45]);
169
170        let private_key = EthereumPrivateKey::from_secp256k1_secret_key(SecretKey::from_slice(&data[46..78])?);
171
172        let expected = &data[78..82];
173        let checksum = &checksum(&data[0..78])[0..4];
174        if *expected != *checksum {
175            let expected = expected.to_base58();
176            let found = checksum.to_base58();
177            return Err(ExtendedPrivateKeyError::InvalidChecksum(expected, found));
178        }
179
180        Ok(Self {
181            depth,
182            parent_fingerprint,
183            child_index,
184            chain_code,
185            private_key,
186            _network: PhantomData,
187        })
188    }
189}
190
191impl<N: EthereumNetwork> Display for EthereumExtendedPrivateKey<N> {
192    /// BIP32 serialization format:
193    /// https://github.com/ethereum/bips/blob/master/bip-0032.mediawiki#serialization-format
194    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
195        let mut result = [0u8; 82];
196        result[0..4].copy_from_slice(&[0x04, 0x88, 0xAD, 0xE4][..]);
197        result[4] = self.depth as u8;
198        result[5..9].copy_from_slice(&self.parent_fingerprint[..]);
199        result[9..13].copy_from_slice(&u32::from(self.child_index).to_be_bytes());
200        result[13..45].copy_from_slice(&self.chain_code[..]);
201        result[45] = 0;
202        result[46..78].copy_from_slice(&self.private_key.to_secp256k1_secret_key()[..]);
203
204        let checksum = &checksum(&result[0..78])[0..4];
205        result[78..82].copy_from_slice(&checksum);
206
207        fmt.write_str(&result.to_base58())
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use crate::network::*;
215
216    use hex;
217    use std::convert::TryInto;
218    use std::string::String;
219
220    fn test_new<N: EthereumNetwork>(
221        expected_extended_private_key: &str,
222        expected_parent_fingerprint: &str,
223        expected_child_index: u32,
224        expected_chain_code: &str,
225        expected_secret_key: &str,
226        seed: &str,
227        path: &EthereumDerivationPath<N>,
228    ) {
229        let extended_private_key =
230            EthereumExtendedPrivateKey::new(&hex::decode(seed).unwrap(), &EthereumFormat::Standard, path).unwrap();
231        assert_eq!(expected_extended_private_key, extended_private_key.to_string());
232        assert_eq!(
233            expected_parent_fingerprint,
234            hex::encode(extended_private_key.parent_fingerprint)
235        );
236        assert_eq!(expected_child_index, u32::from(extended_private_key.child_index));
237        assert_eq!(expected_chain_code, hex::encode(extended_private_key.chain_code));
238        assert_eq!(
239            expected_secret_key,
240            extended_private_key.private_key.to_secp256k1_secret_key().to_string()
241        );
242    }
243
244    // Check: (extended_private_key1 -> extended_private_key2) == (expected_extended_private_key2)
245    fn test_derive<N: EthereumNetwork>(
246        expected_extended_private_key1: &str,
247        expected_extended_private_key2: &str,
248        expected_child_index2: u32,
249    ) {
250        let path = vec![ChildIndex::from(expected_child_index2)].try_into().unwrap();
251
252        let extended_private_key1 = EthereumExtendedPrivateKey::<N>::from_str(expected_extended_private_key1).unwrap();
253        let extended_private_key2 = extended_private_key1.derive(&path).unwrap();
254
255        let expected_extended_private_key2 =
256            EthereumExtendedPrivateKey::<N>::from_str(&expected_extended_private_key2).unwrap();
257
258        assert_eq!(expected_extended_private_key2, extended_private_key2);
259        assert_eq!(
260            expected_extended_private_key2.private_key,
261            extended_private_key2.private_key
262        );
263        assert_eq!(expected_extended_private_key2.depth, extended_private_key2.depth);
264        assert_eq!(
265            expected_extended_private_key2.child_index,
266            extended_private_key2.child_index
267        );
268        assert_eq!(
269            expected_extended_private_key2.chain_code,
270            extended_private_key2.chain_code
271        );
272        assert_eq!(
273            expected_extended_private_key2.parent_fingerprint,
274            extended_private_key2.parent_fingerprint
275        );
276    }
277
278    fn test_to_extended_public_key<N: EthereumNetwork>(
279        expected_extended_public_key: &str,
280        seed: &str,
281        path: &EthereumDerivationPath<N>,
282    ) {
283        let extended_private_key =
284            EthereumExtendedPrivateKey::<N>::new(&hex::decode(seed).unwrap(), &EthereumFormat::Standard, path).unwrap();
285        let extended_public_key = extended_private_key.to_extended_public_key();
286        assert_eq!(expected_extended_public_key, extended_public_key.to_string());
287    }
288
289    fn test_from_str<N: EthereumNetwork>(
290        expected_extended_private_key: &str,
291        expected_parent_fingerprint: &str,
292        expected_child_index: u32,
293        expected_chain_code: &str,
294        expected_secret_key: &str,
295    ) {
296        let extended_private_key = EthereumExtendedPrivateKey::<N>::from_str(expected_extended_private_key).unwrap();
297        assert_eq!(expected_extended_private_key, extended_private_key.to_string());
298        assert_eq!(
299            expected_parent_fingerprint,
300            hex::encode(extended_private_key.parent_fingerprint)
301        );
302        assert_eq!(expected_child_index, u32::from(extended_private_key.child_index));
303        assert_eq!(expected_chain_code, hex::encode(extended_private_key.chain_code));
304        assert_eq!(
305            expected_secret_key,
306            extended_private_key.private_key.to_secp256k1_secret_key().to_string()
307        );
308    }
309
310    fn test_to_string<N: EthereumNetwork>(expected_extended_private_key: &str) {
311        let extended_private_key = EthereumExtendedPrivateKey::<N>::from_str(expected_extended_private_key).unwrap();
312        assert_eq!(expected_extended_private_key, extended_private_key.to_string());
313    }
314
315    mod bip32_mainnet {
316        use super::*;
317
318        type N = Mainnet;
319
320        // (path, seed, child_index, secret_key, chain_code, parent_fingerprint, extended_private_key, extended_public_key)
321        const KEYPAIRS: [(&str, &str, &str, &str, &str, &str, &str, &str); 18] = [
322            (
323                "m",
324                "000102030405060708090a0b0c0d0e0f",
325                "0",
326                "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35",
327                "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
328                "00000000",
329                "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
330                "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
331            ),
332            (
333                "m/0'",
334                "000102030405060708090a0b0c0d0e0f",
335                "2147483648",
336                "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea",
337                "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141",
338                "3442193e",
339                "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
340                "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
341            ),
342            (
343                "m/0'/1",
344                "000102030405060708090a0b0c0d0e0f",
345                "1",
346                "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368",
347                "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19",
348                "5c1bd648",
349                "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
350                "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
351            ),
352            (
353                "m/0'/1/2'",
354                "000102030405060708090a0b0c0d0e0f",
355                "2147483650",
356                "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca",
357                "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f",
358                "bef5a2f9",
359                "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
360                "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
361            ),
362            (
363                "m/0'/1/2'/2",
364                "000102030405060708090a0b0c0d0e0f",
365                "2",
366                "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4",
367                "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd",
368                "ee7ab90c",
369                "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
370                "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
371            ),
372            (
373                "m/0'/1/2'/2/1000000000",
374                "000102030405060708090a0b0c0d0e0f",
375                "1000000000",
376                "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8",
377                "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e",
378                "d880d7d8",
379                "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
380                "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
381            ),
382            (
383                "m",
384                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
385                "0",
386                "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e",
387                "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689",
388                "00000000",
389                "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
390                "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
391            ),
392            (
393                "m/0",
394                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
395                "0",
396                "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e",
397                "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c",
398                "bd16bee5",
399                "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
400                "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
401            ),
402            (
403                "m/0/2147483647'",
404                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
405                "4294967295",
406                "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93",
407                "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9",
408                "5a61ff8e",
409                "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
410                "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
411            ),
412            (
413                "m/0/2147483647'/1",
414                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
415                "1",
416                "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7",
417                "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb",
418                "d8ab4937",
419                "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
420                "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
421            ),
422            (
423                "m/0/2147483647'/1/2147483646'",
424                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
425                "4294967294",
426                "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d",
427                "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29",
428                "78412e3a",
429                "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
430                "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
431            ),
432            (
433                "m/0/2147483647'/1/2147483646'/2",
434                "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
435                "2",
436                "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23",
437                "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271",
438                "31a507b8",
439                "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
440                "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
441            ),
442
443            // Ethereum Derivation Paths
444            (
445                "m/44'/60'/0'/0",
446                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
447                "0",
448                "39095b7dddd80c60473297772536fa1350d3ecf8d0bced02ff601c0c428af1e3",
449                "2276cdb0e042e524e767a875544c3f92fbea560d9d77447de54c9b4afabedfdb",
450                "997d7b25",
451                "xprvA1fzCYJXXiAYQ5epqaDqgkPGeTUkwHRuzqGVWKZEtBSSEXKu91wrPhfihHJRHQtXXcRfLbMitd64PJeiL7upKh81RoSxHmNXLgQanCtbTK2",
452                "xpub6EfLc3qRN5iqcZjHwbkr3tL1CVKFLk9mN4C6JhxrSWyR7Kf3gZG6wVzCYawvTrB4bgDzLPHm8T7ayKny1xJ8C4FVgP8zS3shyHzQ5QB36GJ"
453            ),
454            // Ledger Live Derivation Paths
455            (
456                "m/44'/60'/0'/0/0",
457                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
458                "0",
459                "f62a8ea4ab7025d151ccd84981c66278d0d3cd58ff837467cdc51229915a22d1",
460                "ab62505ed816d7e57fd23580f87688abf2e419ef788baf079b6e862eee6f0a79",
461                "1c7a091b",
462                "xprvA2dpyTTnuc4DpVWb1cGwu7ibggAxLHn8wRHDdwXsBGGdSUpq1VXt5wegJ7aL9VpX7QjYWocZTomGQ4Zh1woawPJDtFzFdqDr86jbRasrVHE",
463                "xpub6FdBNxzgjycX2yb47doxGFfLEi1SjkVzJeCpSKwUjbocKH9yZ2r8djyA9Md5oDG846jjJGBGE5hBuxtyWdHHoLWcZUrBq2RCLTUKxusyGvX"
464            ),
465
466            // Ledger Legacy Derivation Paths
467            (
468                "m/44'/60'/0'",
469                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
470                "2147483648",
471                "2d103433c67eaf9a829f76090ab3e867e74247a7433346963815db6ae1577a78",
472                "6d20171efe0658e3d4c948b0224cba50a0122358562f166b43b57cae25c41fcb",
473                "68bd693b",
474                "xprv9yS4waAqz3vsbKmE4J2GAfbtm8uLqPvcdbnbFGd77e4zCFYriZLzZ76fhyq97qsgegXX9eFJdAGA8VQhrQrnDhu1ZXX73YESwHpqdTFuiEB",
475                "xpub6CRRM5hjpRVAooqhAKZGXoYdKAjqEreTzpiC3f2ifyby53t1G6fF6uR9ZFgYC8Ccwi7AbBLNKGP5oTYvNCcpuAcnUifyNZaj7yK32nXSyYL"
476            ),
477            (
478                "m/44'/60'/0'/0",
479                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
480                "0",
481                "39095b7dddd80c60473297772536fa1350d3ecf8d0bced02ff601c0c428af1e3",
482                "2276cdb0e042e524e767a875544c3f92fbea560d9d77447de54c9b4afabedfdb",
483                "997d7b25",
484                "xprvA1fzCYJXXiAYQ5epqaDqgkPGeTUkwHRuzqGVWKZEtBSSEXKu91wrPhfihHJRHQtXXcRfLbMitd64PJeiL7upKh81RoSxHmNXLgQanCtbTK2",
485                "xpub6EfLc3qRN5iqcZjHwbkr3tL1CVKFLk9mN4C6JhxrSWyR7Kf3gZG6wVzCYawvTrB4bgDzLPHm8T7ayKny1xJ8C4FVgP8zS3shyHzQ5QB36GJ"
486            ),
487
488            // KeepKey Derivation Paths
489            (
490                "m/44'/60'",
491                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
492                "2147483708",
493                "c00ba4d8319dd38c07884f18cc9811f537dc6130f3003aed4d0be8b7c7d8289f",
494                "b1d4797738fd5a81f38a79c33dc6d03ebee13cfdfd42cd1f0dbe24c3051ab70d",
495                "754aeee2",
496                "xprv9weHTtsPstnRCTvCNduPo9AZRGJX27e65kNZVzX18XafdqJQ2HKfYnYkS2xQh9MWRnhsNGfR1WdrHK16c3So8WqXeGKi43CD8QbqdBr8J6i",
497                "xpub6AddsQQHiGLiQwzfUfSQAH7HyJ91RaMwSyJAJNvcgs7eWddYZpdv6asEHH2eWBFQCaZ5K8ddKpKxpedZ1AXwaTDcNpW5ToKo6NiWxf8GCuh"
498            ),
499            (
500                "m/44'/60'/0",
501                "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
502                "0",
503                "e99402522166f472ab14fd43b5aa95aa0c646b38959b0fc8424d1edb7ba7c440",
504                "7725e5d1302ad41a5e865deb7ed7178518085754b2a196c03f731db99ed11e65",
505                "68bd693b",
506                "xprv9yS4waAhePPuR9G8db9MA5Nz67JXcybm7CB5wVsiXtAnfQj895J2xrKiGRE6hgkAUVedGEJ3eJT4MhkUJaZD5p4j3t2Rn4LGiPsNqtPUeow",
507                "xpub6CRRM5hbUkxCddLbjcgMXDKie9922SKcUR6gjtHL6DhmYD4GgccHWeeC7huUgFoagyzVxa2qu7cdrpnQx6KDxqxsvXtBRA8z3xptPur93bf"
508            ),
509        ];
510
511        #[test]
512        fn new() {
513            KEYPAIRS.iter().for_each(
514                |(path, seed, child_index, secret_key, chain_code, parent_fingerprint, extended_private_key, _)| {
515                    test_new::<N>(
516                        extended_private_key,
517                        parent_fingerprint,
518                        child_index.parse().unwrap(),
519                        chain_code,
520                        secret_key,
521                        seed,
522                        &EthereumDerivationPath::from_str(path).unwrap(),
523                    );
524                },
525            );
526        }
527
528        #[test]
529        fn derive() {
530            KEYPAIRS.chunks(2).for_each(|pair| {
531                let (_, _, _, _, _, _, expected_extended_private_key1, _) = pair[0];
532                let (_, _, expected_child_index2, _, _, _, expected_extended_private_key2, _) = pair[1];
533                test_derive::<N>(
534                    expected_extended_private_key1,
535                    expected_extended_private_key2,
536                    expected_child_index2.parse().unwrap(),
537                );
538            });
539        }
540
541        #[test]
542        fn to_extended_public_key() {
543            KEYPAIRS
544                .iter()
545                .for_each(|(path, seed, _, _, _, _, _, expected_public_key)| {
546                    test_to_extended_public_key::<N>(
547                        expected_public_key,
548                        seed,
549                        &EthereumDerivationPath::from_str(path).unwrap(),
550                    );
551                });
552        }
553
554        #[test]
555        fn from_str() {
556            KEYPAIRS.iter().for_each(
557                |(_, _, child_index, secret_key, chain_code, parent_fingerprint, extended_private_key, _)| {
558                    test_from_str::<N>(
559                        extended_private_key,
560                        parent_fingerprint,
561                        child_index.parse().unwrap(),
562                        chain_code,
563                        secret_key,
564                    );
565                },
566            );
567        }
568
569        #[test]
570        fn to_string() {
571            KEYPAIRS.iter().for_each(|(_, _, _, _, _, _, extended_private_key, _)| {
572                test_to_string::<N>(extended_private_key);
573            });
574        }
575    }
576
577    mod bip44 {
578        use super::*;
579
580        /// Test case from ethereumjs-wallet https://github.com/ethereumjs/ethereumjs-wallet/blob/master/src/test/hdkey.js
581        #[test]
582        fn test_derivation_path() {
583            type N = Mainnet;
584
585            let path = "m/44'/0'/0/1";
586            let expected_extended_private_key_serialized = "xprvA1ErCzsuXhpB8iDTsbmgpkA2P8ggu97hMZbAXTZCdGYeaUrDhyR8fEw47BNEgLExsWCVzFYuGyeDZJLiFJ9kwBzGojQ6NB718tjVJrVBSrG";
587            let master_extended_private_key = EthereumExtendedPrivateKey::<N>::from_str("xprv9s21ZrQH143K4KqQx9Zrf1eN8EaPQVFxM2Ast8mdHn7GKiDWzNEyNdduJhWXToy8MpkGcKjxeFWd8oBSvsz4PCYamxR7TX49pSpp3bmHVAY").unwrap();
588            let extended_private_key = master_extended_private_key
589                .derive(&EthereumDerivationPath::from_str(path).unwrap())
590                .expect("error deriving extended private key from path");
591            assert_eq!(
592                expected_extended_private_key_serialized,
593                extended_private_key.to_string()
594            );
595        }
596    }
597
598    mod test_invalid {
599        use super::*;
600
601        type N = Mainnet;
602
603        const INVALID_EXTENDED_PRIVATE_KEY_SECP256K1_SECRET_KEY: &str = "xprv9s21ZrQH143K24Mfq5zL5MhWK9hUhhGbd45hLXo2Pq2oqzMMo63oStZzFAzHGBP2UuGCqWLTAPLcMtD9y5gkZ6Eq3Rjuahrv17fENZ3QzxW";
604        const INVALID_EXTENDED_PRIVATE_KEY_NETWORK: &str = "xprv8s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
605        const INVALID_EXTENDED_PRIVATE_KEY_CHECKSUM: &str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHj";
606        const VALID_EXTENDED_PRIVATE_KEY: &str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
607
608        #[test]
609        #[should_panic(expected = "Crate(\"secp256k1\", \"InvalidSecretKey\")")]
610        fn from_str_invalid_secret_key() {
611            let _result =
612                EthereumExtendedPrivateKey::<N>::from_str(INVALID_EXTENDED_PRIVATE_KEY_SECP256K1_SECRET_KEY).unwrap();
613        }
614
615        #[test]
616        #[should_panic(expected = "InvalidVersionBytes([4, 136, 173, 227])")]
617        fn from_str_invalid_version() {
618            let _result = EthereumExtendedPrivateKey::<N>::from_str(INVALID_EXTENDED_PRIVATE_KEY_NETWORK).unwrap();
619        }
620
621        #[test]
622        #[should_panic(expected = "InvalidChecksum(\"6vCfku\", \"6vCfkt\")")]
623        fn from_str_invalid_checksum() {
624            let _result = EthereumExtendedPrivateKey::<N>::from_str(INVALID_EXTENDED_PRIVATE_KEY_CHECKSUM).unwrap();
625        }
626
627        #[test]
628        #[should_panic(expected = "InvalidByteLength(81)")]
629        fn from_str_short() {
630            let _result = EthereumExtendedPrivateKey::<N>::from_str(&VALID_EXTENDED_PRIVATE_KEY[1..]).unwrap();
631        }
632
633        #[test]
634        #[should_panic(expected = "InvalidByteLength(83)")]
635        fn from_str_long() {
636            let mut string = String::from(VALID_EXTENDED_PRIVATE_KEY);
637            string.push('a');
638            let _result = EthereumExtendedPrivateKey::<N>::from_str(&string).unwrap();
639        }
640    }
641}