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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
24pub struct EthereumExtendedPrivateKey<N> {
25 pub(super) depth: u8,
27 pub(super) parent_fingerprint: [u8; 4],
29 pub(super) child_index: ChildIndex,
31 pub(super) chain_code: [u8; 32],
33 private_key: EthereumPrivateKey,
35 _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 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 fn new_master(seed: &[u8], _format: &Self::Format) -> Result<Self, ExtendedPrivateKeyError> {
54 let mut mac = HmacSha512::new_varkey(b"Bitcoin seed")?; 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 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 ChildIndex::Normal(_) => mac.input(public_key),
91 ChildIndex::Hardened(_) => {
94 mac.input(&[0u8]);
95 mac.input(&extended_private_key.private_key.to_secp256k1_secret_key()[..]);
96 }
97 }
98 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 fn to_extended_public_key(&self) -> Self::ExtendedPublicKey {
127 Self::ExtendedPublicKey::from_extended_private_key(&self)
128 }
129
130 fn to_private_key(&self) -> Self::PrivateKey {
132 self.private_key.clone()
133 }
134
135 fn to_public_key(&self) -> Self::PublicKey {
137 self.private_key.to_public_key()
138 }
139
140 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 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 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 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 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 (
445 "m/44'/60'/0'/0",
446 "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
447 "0",
448 "39095b7dddd80c60473297772536fa1350d3ecf8d0bced02ff601c0c428af1e3",
449 "2276cdb0e042e524e767a875544c3f92fbea560d9d77447de54c9b4afabedfdb",
450 "997d7b25",
451 "xprvA1fzCYJXXiAYQ5epqaDqgkPGeTUkwHRuzqGVWKZEtBSSEXKu91wrPhfihHJRHQtXXcRfLbMitd64PJeiL7upKh81RoSxHmNXLgQanCtbTK2",
452 "xpub6EfLc3qRN5iqcZjHwbkr3tL1CVKFLk9mN4C6JhxrSWyR7Kf3gZG6wVzCYawvTrB4bgDzLPHm8T7ayKny1xJ8C4FVgP8zS3shyHzQ5QB36GJ"
453 ),
454 (
456 "m/44'/60'/0'/0/0",
457 "747f302d9c916698912d5f70be53a6cf53bc495803a5523d3a7c3afa2afba94ec3803f838b3e1929ab5481f9da35441372283690fdcf27372c38f40ba134fe03",
458 "0",
459 "f62a8ea4ab7025d151ccd84981c66278d0d3cd58ff837467cdc51229915a22d1",
460 "ab62505ed816d7e57fd23580f87688abf2e419ef788baf079b6e862eee6f0a79",
461 "1c7a091b",
462 "xprvA2dpyTTnuc4DpVWb1cGwu7ibggAxLHn8wRHDdwXsBGGdSUpq1VXt5wegJ7aL9VpX7QjYWocZTomGQ4Zh1woawPJDtFzFdqDr86jbRasrVHE",
463 "xpub6FdBNxzgjycX2yb47doxGFfLEi1SjkVzJeCpSKwUjbocKH9yZ2r8djyA9Md5oDG846jjJGBGE5hBuxtyWdHHoLWcZUrBq2RCLTUKxusyGvX"
464 ),
465
466 (
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 (
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]
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}