1use crate::address::EthereumAddress;
2use crate::derivation_path::EthereumDerivationPath;
3use crate::extended_private_key::EthereumExtendedPrivateKey;
4use crate::format::EthereumFormat;
5use crate::network::EthereumNetwork;
6use crate::public_key::EthereumPublicKey;
7use wagyu_model::{
8 crypto::{checksum, hash160},
9 AddressError, ChildIndex, DerivationPath, ExtendedPrivateKey, ExtendedPublicKey, ExtendedPublicKeyError, PublicKey,
10};
11
12use base58::{FromBase58, ToBase58};
13use hex;
14use hmac::{Hmac, Mac};
15use secp256k1::{PublicKey as Secp256k1_PublicKey, Secp256k1, SecretKey};
16use sha2::Sha512;
17use std::{convert::TryFrom, fmt, marker::PhantomData, str::FromStr};
18
19type HmacSha512 = Hmac<Sha512>;
20
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
23pub struct EthereumExtendedPublicKey<N: EthereumNetwork> {
24 depth: u8,
26 parent_fingerprint: [u8; 4],
28 child_index: ChildIndex,
30 chain_code: [u8; 32],
32 public_key: EthereumPublicKey,
34 _network: PhantomData<N>,
36}
37
38impl<N: EthereumNetwork> ExtendedPublicKey for EthereumExtendedPublicKey<N> {
39 type Address = EthereumAddress;
40 type DerivationPath = EthereumDerivationPath<N>;
41 type ExtendedPrivateKey = EthereumExtendedPrivateKey<N>;
42 type Format = EthereumFormat;
43 type PublicKey = EthereumPublicKey;
44
45 fn from_extended_private_key(extended_private_key: &Self::ExtendedPrivateKey) -> Self {
47 Self {
48 depth: extended_private_key.depth,
49 parent_fingerprint: extended_private_key.parent_fingerprint,
50 child_index: extended_private_key.child_index,
51 chain_code: extended_private_key.chain_code,
52 public_key: extended_private_key.to_public_key(),
53 _network: PhantomData,
54 }
55 }
56
57 fn derive(&self, path: &Self::DerivationPath) -> Result<Self, ExtendedPublicKeyError> {
59 if self.depth == 255 {
60 return Err(ExtendedPublicKeyError::MaximumChildDepthReached(self.depth));
61 }
62
63 let mut extended_public_key = self.clone();
64
65 for index in path.to_vec()?.into_iter() {
66 let public_key_serialized = &self.public_key.to_secp256k1_public_key().serialize()[..];
67
68 let mut mac = HmacSha512::new_varkey(&self.chain_code)?;
69 match index {
70 ChildIndex::Normal(_) => mac.input(public_key_serialized),
72 ChildIndex::Hardened(_) => {
74 return Err(ExtendedPublicKeyError::InvalidChildNumber(1 << 31, u32::from(index)))
75 }
76 }
77 mac.input(&u32::from(index).to_be_bytes());
79 let hmac = mac.result().code();
80
81 let mut chain_code = [0u8; 32];
82 chain_code[0..32].copy_from_slice(&hmac[32..]);
83
84 let mut public_key = self.public_key.to_secp256k1_public_key();
85 public_key.add_exp_assign(&Secp256k1::new(), &SecretKey::from_slice(&hmac[..32])?[..])?;
86 let public_key = Self::PublicKey::from_secp256k1_public_key(public_key);
87
88 let mut parent_fingerprint = [0u8; 4];
89 parent_fingerprint.copy_from_slice(&hash160(public_key_serialized)[0..4]);
90
91 extended_public_key = Self {
92 depth: extended_public_key.depth + 1,
93 parent_fingerprint,
94 child_index: index,
95 chain_code,
96 public_key,
97 _network: PhantomData,
98 };
99 }
100
101 Ok(extended_public_key)
102 }
103
104 fn to_public_key(&self) -> Self::PublicKey {
106 self.public_key
107 }
108
109 fn to_address(&self, _format: &Self::Format) -> Result<Self::Address, AddressError> {
111 self.public_key.to_address(_format)
112 }
113}
114
115impl<N: EthereumNetwork> FromStr for EthereumExtendedPublicKey<N> {
116 type Err = ExtendedPublicKeyError;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 let data = s.from_base58()?;
120 if data.len() != 82 {
121 return Err(ExtendedPublicKeyError::InvalidByteLength(data.len()));
122 }
123
124 if &data[0..4] != [0x04u8, 0x88, 0xB2, 0x1E] {
125 return Err(ExtendedPublicKeyError::InvalidVersionBytes(data[0..4].to_vec()));
126 };
127
128 let depth = data[4] as u8;
129
130 let mut parent_fingerprint = [0u8; 4];
131 parent_fingerprint.copy_from_slice(&data[5..9]);
132
133 let child_index = ChildIndex::from(u32::from_be_bytes(<[u8; 4]>::try_from(&data[9..13])?));
134
135 let mut chain_code = [0u8; 32];
136 chain_code.copy_from_slice(&data[13..45]);
137
138 let public_key = EthereumPublicKey::from_str(&hex::encode(
139 &Secp256k1_PublicKey::from_slice(&data[45..78])?.serialize_uncompressed()[1..],
140 ))?;
141
142 let expected = &data[78..82];
143 let checksum = &checksum(&data[0..78])[0..4];
144 if *expected != *checksum {
145 let expected = expected.to_base58();
146 let found = checksum.to_base58();
147 return Err(ExtendedPublicKeyError::InvalidChecksum(expected, found));
148 }
149
150 Ok(Self {
151 depth,
152 parent_fingerprint,
153 child_index,
154 chain_code,
155 public_key,
156 _network: PhantomData,
157 })
158 }
159}
160
161impl<N: EthereumNetwork> fmt::Display for EthereumExtendedPublicKey<N> {
162 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
165 let mut result = [0u8; 82];
166 result[0..4].copy_from_slice(&[0x04u8, 0x88, 0xB2, 0x1E][..]);
167 result[4] = self.depth as u8;
168 result[5..9].copy_from_slice(&self.parent_fingerprint[..]);
169 result[9..13].copy_from_slice(&u32::from(self.child_index).to_be_bytes());
170 result[13..45].copy_from_slice(&self.chain_code[..]);
171 result[45..78].copy_from_slice(&self.public_key.to_secp256k1_public_key().serialize()[..]);
172
173 let sum = &checksum(&result[0..78])[0..4];
174 result[78..82].copy_from_slice(sum);
175
176 fmt.write_str(&result.to_base58())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use crate::network::*;
184 use wagyu_model::extended_private_key::ExtendedPrivateKey;
185
186 use hex;
187 use std::convert::TryInto;
188
189 fn test_from_extended_private_key<N: EthereumNetwork>(
190 expected_extended_public_key: &str,
191 expected_public_key: &str,
192 expected_child_index: u32,
193 expected_chain_code: &str,
194 expected_parent_fingerprint: &str,
195 extended_private_key: &str,
196 ) {
197 let extended_private_key = EthereumExtendedPrivateKey::<N>::from_str(extended_private_key).unwrap();
198 let extended_public_key = EthereumExtendedPublicKey::<N>::from_extended_private_key(&extended_private_key);
199 assert_eq!(expected_extended_public_key, extended_public_key.to_string());
200 assert_eq!(
201 expected_public_key,
202 extended_public_key.public_key.to_secp256k1_public_key().to_string()
203 );
204 assert_eq!(expected_child_index, u32::from(extended_public_key.child_index));
205 assert_eq!(expected_chain_code, hex::encode(extended_public_key.chain_code));
206 assert_eq!(
207 expected_parent_fingerprint,
208 hex::encode(extended_public_key.parent_fingerprint)
209 );
210 }
211
212 fn test_derive<N: EthereumNetwork>(
214 expected_extended_private_key1: &str,
215 expected_extended_public_key2: &str,
216 expected_child_index2: u32,
217 ) {
218 let path = vec![ChildIndex::from(expected_child_index2)].try_into().unwrap();
219
220 let extended_private_key1 = EthereumExtendedPrivateKey::<N>::from_str(expected_extended_private_key1).unwrap();
221 let extended_private_key2 = extended_private_key1.derive(&path).unwrap();
222 let extended_public_key2 = extended_private_key2.to_extended_public_key();
223
224 let expected_extended_public_key2 =
225 EthereumExtendedPublicKey::<N>::from_str(&expected_extended_public_key2).unwrap();
226
227 assert_eq!(expected_extended_public_key2, extended_public_key2);
228 assert_eq!(
229 expected_extended_public_key2.public_key,
230 extended_public_key2.public_key
231 );
232 assert_eq!(expected_extended_public_key2.depth, extended_public_key2.depth);
233 assert_eq!(
234 expected_extended_public_key2.child_index,
235 extended_public_key2.child_index
236 );
237 assert_eq!(
238 expected_extended_public_key2.chain_code,
239 extended_public_key2.chain_code
240 );
241 assert_eq!(
242 expected_extended_public_key2.parent_fingerprint,
243 extended_public_key2.parent_fingerprint
244 );
245 }
246
247 fn test_from_str<N: EthereumNetwork>(
248 expected_public_key: &str,
249 expected_child_index: u32,
250 expected_chain_code: &str,
251 expected_parent_fingerprint: &str,
252 extended_public_key: &str,
253 ) {
254 let extended_public_key = EthereumExtendedPublicKey::<N>::from_str(&extended_public_key).unwrap();
255 assert_eq!(
256 expected_public_key,
257 extended_public_key.public_key.to_secp256k1_public_key().to_string()
258 );
259 assert_eq!(expected_child_index, u32::from(extended_public_key.child_index));
260 assert_eq!(expected_chain_code, hex::encode(extended_public_key.chain_code));
261 assert_eq!(
262 expected_parent_fingerprint,
263 hex::encode(extended_public_key.parent_fingerprint)
264 );
265 }
266
267 fn test_to_string<N: EthereumNetwork>(expected_extended_public_key: &str) {
268 let extended_public_key = EthereumExtendedPublicKey::<N>::from_str(&expected_extended_public_key).unwrap();
269 assert_eq!(expected_extended_public_key, extended_public_key.to_string());
270 }
271
272 mod bip32_mainnet {
273 use super::*;
274
275 type N = Mainnet;
276
277 const KEYPAIRS: [(&str, &str, &str, &str, &str, &str, &str, &str); 12] = [
279 (
280 "m",
281 "000102030405060708090a0b0c0d0e0f",
282 "0",
283 "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2",
284 "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508",
285 "00000000",
286 "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
287 "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
288 ),
289 (
290 "m/0'",
291 "000102030405060708090a0b0c0d0e0f",
292 "2147483648",
293 "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56",
294 "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141",
295 "3442193e",
296 "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
297 "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
298 ),
299 (
300 "m/0'/1",
301 "000102030405060708090a0b0c0d0e0f",
302 "1",
303 "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c",
304 "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19",
305 "5c1bd648",
306 "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
307 "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
308 ),
309 (
310 "m/0'/1/2'",
311 "000102030405060708090a0b0c0d0e0f",
312 "2147483650",
313 "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2",
314 "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f",
315 "bef5a2f9",
316 "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
317 "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
318 ),
319 (
320 "m/0'/1/2'/2",
321 "000102030405060708090a0b0c0d0e0f",
322 "2",
323 "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29",
324 "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd",
325 "ee7ab90c",
326 "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
327 "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
328 ),
329 (
330 "m/0'/1/2'/2/1000000000",
331 "000102030405060708090a0b0c0d0e0f",
332 "1000000000",
333 "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011",
334 "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e",
335 "d880d7d8",
336 "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
337 "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
338 ),
339 (
340 "m",
341 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
342 "0",
343 "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7",
344 "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689",
345 "00000000",
346 "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
347 "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
348 ),
349 (
350 "m/0",
351 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
352 "0",
353 "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea",
354 "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c",
355 "bd16bee5",
356 "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
357 "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
358 ),
359 (
360 "m/0/2147483647'",
361 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
362 "4294967295",
363 "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b",
364 "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9",
365 "5a61ff8e",
366 "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
367 "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
368 ),
369 (
370 "m/0/2147483647'/1",
371 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
372 "1",
373 "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9",
374 "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb",
375 "d8ab4937",
376 "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
377 "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
378 ),
379 (
380 "m/0/2147483647'/1/2147483646'",
381 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
382 "4294967294",
383 "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0",
384 "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29",
385 "78412e3a",
386 "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
387 "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
388 ),
389 (
390 "m/0/2147483647'/1/2147483646'/2",
391 "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
392 "2",
393 "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c",
394 "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271",
395 "31a507b8",
396 "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
397 "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
398 ),
399 ];
400
401 #[test]
402 fn from_extended_private_key() {
403 KEYPAIRS.iter().for_each(
404 |(
405 _,
406 _,
407 child_index,
408 public_key,
409 chain_code,
410 parent_fingerprint,
411 extended_private_key,
412 extended_public_key,
413 )| {
414 test_from_extended_private_key::<N>(
415 extended_public_key,
416 public_key,
417 child_index.parse().unwrap(),
418 chain_code,
419 parent_fingerprint,
420 extended_private_key,
421 );
422 },
423 );
424 }
425
426 #[test]
427 fn derive() {
428 KEYPAIRS.chunks(2).for_each(|pair| {
429 let (_, _, _, _, _, _, expected_extended_private_key1, _) = pair[0];
430 let (_, _, expected_child_index2, _, _, _, _, expected_extended_public_key2) = pair[1];
431 test_derive::<N>(
432 expected_extended_private_key1,
433 expected_extended_public_key2,
434 expected_child_index2.parse().unwrap(),
435 );
436 });
437 }
438
439 #[test]
440 fn from_str() {
441 KEYPAIRS.iter().for_each(
442 |(_, _, child_index, public_key, chain_code, parent_fingerprint, _, extended_public_key)| {
443 test_from_str::<N>(
444 public_key,
445 child_index.parse().unwrap(),
446 chain_code,
447 parent_fingerprint,
448 extended_public_key,
449 );
450 },
451 );
452 }
453
454 #[test]
455 fn to_string() {
456 KEYPAIRS.iter().for_each(|(_, _, _, _, _, _, _, extended_public_key)| {
457 test_to_string::<N>(extended_public_key);
458 });
459 }
460 }
461
462 mod test_invalid {
463 use super::*;
464
465 type N = Mainnet;
466
467 const INVALID_EXTENDED_PUBLIC_KEY__SECP256K1_PUBLIC_KEY: &str = "xpub661MyMwAqRbcftXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
468 const INVALID_EXTENDED_PUBLIC_KEY_NETWORK: &str = "xpub561MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
469 const INVALID_EXTENDED_PUBLIC_KEY_CHECKSUM: &str = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet7";
470 const VALID_EXTENDED_PUBLIC_KEY: &str = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
471
472 #[test]
473 #[should_panic(expected = "Crate(\"secp256k1\", \"InvalidPublicKey\")")]
474 fn from_str_invalid_secret_key() {
475 let _result =
476 EthereumExtendedPublicKey::<N>::from_str(INVALID_EXTENDED_PUBLIC_KEY__SECP256K1_PUBLIC_KEY).unwrap();
477 }
478
479 #[test]
480 #[should_panic(expected = "InvalidVersionBytes([4, 136, 178, 29])")]
481 fn from_str_invalid_version() {
482 let _result = EthereumExtendedPublicKey::<N>::from_str(INVALID_EXTENDED_PUBLIC_KEY_NETWORK).unwrap();
483 }
484
485 #[test]
486 #[should_panic(expected = "InvalidChecksum(\"5Nvot3\", \"5Nvot4\")")]
487 fn from_str_invalid_checksum() {
488 let _result = EthereumExtendedPublicKey::<N>::from_str(INVALID_EXTENDED_PUBLIC_KEY_CHECKSUM).unwrap();
489 }
490
491 #[test]
492 #[should_panic(expected = "InvalidByteLength(81)")]
493 fn from_str_short() {
494 let _result = EthereumExtendedPublicKey::<N>::from_str(&VALID_EXTENDED_PUBLIC_KEY[1..]).unwrap();
495 }
496
497 #[test]
498 #[should_panic(expected = "InvalidByteLength(83)")]
499 fn from_str_long() {
500 let mut string = String::from(VALID_EXTENDED_PUBLIC_KEY);
501 string.push('a');
502 let _result = EthereumExtendedPublicKey::<N>::from_str(&string).unwrap();
503 }
504 }
505}