1use std::borrow::Borrow;
24use std::fmt::{self, Debug, Display, Formatter};
25use std::str::FromStr;
26
27use amplify::{hex, ByteArray, Bytes20, Bytes32, Bytes4, Wrapper};
28use bc::secp256k1::{Keypair, PublicKey, SecretKey, SECP256K1};
29use bc::{secp256k1, CompressedPk, InvalidPubkey, LegacyPk, XOnlyPk};
30use commit_verify::{Digest, Ripemd160};
31use hmac::{Hmac, Mac};
32use sha2::Sha512;
33
34use crate::{
35 base58, DerivationIndex, DerivationParseError, DerivationPath, DerivationSeg, HardenedIndex,
36 Idx, IdxBase, IndexParseError, Keychain, NormalIndex, SegParseError, Terminal,
37};
38
39pub const XPRIV_MAINNET_MAGIC: [u8; 4] = [0x04u8, 0x88, 0xAD, 0xE4];
40pub const XPRIV_TESTNET_MAGIC: [u8; 4] = [0x04u8, 0x35, 0x83, 0x94];
41
42pub const XPUB_MAINNET_MAGIC: [u8; 4] = [0x04u8, 0x88, 0xB2, 0x1E];
43pub const XPUB_TESTNET_MAGIC: [u8; 4] = [0x04u8, 0x35, 0x87, 0xCF];
44
45#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)]
46#[display(doc_comments)]
47pub enum XkeyAccountError {
48 DepthMismatch,
51
52 ParentMismatch,
55
56 MasterMismatch,
59
60 TooManyKeychains,
63}
64
65#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
66#[display(doc_comments)]
67pub enum XkeyDecodeError {
68 WrongExtendedKeyLength(usize),
70
71 UnknownKeyType([u8; 4]),
73
74 #[from]
76 #[from(bc::secp256k1::Error)]
77 InvalidPubkey(InvalidPubkey<33>),
78
79 InvalidType(u8),
81
82 InvalidSecretKey,
84}
85
86#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
87#[display(doc_comments)]
88pub enum XkeyParseError {
89 #[from]
91 Base58(base58::Error),
92
93 #[display(inner)]
94 #[from]
95 Decode(XkeyDecodeError),
96
97 #[display(inner)]
98 #[from]
99 DerivationPath(DerivationParseError),
100
101 #[from]
103 InvalidMasterFp(hex::Error),
104
105 InvalidTerminal,
107
108 #[from]
110 InvalidKeychain(SegParseError),
111
112 #[from]
114 InvalidIndex(IndexParseError),
115
116 NoOrigin,
118
119 NoXpub,
121
122 NetworkMismatch,
124
125 #[display(inner)]
126 #[from]
127 Account(XkeyAccountError),
128}
129
130impl From<OriginParseError> for XkeyParseError {
131 fn from(err: OriginParseError) -> Self {
132 match err {
133 OriginParseError::DerivationPath(e) => XkeyParseError::DerivationPath(e),
134 OriginParseError::InvalidMasterFp(e) => XkeyParseError::InvalidMasterFp(e),
135 }
136 }
137}
138
139#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
141#[wrapper(Deref, RangeOps)]
142pub struct ChainCode(Bytes32);
143
144impl AsRef<[u8]> for ChainCode {
145 fn as_ref(&self) -> &[u8] { self.0.as_ref() }
146}
147
148impl From<[u8; 32]> for ChainCode {
149 fn from(value: [u8; 32]) -> Self { Self(value.into()) }
150}
151
152impl From<ChainCode> for [u8; 32] {
153 fn from(value: ChainCode) -> Self { value.0.into_inner() }
154}
155
156#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
158pub struct XpubCore {
159 pub public_key: CompressedPk,
161 pub chain_code: ChainCode,
163}
164
165#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display, From)]
166#[wrapper(RangeOps, Hex, FromStr)]
167#[display(LowerHex)]
168#[cfg_attr(
169 feature = "serde",
170 derive(Serialize, Deserialize),
171 serde(crate = "serde_crate", transparent)
172)]
173pub struct XpubFp(
174 #[from]
175 #[from([u8; 4])]
176 Bytes4,
177);
178
179impl AsRef<[u8]> for XpubFp {
180 fn as_ref(&self) -> &[u8] { self.0.as_ref() }
181}
182
183impl From<XpubFp> for [u8; 4] {
184 fn from(value: XpubFp) -> Self { value.0.into_inner() }
185}
186
187impl XpubFp {
188 pub const fn master() -> Self { Self(Bytes4::zero()) }
189}
190
191#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display, From)]
192#[wrapper(RangeOps, Hex, FromStr)]
193#[display(LowerHex)]
194#[cfg_attr(
195 feature = "serde",
196 derive(Serialize, Deserialize),
197 serde(crate = "serde_crate", transparent)
198)]
199pub struct XpubId(
200 #[from]
201 #[from([u8; 20])]
202 Bytes20,
203);
204
205impl AsRef<[u8]> for XpubId {
206 fn as_ref(&self) -> &[u8] { self.0.as_ref() }
207}
208
209impl From<XpubId> for [u8; 20] {
210 fn from(value: XpubId) -> Self { value.0.into_inner() }
211}
212
213#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
214pub struct XkeyMeta {
215 pub depth: u8,
216 pub parent_fp: XpubFp,
217 pub child_number: DerivationIndex,
218}
219
220#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
221pub struct Xpub {
222 testnet: bool,
223 meta: XkeyMeta,
224 core: XpubCore,
225}
226
227impl Xpub {
228 pub fn decode(data: impl Borrow<[u8]>) -> Result<Xpub, XkeyDecodeError> {
229 let data = data.borrow();
230
231 if data.len() != 78 {
232 return Err(XkeyDecodeError::WrongExtendedKeyLength(data.len()));
233 }
234
235 let testnet = match &data[0..4] {
236 magic if magic == XPUB_MAINNET_MAGIC => false,
237 magic if magic == XPUB_TESTNET_MAGIC => true,
238 unknown => {
239 let mut magic = [0u8; 4];
240 magic.copy_from_slice(unknown);
241 return Err(XkeyDecodeError::UnknownKeyType(magic));
242 }
243 };
244 let depth = data[4];
245
246 let mut parent_fp = [0u8; 4];
247 parent_fp.copy_from_slice(&data[5..9]);
248
249 let mut child_number = [0u8; 4];
250 child_number.copy_from_slice(&data[9..13]);
251 let child_number = u32::from_be_bytes(child_number);
252
253 let mut chain_code = [0u8; 32];
254 chain_code.copy_from_slice(&data[13..45]);
255
256 let public_key = CompressedPk::from_bytes(&data[45..78])?;
257
258 Ok(Xpub {
259 testnet,
260 meta: XkeyMeta {
261 depth,
262 parent_fp: parent_fp.into(),
263 child_number: child_number.into(),
264 },
265 core: XpubCore {
266 public_key,
267 chain_code: chain_code.into(),
268 },
269 })
270 }
271
272 pub fn encode(&self) -> [u8; 78] {
273 let mut ret = [0; 78];
274 ret[0..4].copy_from_slice(&match self.testnet {
275 false => XPUB_MAINNET_MAGIC,
276 true => XPUB_TESTNET_MAGIC,
277 });
278 ret[4] = self.meta.depth;
279 ret[5..9].copy_from_slice(self.meta.parent_fp.as_ref());
280 ret[9..13].copy_from_slice(&self.meta.child_number.index().to_be_bytes());
281 ret[13..45].copy_from_slice(self.core.chain_code.as_ref());
282 ret[45..78].copy_from_slice(&self.core.public_key.serialize());
283 ret
284 }
285
286 #[must_use]
287 pub fn is_testnet(&self) -> bool { self.testnet }
288
289 pub fn depth(&self) -> u8 { self.meta.depth }
290
291 pub fn child_number(&self) -> DerivationIndex { self.meta.child_number }
292
293 pub fn parent_fp(&self) -> XpubFp { self.meta.parent_fp }
294
295 pub fn identifier(&self) -> XpubId {
297 let hash = Ripemd160::digest(self.core.public_key.serialize());
298 XpubId::from_slice_unsafe(hash)
299 }
300
301 pub fn fingerprint(&self) -> XpubFp {
302 let mut bytes = [0u8; 4];
303 bytes.copy_from_slice(&self.identifier()[..4]);
304 XpubFp::from_slice_unsafe(bytes)
305 }
306
307 pub fn to_legacy_pk(&self) -> LegacyPk { LegacyPk::compressed(*self.core.public_key) }
309
310 pub fn to_compr_pk(&self) -> CompressedPk { self.core.public_key }
312
313 pub fn to_xonly_pk(&self) -> XOnlyPk { XOnlyPk::from(self.core.public_key) }
315
316 pub fn derive_pub(&self, path: impl AsRef<[NormalIndex]>) -> Self {
321 let mut pk = *self;
322 for cnum in path.as_ref() {
323 pk = pk.ckd_pub(*cnum)
324 }
325 pk
326 }
327
328 pub fn ckd_pub_tweak(&self, child_no: NormalIndex) -> (secp256k1::Scalar, ChainCode) {
330 let mut hmac: Hmac<Sha512> =
331 Hmac::new_from_slice(self.core.chain_code.as_ref()).expect("fixed chaincode length");
332 hmac.update(&self.core.public_key.serialize());
333 hmac.update(&child_no.to_be_bytes());
334
335 let hmac_result = hmac.finalize().into_bytes();
336
337 let private_key = secp256k1::SecretKey::from_slice(&hmac_result[..32])
338 .expect("negligible probability")
339 .into();
340 let mut bytes = [0u8; 32];
341 bytes.copy_from_slice(&hmac_result[32..]);
342 let chain_code = ChainCode::from_byte_array(bytes);
343 (private_key, chain_code)
344 }
345
346 pub fn ckd_pub(&self, child_no: NormalIndex) -> Xpub {
348 let (scalar, chain_code) = self.ckd_pub_tweak(child_no);
349 let tweaked =
350 self.core.public_key.add_exp_tweak(SECP256K1, &scalar).expect("negligible probability");
351
352 let meta = XkeyMeta {
353 depth: self.meta.depth + 1,
354 parent_fp: self.fingerprint(),
355 child_number: child_no.into(),
356 };
357 let core = XpubCore {
358 public_key: tweaked.into(),
359 chain_code,
360 };
361 Xpub {
362 testnet: self.testnet,
363 meta,
364 core,
365 }
366 }
367}
368
369impl Display for Xpub {
370 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
371 base58::encode_check_to_fmt(f, &self.encode())
372 }
373}
374
375impl FromStr for Xpub {
376 type Err = XkeyParseError;
377
378 fn from_str(inp: &str) -> Result<Xpub, XkeyParseError> {
379 let data = base58::decode_check(inp)?;
380 Ok(Xpub::decode(data)?)
381 }
382}
383
384#[derive(Copy, Clone, Eq, PartialEq)]
386pub struct XprivCore {
387 pub private_key: SecretKey,
389 pub chain_code: ChainCode,
391}
392
393impl Debug for XprivCore {
394 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
395 f.debug_struct("XprivCore")
396 .field("public_key", &self.private_key.public_key(SECP256K1))
397 .field("chain_code", &self.chain_code)
398 .finish()
399 }
400}
401
402#[derive(Copy, Clone, Eq, PartialEq, Debug)]
403pub struct Xpriv {
404 testnet: bool,
405 meta: XkeyMeta,
406 core: XprivCore,
407}
408
409impl Xpriv {
410 pub fn new_master(testnet: bool, seed: &[u8]) -> Xpriv {
412 let mut hmac: Hmac<Sha512> =
413 Hmac::new_from_slice(b"Bitcoin seed").expect("HMAC can take key of any size");
414 hmac.update(seed);
415 let hmac_result = hmac.finalize().into_bytes();
416
417 let mut chain_code = [0u8; 32];
418 chain_code.copy_from_slice(&hmac_result[32..]);
419
420 Xpriv {
421 testnet,
422 meta: XkeyMeta {
423 depth: 0,
424 parent_fp: XpubFp::master(),
425 child_number: DerivationIndex::ZERO,
426 },
427 core: XprivCore {
428 private_key: SecretKey::from_slice(&hmac_result[..32])
429 .expect("negligible probability"),
430 chain_code: chain_code.into(),
431 },
432 }
433 }
434
435 pub fn decode(data: impl Borrow<[u8]>) -> Result<Xpriv, XkeyDecodeError> {
436 let data = data.borrow();
437
438 if data.len() != 78 {
439 return Err(XkeyDecodeError::WrongExtendedKeyLength(data.len()));
440 }
441
442 let testnet = match &data[0..4] {
443 magic if magic == XPRIV_MAINNET_MAGIC => false,
444 magic if magic == XPRIV_TESTNET_MAGIC => true,
445 unknown => {
446 let mut magic = [0u8; 4];
447 magic.copy_from_slice(unknown);
448 return Err(XkeyDecodeError::UnknownKeyType(magic));
449 }
450 };
451 let depth = data[4];
452
453 let mut parent_fp = [0u8; 4];
454 parent_fp.copy_from_slice(&data[5..9]);
455
456 let mut child_number = [0u8; 4];
457 child_number.copy_from_slice(&data[9..13]);
458 let child_number = u32::from_be_bytes(child_number);
459
460 let mut chain_code = [0u8; 32];
461 chain_code.copy_from_slice(&data[13..45]);
462
463 if data[45] != 0x00 {
464 return Err(XkeyDecodeError::InvalidType(data[45]));
465 }
466 let private_key =
467 SecretKey::from_slice(&data[46..78]).map_err(|_| XkeyDecodeError::InvalidSecretKey)?;
468
469 Ok(Xpriv {
470 testnet,
471 meta: XkeyMeta {
472 depth,
473 parent_fp: parent_fp.into(),
474 child_number: child_number.into(),
475 },
476 core: XprivCore {
477 private_key,
478 chain_code: chain_code.into(),
479 },
480 })
481 }
482
483 pub fn encode(&self) -> [u8; 78] {
484 let mut ret = [0; 78];
485 ret[0..4].copy_from_slice(&match self.testnet {
486 false => XPRIV_MAINNET_MAGIC,
487 true => XPRIV_TESTNET_MAGIC,
488 });
489 ret[4] = self.meta.depth;
490 ret[5..9].copy_from_slice(self.meta.parent_fp.as_ref());
491 ret[9..13].copy_from_slice(&self.meta.child_number.index().to_be_bytes());
492 ret[13..45].copy_from_slice(self.core.chain_code.as_ref());
493 ret[45] = 0;
494 ret[46..78].copy_from_slice(&self.core.private_key.secret_bytes());
495 ret
496 }
497
498 #[must_use]
499 pub fn is_testnet(&self) -> bool { self.testnet }
500
501 pub fn depth(&self) -> u8 { self.meta.depth }
502
503 pub fn child_number(&self) -> DerivationIndex { self.meta.child_number }
504
505 pub fn parent_fp(&self) -> XpubFp { self.meta.parent_fp }
506
507 pub fn fingerprint(self) -> XpubFp { self.to_xpub().fingerprint() }
508
509 pub fn identifier(self) -> XpubId { self.to_xpub().identifier() }
510
511 pub fn to_xpub(self) -> Xpub {
512 Xpub {
513 testnet: self.testnet,
514 meta: self.meta,
515 core: XpubCore {
516 public_key: self.core.private_key.public_key(SECP256K1).into(),
517 chain_code: self.core.chain_code,
518 },
519 }
520 }
521
522 pub fn to_compr_pk(self) -> CompressedPk {
523 self.to_private_ecdsa().public_key(SECP256K1).into()
524 }
525
526 pub fn to_xonly_pk(self) -> XOnlyPk {
527 self.to_private_ecdsa().x_only_public_key(SECP256K1).0.into()
528 }
529
530 pub fn to_private_ecdsa(self) -> SecretKey { self.core.private_key }
531
532 pub fn to_keypair_bip340(self) -> Keypair {
533 Keypair::from_seckey_slice(SECP256K1, &self.core.private_key[..])
534 .expect("BIP32 internal private key representation is broken")
535 }
536
537 pub fn derive_priv<I: Into<DerivationIndex> + Copy>(&self, path: impl AsRef<[I]>) -> Xpriv {
541 let mut xpriv: Xpriv = *self;
542 for idx in path.as_ref() {
543 xpriv = xpriv.ckd_priv((*idx).into());
544 }
545 xpriv
546 }
547
548 pub fn ckd_priv(&self, idx: impl Into<DerivationIndex>) -> Xpriv {
550 let idx = idx.into();
551
552 let mut hmac: Hmac<Sha512> = Hmac::new_from_slice(self.core.chain_code.as_slice())
553 .expect("fixed length of chain code");
554 match idx {
555 DerivationIndex::Normal(_) => {
556 hmac.update(
558 &PublicKey::from_secret_key(SECP256K1, &self.core.private_key).serialize(),
559 );
560 }
561 DerivationIndex::Hardened(_) => {
562 hmac.update(&[0u8]);
564 hmac.update(&self.core.private_key[..]);
565 }
566 }
567
568 hmac.update(&idx.index().to_be_bytes());
569 let hmac_result = hmac.finalize().into_bytes();
570 let sk =
571 SecretKey::from_slice(&hmac_result[..32]).expect("statistically impossible to hit");
572 let tweaked =
573 sk.add_tweak(&self.core.private_key.into()).expect("statistically impossible to hit");
574
575 let mut chain_code = [0u8; 32];
576 chain_code.copy_from_slice(&hmac_result[32..]);
577
578 Xpriv {
579 testnet: self.testnet,
580 meta: XkeyMeta {
581 depth: self.meta.depth + 1,
582 parent_fp: self.fingerprint(),
583 child_number: idx,
584 },
585 core: XprivCore {
586 private_key: tweaked,
587 chain_code: chain_code.into(),
588 },
589 }
590 }
591}
592
593impl Display for Xpriv {
594 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
595 base58::encode_check_to_fmt(f, &self.encode())
596 }
597}
598
599impl FromStr for Xpriv {
600 type Err = XkeyParseError;
601
602 fn from_str(inp: &str) -> Result<Xpriv, XkeyParseError> {
603 let data = base58::decode_check(inp)?;
604 Ok(Xpriv::decode(data)?)
605 }
606}
607
608#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
609#[display("{master_fp}{derivation}", alt = "{master_fp}{derivation:#}")]
610#[cfg_attr(
611 feature = "serde",
612 derive(Serialize, Deserialize),
613 serde(crate = "serde_crate", rename_all = "camelCase")
614)]
615pub struct XkeyOrigin {
616 master_fp: XpubFp,
617 derivation: DerivationPath<HardenedIndex>,
618}
619
620impl XkeyOrigin {
621 pub fn new(master_fp: XpubFp, derivation: DerivationPath<HardenedIndex>) -> Self {
622 XkeyOrigin {
623 master_fp,
624 derivation,
625 }
626 }
627
628 pub fn new_master(master_fp: XpubFp) -> Self {
629 XkeyOrigin {
630 master_fp,
631 derivation: empty!(),
632 }
633 }
634
635 pub const fn master_fp(&self) -> XpubFp { self.master_fp }
636
637 pub fn derivation(&self) -> &[HardenedIndex] { self.derivation.as_ref() }
638
639 pub fn as_derivation(&self) -> &DerivationPath<HardenedIndex> { &self.derivation }
640
641 pub fn to_derivation(&self) -> DerivationPath {
642 self.derivation.iter().copied().map(DerivationIndex::from).collect()
643 }
644
645 pub fn child_derivation<'a>(&'a self, child: &'a KeyOrigin) -> Option<&'a [DerivationIndex]> {
646 if self.master_fp() == child.master_fp() {
647 let d = child.derivation();
648 let shared = d.shared_prefix(self.derivation());
649 if shared > 0 {
650 return Some(&d[shared..]);
651 }
652 }
653 None
654 }
655
656 pub fn is_subset_of(&self, other: &KeyOrigin) -> bool {
657 self.master_fp == other.master_fp
658 && other.derivation.shared_prefix(self.derivation()) == self.derivation.len()
659 }
660}
661
662impl FromStr for XkeyOrigin {
663 type Err = OriginParseError;
664
665 fn from_str(s: &str) -> Result<Self, Self::Err> {
666 let (master_fp, path) = match s.split_once('/') {
667 None => (XpubFp::default(), ""),
668 Some(("00000000", p)) | Some(("m", p)) => (XpubFp::default(), p),
669 Some((fp, p)) => (XpubFp::from_str(fp)?, p),
670 };
671 Ok(XkeyOrigin {
672 master_fp,
673 derivation: DerivationPath::from_str(path)?,
674 })
675 }
676}
677
678#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
679#[display(doc_comments)]
680pub enum OriginParseError {
681 #[from]
683 DerivationPath(DerivationParseError),
684
685 #[from]
687 InvalidMasterFp(hex::Error),
688}
689
690#[derive(Getters, Clone, Eq, PartialEq, Hash, Debug, Display)]
691#[display("{master_fp}{derivation}", alt = "{master_fp}{derivation:#}")]
692#[cfg_attr(
693 feature = "serde",
694 derive(Serialize, Deserialize),
695 serde(crate = "serde_crate", rename_all = "camelCase")
696)]
697pub struct KeyOrigin {
698 #[getter(as_copy)]
699 master_fp: XpubFp,
700 derivation: DerivationPath,
701}
702
703impl FromStr for KeyOrigin {
704 type Err = XkeyParseError;
705
706 fn from_str(s: &str) -> Result<Self, Self::Err> {
707 let (master_fp, path) = match s.split_once('/') {
708 None => (XpubFp::default(), ""),
709 Some(("00000000", p)) | Some(("m", p)) => (XpubFp::default(), p),
710 Some((fp, p)) => (XpubFp::from_str(fp)?, p),
711 };
712 Ok(KeyOrigin {
713 master_fp,
714 derivation: DerivationPath::from_str(path)?,
715 })
716 }
717}
718
719impl KeyOrigin {
720 pub fn new(master_fp: XpubFp, derivation: DerivationPath) -> Self {
721 KeyOrigin {
722 master_fp,
723 derivation,
724 }
725 }
726
727 pub fn with(xpub_origin: XkeyOrigin, terminal: Terminal) -> Self {
728 let mut derivation = DerivationPath::new();
729 derivation.extend(xpub_origin.to_derivation());
730 derivation.push(terminal.keychain.into());
731 derivation.push(DerivationIndex::Normal(terminal.index));
732 KeyOrigin {
733 master_fp: xpub_origin.master_fp(),
734 derivation,
735 }
736 }
737}
738
739#[derive(Getters, Clone, Eq, PartialEq, Hash, Debug)]
740pub struct XpubAccount {
741 origin: XkeyOrigin,
742 xpub: Xpub,
743}
744
745impl XpubAccount {
746 pub fn new(xpub: Xpub, origin: XkeyOrigin) -> Result<Self, XkeyAccountError> {
747 if xpub.meta.depth as usize != origin.derivation.len() {
748 return Err(XkeyAccountError::DepthMismatch);
749 }
750 if origin.derivation.last().copied().map(DerivationIndex::Hardened)
751 != Some(xpub.meta.child_number)
752 {
753 return Err(XkeyAccountError::ParentMismatch);
754 }
755 if xpub.meta.depth == 1 && xpub.meta.parent_fp != origin.master_fp {
756 return Err(XkeyAccountError::MasterMismatch);
757 }
758 Ok(XpubAccount { xpub, origin })
759 }
760
761 #[inline]
762 pub const fn master_fp(&self) -> XpubFp { self.origin.master_fp }
763
764 #[inline]
765 pub fn account_fp(&self) -> XpubFp { self.xpub.fingerprint() }
766
767 #[inline]
768 pub fn account_id(&self) -> XpubId { self.xpub.identifier() }
769
770 #[inline]
771 pub fn derivation(&self) -> &[HardenedIndex] { self.origin.derivation.as_ref() }
772
773 #[inline]
774 pub const fn as_derivation(&self) -> &DerivationPath<HardenedIndex> { &self.origin.derivation }
775
776 #[inline]
777 pub fn to_derivation(&self) -> DerivationPath { self.origin.to_derivation() }
778}
779
780impl Display for XpubAccount {
781 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
782 f.write_str("[")?;
783 Display::fmt(&self.origin, f)?;
784 f.write_str("]")?;
785 write!(f, "{}", self.xpub)
786 }
787}
788
789impl FromStr for XpubAccount {
790 type Err = XkeyParseError;
791
792 fn from_str(s: &str) -> Result<Self, Self::Err> {
793 if !s.starts_with('[') {
794 return Err(XkeyParseError::NoOrigin);
795 }
796 let (origin, xpub) =
797 s.trim_start_matches('[').split_once(']').ok_or(XkeyParseError::NoOrigin)?;
798 let origin = XkeyOrigin::from_str(origin)?;
799 let xpub = Xpub::from_str(xpub)?;
800
801 if !origin.derivation.is_empty() {
802 let network = if xpub.testnet { HardenedIndex::ONE } else { HardenedIndex::ZERO };
803 if origin.derivation.get(1) != Some(&network) {
804 return Err(XkeyParseError::NetworkMismatch);
805 }
806 }
807
808 Ok(XpubAccount::new(xpub, origin)?)
809 }
810}
811
812#[derive(Getters, Eq, PartialEq, Debug)]
813pub struct XprivAccount {
814 origin: XkeyOrigin,
815 xpriv: Xpriv,
816}
817
818impl XprivAccount {
819 pub fn new_master(xpriv: Xpriv) -> Self {
820 Self {
821 origin: XkeyOrigin::new_master(xpriv.fingerprint()),
822 xpriv,
823 }
824 }
825
826 pub fn with_seed(testnet: bool, seed: &[u8]) -> Self {
828 let xpriv = Xpriv::new_master(testnet, seed);
829 Self::new_master(xpriv)
830 }
831
832 pub fn new(xpriv: Xpriv, origin: XkeyOrigin) -> Result<Self, XkeyAccountError> {
833 if xpriv.meta.depth as usize != origin.derivation.len() {
834 return Err(XkeyAccountError::DepthMismatch);
835 }
836 if origin.derivation.last().copied().map(DerivationIndex::Hardened)
837 != Some(xpriv.meta.child_number)
838 {
839 return Err(XkeyAccountError::ParentMismatch);
840 }
841 if xpriv.meta.depth == 1 && xpriv.meta.parent_fp != origin.master_fp {
842 return Err(XkeyAccountError::MasterMismatch);
843 }
844 Ok(XprivAccount { xpriv, origin })
845 }
846
847 pub fn to_xpub_account(&self) -> XpubAccount {
848 XpubAccount {
849 origin: self.origin.clone(),
850 xpub: self.xpriv.to_xpub(),
851 }
852 }
853
854 #[inline]
855 pub const fn master_fp(&self) -> XpubFp { self.origin.master_fp }
856
857 #[inline]
858 pub fn account_fp(&self) -> XpubFp { self.xpriv.fingerprint() }
859
860 #[inline]
861 pub fn account_id(&self) -> XpubId { self.xpriv.identifier() }
862
863 #[inline]
864 pub fn derivation(&self) -> &[HardenedIndex] { self.origin.derivation.as_ref() }
865
866 #[inline]
867 pub const fn as_derivation(&self) -> &DerivationPath<HardenedIndex> { &self.origin.derivation }
868
869 #[inline]
870 pub fn to_derivation(&self) -> DerivationPath { self.origin.to_derivation() }
871
872 #[must_use]
873 pub fn derive(&self, path: impl AsRef<[HardenedIndex]>) -> Self {
874 let path = path.as_ref();
875 let xpriv = self.xpriv.derive_priv(path);
876 let mut prev = DerivationPath::with_capacity(self.origin.derivation.len() + path.len());
877 prev.extend(&self.origin.derivation);
878 prev.extend(path);
879 XprivAccount {
880 origin: XkeyOrigin {
881 master_fp: self.origin.master_fp,
882 derivation: prev,
883 },
884 xpriv,
885 }
886 }
887}
888
889impl Display for XprivAccount {
890 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
891 f.write_str("[")?;
892 Display::fmt(&self.origin, f)?;
893 f.write_str("]")?;
894 write!(f, "{}", self.xpriv)
895 }
896}
897
898impl FromStr for XprivAccount {
899 type Err = XkeyParseError;
900
901 fn from_str(s: &str) -> Result<Self, Self::Err> {
902 if !s.starts_with('[') {
903 return Err(XkeyParseError::NoOrigin);
904 }
905 let (origin, xpriv) =
906 s.trim_start_matches('[').split_once(']').ok_or(XkeyParseError::NoOrigin)?;
907 let origin = XkeyOrigin::from_str(origin)?;
908 let xpriv = Xpriv::from_str(xpriv)?;
909
910 if !origin.derivation.is_empty() {
911 let network = if xpriv.testnet { HardenedIndex::ONE } else { HardenedIndex::ZERO };
912 if origin.derivation.get(1) != Some(&network) {
913 return Err(XkeyParseError::NetworkMismatch);
914 }
915 }
916
917 Ok(XprivAccount::new(xpriv, origin)?)
918 }
919}
920
921#[derive(Getters, Clone, Eq, PartialEq, Hash, Debug)]
922pub struct XpubDerivable {
923 spec: XpubAccount,
924 #[getter(as_copy)]
925 variant: Option<NormalIndex>,
926 pub(crate) keychains: DerivationSeg<Keychain>,
927}
928
929impl From<XpubAccount> for XpubDerivable {
930 fn from(spec: XpubAccount) -> Self {
931 XpubDerivable {
932 spec,
933 variant: None,
934 keychains: DerivationSeg::from([Keychain::INNER, Keychain::OUTER]),
935 }
936 }
937}
938
939impl XpubDerivable {
940 pub fn with(spec: XpubAccount, keychains: &'static [Keychain]) -> Self {
941 XpubDerivable {
942 spec,
943 variant: None,
944 keychains: DerivationSeg::from(keychains),
945 }
946 }
947
948 pub fn try_standard(xpub: Xpub, origin: XkeyOrigin) -> Result<Self, XkeyAccountError> {
949 Ok(XpubDerivable {
950 spec: XpubAccount::new(xpub, origin)?,
951 variant: None,
952 keychains: DerivationSeg::from([Keychain::INNER, Keychain::OUTER]),
953 })
954 }
955
956 pub fn try_custom(
957 xpub: Xpub,
958 origin: XkeyOrigin,
959 keychains: impl IntoIterator<Item = Keychain>,
960 ) -> Result<Self, XkeyAccountError> {
961 Ok(XpubDerivable {
962 spec: XpubAccount::new(xpub, origin)?,
963 variant: None,
964 keychains: DerivationSeg::with(keychains)
965 .map_err(|_| XkeyAccountError::TooManyKeychains)?,
966 })
967 }
968
969 pub fn xpub(&self) -> Xpub { self.spec.xpub }
970
971 pub fn origin(&self) -> &XkeyOrigin { &self.spec.origin }
972}
973
974impl Display for XpubDerivable {
975 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
976 Display::fmt(&self.spec, f)?;
977 f.write_str("/")?;
978 if let Some(variant) = self.variant {
979 write!(f, "{variant}/")?;
980 }
981 Display::fmt(&self.keychains, f)?;
982 f.write_str("/*")
983 }
984}
985
986impl FromStr for XpubDerivable {
987 type Err = XkeyParseError;
988
989 fn from_str(s: &str) -> Result<Self, Self::Err> {
990 if !s.starts_with('[') {
991 return Err(XkeyParseError::NoOrigin);
992 }
993 let (origin, remains) =
994 s.trim_start_matches('[').split_once(']').ok_or(XkeyParseError::NoOrigin)?;
995
996 let origin = XkeyOrigin::from_str(origin)?;
997 let mut segs = remains.split('/');
998 let Some(xpub) = segs.next() else {
999 return Err(XkeyParseError::NoXpub);
1000 };
1001 let xpub = Xpub::from_str(xpub)?;
1002
1003 let (variant, keychains) = match (segs.next(), segs.next(), segs.next(), segs.next()) {
1004 (Some(var), Some(keychains), Some("*"), None) => {
1005 (Some(var.parse()?), keychains.parse()?)
1006 }
1007 (Some(keychains), Some("*"), None, None) => (None, keychains.parse()?),
1008 _ => return Err(XkeyParseError::InvalidTerminal),
1009 };
1010
1011 Ok(XpubDerivable {
1012 spec: XpubAccount::new(xpub, origin)?,
1013 variant,
1014 keychains,
1015 })
1016 }
1017}
1018
1019#[cfg(feature = "serde")]
1020mod _serde {
1021 use serde_crate::{de, Deserialize, Deserializer, Serialize, Serializer};
1022
1023 use super::*;
1024
1025 impl Serialize for Xpub {
1026 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1027 where S: Serializer {
1028 if serializer.is_human_readable() {
1029 serializer.serialize_str(&self.to_string())
1030 } else {
1031 serializer.serialize_bytes(&self.encode())
1032 }
1033 }
1034 }
1035
1036 impl<'de> Deserialize<'de> for Xpub {
1037 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1038 where D: Deserializer<'de> {
1039 if deserializer.is_human_readable() {
1040 let s = String::deserialize(deserializer)?;
1041 Xpub::from_str(&s).map_err(|err| {
1042 de::Error::custom(format!("invalid xpub string representation; {err}"))
1043 })
1044 } else {
1045 let v = Vec::<u8>::deserialize(deserializer)?;
1046 Xpub::decode(v)
1047 .map_err(|err| de::Error::custom(format!("invalid xpub bytes; {err}")))
1048 }
1049 }
1050 }
1051
1052 impl Serialize for XpubAccount {
1053 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1054 where S: Serializer {
1055 serializer.serialize_str(&self.to_string())
1056 }
1057 }
1058
1059 impl<'de> Deserialize<'de> for XpubAccount {
1060 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1061 where D: Deserializer<'de> {
1062 let s = String::deserialize(deserializer)?;
1063 XpubAccount::from_str(&s).map_err(|err| {
1064 de::Error::custom(format!(
1065 "invalid xpub specification string representation; {err}"
1066 ))
1067 })
1068 }
1069 }
1070
1071 impl Serialize for XpubDerivable {
1072 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1073 where S: Serializer {
1074 serializer.serialize_str(&self.to_string())
1075 }
1076 }
1077
1078 impl<'de> Deserialize<'de> for XpubDerivable {
1079 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1080 where D: Deserializer<'de> {
1081 let s = String::deserialize(deserializer)?;
1082 XpubDerivable::from_str(&s).map_err(|err| {
1083 de::Error::custom(format!("invalid xpub derivation string representation; {err}"))
1084 })
1085 }
1086 }
1087}
1088
1089#[cfg(test)]
1090mod test {
1091 use super::*;
1092 use crate::h;
1093
1094 #[test]
1095 fn xpub_derivable_from_str_with_hardened_index() {
1096 let s = "[643a7adc/86h/1h/0h]tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1>/*";
1097 let xpub = XpubDerivable::from_str(s).unwrap();
1098 assert_eq!(s, xpub.to_string());
1099 }
1100
1101 #[test]
1102 fn xpub_derivable_from_str_with_normal_index() {
1103 let s = "[643a7adc/86'/1'/0']tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1>/*";
1104 let xpub = XpubDerivable::from_str(s).unwrap();
1105 assert_eq!(s, format!("{xpub:#}"));
1106 }
1107
1108 #[test]
1109 fn xpub_derivable_from_str_with_normal_index_rgb_keychain() {
1110 let s = "[643a7adc/86'/1'/0']tpubDCNiWHaiSkgnQjuhsg9kjwaUzaxQjUcmhagvYzqQ3TYJTgFGJstVaqnu4yhtFktBhCVFmBNLQ5sN53qKzZbMksm3XEyGJsEhQPfVZdWmTE2/<0;1;9;10>/*";
1111 let xpub = XpubDerivable::from_str(s).unwrap();
1112 assert_eq!(s, format!("{xpub:#}"));
1113 }
1114
1115 #[test]
1116 fn xpriv_account_display_fromstr() {
1117 use secp256k1::rand::{self, RngCore};
1118
1119 let mut seed = vec![0u8; 128];
1120 rand::thread_rng().fill_bytes(&mut seed);
1121
1122 let xpriv_account = XprivAccount::with_seed(true, &seed).derive(h![86, 1, 0]);
1123 let xpriv_account_str = xpriv_account.to_string();
1124 let recovered = XprivAccount::from_str(&xpriv_account_str).unwrap();
1125 assert_eq!(recovered, xpriv_account);
1126 }
1127
1128 #[test]
1129 fn xpriv_derivable() {
1130 use secp256k1::rand::{self, RngCore};
1131
1132 let mut seed = vec![0u8; 128];
1133 rand::thread_rng().fill_bytes(&mut seed);
1134
1135 let derivation = DerivationPath::from(h![86, 1, 0]);
1136 let xpriv_account = XprivAccount::with_seed(true, &seed).derive(&derivation);
1137 let xpub_account = xpriv_account.to_xpub_account();
1138 let derivable_other =
1139 XpubDerivable::try_custom(xpub_account.xpub, xpub_account.origin.clone(), [
1140 Keychain::INNER,
1141 Keychain::OUTER,
1142 ])
1143 .unwrap();
1144 let derivable = XpubDerivable::with(xpub_account, &[Keychain::INNER, Keychain::OUTER]);
1145 assert_eq!(derivable, derivable_other);
1146 assert_eq!(derivable.spec.origin, xpriv_account.origin);
1147 assert_eq!(derivable.spec.origin.derivation, derivation);
1148 }
1149}