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}