1use coins_core::hashes::{Hash160, Hash160Digest, MarkedDigest, MarkedDigestOutput};
2use hmac::{Hmac, Mac};
3use k256::{ecdsa, elliptic_curve::sec1::FromEncodedPoint};
4use sha2::Sha512;
5use std::{
6 convert::{TryFrom, TryInto},
7 ops::{AddAssign, Mul},
8};
9
10use crate::{
11 path::DerivationPath,
12 primitives::{ChainCode, Hint, KeyFingerprint, XKeyInfo},
13 Bip32Error, BIP32_HARDEN,
14};
15
16pub const SEED: &[u8; 12] = b"Bitcoin seed";
18
19fn hmac_and_split(
20 seed: &[u8],
21 data: &[u8],
22) -> Result<(k256::NonZeroScalar, ChainCode), Bip32Error> {
23 let mut mac = Hmac::<Sha512>::new_from_slice(seed).expect("key length is ok");
24 mac.update(data);
25 let result = mac.finalize().into_bytes();
26
27 let left = k256::NonZeroScalar::try_from(&result[..32])?;
28
29 let mut right = [0u8; 32];
30 right.copy_from_slice(&result[32..]);
31
32 Ok((left, ChainCode(right)))
33}
34
35pub trait Parent: Sized + Clone {
37 fn derive_child(&self, index: u32) -> Result<Self, Bip32Error>;
41
42 fn derive_path<E, P>(&self, p: P) -> Result<Self, Bip32Error>
45 where
46 E: Into<Bip32Error>,
47 P: TryInto<DerivationPath, Error = E>,
48 {
49 let path: DerivationPath = p.try_into().map_err(Into::into)?;
50
51 if path.is_empty() {
52 return Ok(self.clone());
53 }
54
55 let mut current = self.to_owned();
56 for index in path.iter() {
57 current = current.derive_child(*index)?;
58 }
59 Ok(current)
60 }
61}
62
63pub struct XPriv {
65 pub(crate) key: ecdsa::SigningKey,
66 pub(crate) xkey_info: XKeyInfo,
67}
68
69impl PartialEq for XPriv {
70 fn eq(&self, other: &XPriv) -> bool {
71 self.fingerprint() == other.fingerprint() && self.xkey_info == other.xkey_info
72 }
73}
74
75impl Clone for XPriv {
76 fn clone(&self) -> Self {
77 Self {
78 key: ecdsa::SigningKey::from_bytes(&self.key.to_bytes()).unwrap(),
79 xkey_info: self.xkey_info,
80 }
81 }
82}
83
84inherit_signer!(XPriv.key);
85
86impl std::fmt::Debug for XPriv {
87 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88 f.debug_struct("XPriv")
89 .field("key fingerprint", &self.fingerprint())
90 .field("key info", &self.xkey_info)
91 .finish()
92 }
93}
94
95impl AsRef<XPriv> for XPriv {
96 fn as_ref(&self) -> &XPriv {
97 self
98 }
99}
100
101impl AsRef<XKeyInfo> for XPriv {
102 fn as_ref(&self) -> &XKeyInfo {
103 &self.xkey_info
104 }
105}
106
107impl AsRef<ecdsa::SigningKey> for XPriv {
108 fn as_ref(&self) -> &ecdsa::SigningKey {
109 &self.key
110 }
111}
112
113impl XPriv {
114 pub const fn new(key: ecdsa::SigningKey, xkey_info: XKeyInfo) -> Self {
116 Self { key, xkey_info }
117 }
118
119 pub fn verify_key(&self) -> XPub {
121 XPub {
122 key: self.key.verifying_key().to_owned(),
123 xkey_info: self.xkey_info,
124 }
125 }
126
127 pub fn fingerprint(&self) -> KeyFingerprint {
129 self.verify_key().fingerprint()
130 }
131
132 pub fn root_node(
134 hmac_key: &[u8],
135 data: &[u8],
136 hint: Option<Hint>,
137 ) -> Result<XPriv, Bip32Error> {
138 Self::custom_root_node(hmac_key, data, hint)
139 }
140
141 pub fn root_from_seed(data: &[u8], hint: Option<Hint>) -> Result<XPriv, Bip32Error> {
148 Self::custom_root_from_seed(data, hint)
149 }
150
151 pub fn custom_root_node(
153 hmac_key: &[u8],
154 data: &[u8],
155 hint: Option<Hint>,
156 ) -> Result<XPriv, Bip32Error> {
157 if data.len() < 16 {
158 return Err(Bip32Error::SeedTooShort);
159 }
160 let parent = KeyFingerprint([0u8; 4]);
161 let (key, chain_code) = hmac_and_split(hmac_key, data)?;
162 if bool::from(key.is_zero()) {
163 return Err(Bip32Error::InvalidKey);
165 }
166
167 let key = ecdsa::SigningKey::from(key);
168
169 Ok(XPriv {
170 key,
171 xkey_info: XKeyInfo {
172 depth: 0,
173 parent,
174 index: 0,
175 chain_code,
176 hint: hint.unwrap_or(Hint::SegWit),
177 },
178 })
179 }
180
181 pub fn custom_root_from_seed(data: &[u8], hint: Option<Hint>) -> Result<XPriv, Bip32Error> {
188 Self::custom_root_node(SEED, data, hint)
189 }
190
191 pub fn derive_path<E, P>(&self, p: P) -> Result<Self, Bip32Error>
194 where
195 E: Into<Bip32Error>,
196 P: TryInto<DerivationPath, Error = E>,
197 {
198 let path: DerivationPath = p.try_into().map_err(Into::into)?;
199
200 if path.is_empty() {
201 return Ok(self.clone());
202 }
203
204 let mut current = self.to_owned();
205 for index in path.iter() {
206 current = current.derive_child(*index)?;
207 }
208 Ok(current)
209 }
210}
211
212impl Parent for XPriv {
213 fn derive_child(&self, index: u32) -> Result<Self, Bip32Error> {
214 let hardened = index >= BIP32_HARDEN;
215
216 let key: &ecdsa::SigningKey = self.as_ref();
217
218 let mut data: Vec<u8> = vec![];
219 if hardened {
220 data.push(0);
221 data.extend(key.to_bytes());
222 data.extend(index.to_be_bytes());
223 } else {
224 data.extend(key.verifying_key().to_sec1_bytes().iter());
225 data.extend(index.to_be_bytes());
226 };
227
228 let res = hmac_and_split(&self.xkey_info.chain_code.0, &data);
229 let (tweak, chain_code) = match res {
230 Ok((tweak, chain_code)) => (tweak, chain_code),
231 _ => return self.derive_child(index + 1),
232 };
233
234 let parent_key = k256::NonZeroScalar::from_repr(key.to_bytes()).unwrap();
235 let tweaked = tweak.clone().add(&parent_key);
236
237 let tweaked: k256::NonZeroScalar =
238 Option::from(k256::NonZeroScalar::new(tweaked)).ok_or(Bip32Error::BadTweak)?;
239
240 Ok(Self {
241 key: ecdsa::SigningKey::from(tweaked),
242 xkey_info: XKeyInfo {
243 depth: self.xkey_info.depth + 1,
244 parent: self.fingerprint(),
245 index,
246 chain_code,
247 hint: self.xkey_info.hint,
248 },
249 })
250 }
251}
252
253#[derive(Copy)]
254pub struct XPub {
256 pub(crate) key: ecdsa::VerifyingKey,
257 pub(crate) xkey_info: XKeyInfo,
258}
259
260inherit_verifier!(XPub.key);
261
262impl Clone for XPub {
263 fn clone(&self) -> Self {
264 *self
265 }
266}
267
268impl std::fmt::Debug for XPub {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 f.debug_struct("XPub")
271 .field("public key", &self.key.to_sec1_bytes())
272 .field("key fingerprint", &self.fingerprint())
273 .field("key info", &self.xkey_info)
274 .finish()
275 }
276}
277
278impl AsRef<XPub> for XPub {
279 fn as_ref(&self) -> &XPub {
280 self
281 }
282}
283
284impl AsRef<XKeyInfo> for XPub {
285 fn as_ref(&self) -> &XKeyInfo {
286 &self.xkey_info
287 }
288}
289
290impl AsRef<ecdsa::VerifyingKey> for XPub {
291 fn as_ref(&self) -> &ecdsa::VerifyingKey {
292 &self.key
293 }
294}
295
296impl XPub {
297 pub const fn new(key: ecdsa::VerifyingKey, xkey_info: XKeyInfo) -> Self {
299 Self { key, xkey_info }
300 }
301
302 pub fn fingerprint(&self) -> KeyFingerprint {
305 let digest = self.pubkey_hash160();
306 let mut buf = [0u8; 4];
307 buf.copy_from_slice(&digest.as_slice()[..4]);
308 buf.into()
309 }
310
311 pub fn pubkey_hash160(&self) -> Hash160Digest {
313 Hash160::digest_marked(self.key.to_sec1_bytes().as_ref())
314 }
315}
316
317impl PartialEq for XPub {
318 fn eq(&self, other: &XPub) -> bool {
319 self.key == other.key
320 }
321}
322
323impl Parent for XPub {
324 fn derive_child(&self, index: u32) -> Result<XPub, Bip32Error> {
325 if index >= BIP32_HARDEN {
326 return Err(Bip32Error::HardenedDerivationFailed);
327 }
328 let mut data = vec![];
329 data.extend(self.key.to_sec1_bytes().iter());
332 data.extend(index.to_be_bytes());
333
334 let res = hmac_and_split(&self.xkey_info.chain_code.0, &data);
335
336 let (tweak, chain_code) = match res {
337 Ok((tweak, chain_code)) => (tweak, chain_code),
338 _ => return self.derive_child(index + 1),
339 };
340
341 if bool::from(tweak.is_zero()) {
342 return self.derive_child(index + 1);
343 }
344
345 let parent_key =
346 k256::ProjectivePoint::from_encoded_point(&self.key.to_encoded_point(true)).unwrap();
347 let mut tweak_point = k256::ProjectivePoint::GENERATOR.mul(*tweak);
348 tweak_point.add_assign(parent_key);
349
350 let key = ecdsa::VerifyingKey::from_affine(tweak_point.to_affine())?;
351 Ok(Self {
352 key,
353 xkey_info: XKeyInfo {
354 depth: self.xkey_info.depth + 1,
355 parent: self.fingerprint(),
356 index,
357 chain_code,
358 hint: self.xkey_info.hint,
359 },
360 })
361 }
362}
363
364#[cfg(test)]
365mod test {
366 use super::*;
367 use crate::{
368 enc::{MainnetEncoder, XKeyEncoder},
369 primitives::*,
370 };
371 use coins_core::hashes::Hash256;
372 use k256::ecdsa::signature::{DigestSigner, DigestVerifier};
373
374 use hex;
375
376 struct KeyDeriv<'a> {
377 pub(crate) path: &'a [u32],
378 pub(crate) xpub: String,
379 pub(crate) xpriv: String,
380 }
381
382 fn validate_descendant(d: &KeyDeriv, m: &XPriv) {
383 let xpriv = m.derive_path(d.path).unwrap();
384 let xpub = xpriv.verify_key();
385
386 let deser_xpriv = MainnetEncoder::xpriv_from_base58(&d.xpriv).unwrap();
391 let deser_xpub = MainnetEncoder::xpub_from_base58(&d.xpub).unwrap();
392
393 assert_eq!(&xpriv.key.to_bytes(), &deser_xpriv.key.to_bytes());
394 assert_eq!(MainnetEncoder::xpriv_to_base58(&xpriv).unwrap(), d.xpriv);
395 assert_eq!(&xpub.key.to_sec1_bytes(), &deser_xpub.key.to_sec1_bytes());
396 assert_eq!(MainnetEncoder::xpub_to_base58(&xpub).unwrap(), d.xpub);
397 }
398
399 #[test]
400 fn bip32_vector_1() {
401 let seed: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
402
403 let xpriv = XPriv::root_from_seed(&seed, Some(Hint::Legacy)).unwrap();
404 let xpub = xpriv.verify_key();
405
406 let expected_xpub = "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8";
407 let expected_xpriv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi";
408
409 let deser_xpub = MainnetEncoder::xpub_from_base58(expected_xpub).unwrap();
410 let deser_xpriv = MainnetEncoder::xpriv_from_base58(expected_xpriv).unwrap();
411
412 assert_eq!(&xpriv.key.to_bytes(), &deser_xpriv.key.to_bytes());
413 assert_eq!(
414 MainnetEncoder::xpriv_to_base58(&xpriv).unwrap(),
415 expected_xpriv
416 );
417 assert_eq!(&xpub.key.to_sec1_bytes(), &deser_xpub.key.to_sec1_bytes());
418 assert_eq!(
419 MainnetEncoder::xpub_to_base58(&xpub).unwrap(),
420 expected_xpub
421 );
422
423 let descendants = [
424 KeyDeriv {
425 path: &[BIP32_HARDEN],
426 xpub: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw".to_owned(),
427 xpriv: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7".to_owned(),
428 },
429 KeyDeriv {
430 path: &[BIP32_HARDEN, 1],
431 xpub: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ".to_owned(),
432 xpriv: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs".to_owned(),
433 },
434 KeyDeriv {
435 path: &[BIP32_HARDEN, 1, 2 + BIP32_HARDEN],
436 xpub: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5".to_owned(),
437 xpriv: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM".to_owned(),
438 },
439 KeyDeriv {
440 path: &[BIP32_HARDEN, 1, 2 + BIP32_HARDEN, 2],
441 xpub: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV".to_owned(),
442 xpriv: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334".to_owned(),
443 },
444 KeyDeriv {
445 path: &[BIP32_HARDEN, 1, 2 + BIP32_HARDEN, 2, 1000000000],
446 xpub: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy".to_owned(),
447 xpriv: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76".to_owned(),
448 },
449 ];
450
451 for case in descendants.iter() {
452 validate_descendant(case, &xpriv);
453 }
454 }
455
456 #[test]
457 fn bip32_vector_2() {
458 let seed = hex::decode("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542").unwrap();
459
460 let xpriv = XPriv::root_from_seed(&seed, Some(Hint::Legacy)).unwrap();
461 let xpub = xpriv.verify_key();
462
463 let expected_xpub = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB";
464 let expected_xpriv = "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U";
465
466 let deser_xpub = MainnetEncoder::xpub_from_base58(expected_xpub).unwrap();
467 let deser_xpriv = MainnetEncoder::xpriv_from_base58(expected_xpriv).unwrap();
468
469 assert_eq!(&xpriv.key.to_bytes(), &deser_xpriv.key.to_bytes());
470 assert_eq!(
471 MainnetEncoder::xpriv_to_base58(&xpriv).unwrap(),
472 expected_xpriv
473 );
474 assert_eq!(&xpub.key.to_sec1_bytes(), &deser_xpub.key.to_sec1_bytes());
475 assert_eq!(
476 MainnetEncoder::xpub_to_base58(&xpub).unwrap(),
477 expected_xpub
478 );
479
480 let descendants = [
481 KeyDeriv {
482 path: &[0],
483 xpub: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH".to_owned(),
484 xpriv: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt".to_owned(),
485 },
486 KeyDeriv {
487 path: &[0, 2147483647 + BIP32_HARDEN],
488 xpub: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a".to_owned(),
489 xpriv: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9".to_owned(),
490 },
491 KeyDeriv {
492 path: &[0, 2147483647 + BIP32_HARDEN, 1],
493 xpub: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon".to_owned(),
494 xpriv: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef".to_owned(),
495 },
496 KeyDeriv {
497 path: &[0, 2147483647 + BIP32_HARDEN, 1, 2147483646 + BIP32_HARDEN],
498 xpub: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL".to_owned(),
499 xpriv: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc".to_owned(),
500 },
501 KeyDeriv {
502 path: &[0, 2147483647 + BIP32_HARDEN, 1, 2147483646 + BIP32_HARDEN, 2],
503 xpub: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt".to_owned(),
504 xpriv: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j".to_owned(),
505 },
506 ];
507
508 for case in descendants.iter() {
509 validate_descendant(case, &xpriv);
510 }
511 }
512
513 #[test]
514 fn bip32_vector_3() {
515 let seed = hex::decode("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be").unwrap();
516
517 let xpriv = XPriv::root_from_seed(&seed, Some(Hint::Legacy)).unwrap();
518 let xpub = xpriv.verify_key();
519
520 let expected_xpub = "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13";
521 let expected_xpriv = "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6";
522
523 let deser_xpub = MainnetEncoder::xpub_from_base58(expected_xpub).unwrap();
524 let deser_xpriv = MainnetEncoder::xpriv_from_base58(expected_xpriv).unwrap();
525
526 assert_eq!(&xpriv.key.to_bytes(), &deser_xpriv.key.to_bytes());
527 assert_eq!(
528 MainnetEncoder::xpriv_to_base58(&xpriv).unwrap(),
529 expected_xpriv
530 );
531 assert_eq!(&xpub.key.to_sec1_bytes(), &deser_xpub.key.to_sec1_bytes());
532 assert_eq!(
533 MainnetEncoder::xpub_to_base58(&xpub).unwrap(),
534 expected_xpub
535 );
536
537 let descendants = [
538 KeyDeriv {
539 path: &[BIP32_HARDEN],
540 xpub: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y".to_owned(),
541 xpriv: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L".to_owned(),
542 },
543 ];
544
545 for case in descendants.iter() {
546 validate_descendant(case, &xpriv);
547 }
548 }
549
550 #[test]
551 fn it_can_sign_and_verify() {
552 let digest = Hash256::default();
553 let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
554 let xpriv = MainnetEncoder::xpriv_from_base58(&xpriv_str).unwrap();
555
556 let child = xpriv.derive_child(33).unwrap();
557 let sig: ecdsa::Signature = child.sign_digest(digest.clone());
558
559 let child_xpub = child.verify_key();
560 child_xpub.verify_digest(digest, &sig).unwrap();
561 }
562
563 #[test]
564 fn it_can_verify_and_recover_from_signatures() {
565 let digest = Hash256::default();
566
567 let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
568 let xpriv = MainnetEncoder::xpriv_from_base58(&xpriv_str).unwrap();
569
570 let child = xpriv.derive_child(33).unwrap();
571
572 let (sig, recovery_id): (ecdsa::Signature, ecdsa::RecoveryId) =
573 child.sign_digest(digest.clone());
574
575 let child_xpub = child.verify_key();
576 child_xpub.verify_digest(digest.clone(), &sig).unwrap();
577
578 let recovered =
579 ecdsa::VerifyingKey::recover_from_digest(digest, &sig, recovery_id).unwrap();
580
581 assert_eq!(&recovered.to_sec1_bytes(), &child_xpub.key.to_sec1_bytes());
582 }
583
584 #[test]
585 fn it_can_read_keys() {
586 let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
587 let _xpriv: XPriv = MainnetEncoder::xpriv_from_base58(&xpriv_str).unwrap();
588 }
589
590 #[test]
591 fn print_key() {
592 let xpriv_str = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi".to_owned();
593 let xpriv: XPriv = MainnetEncoder::xpriv_from_base58(&xpriv_str).unwrap();
594 println!("{xpriv:?}");
595 }
596}