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