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