derive/
xkey.rs

1// Modern, minimalistic & standard-compliant cold wallet library.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2020-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2020-2024 LNP/BP Standards Association. All rights reserved.
9// Copyright (C) 2020-2024 Dr Maxim Orlovsky. All rights reserved.
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22
23use 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    /// mismatch between extended key depth and length of the derivation path in the key origin
49    /// information.
50    DepthMismatch,
51
52    /// extended key child derivation index does not match the last derivation index in the
53    /// provided origin information.
54    ParentMismatch,
55
56    /// extended key has derivation depth 1, but its parent fingerprint does not match the provided
57    /// master key fingerprint.
58    MasterMismatch,
59
60    /// attempt to create an extended key account with too many derivation indexes in the keychain
61    /// segment.
62    TooManyKeychains,
63}
64
65#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
66#[display(doc_comments)]
67pub enum XkeyDecodeError {
68    /// wrong length of extended pubkey data ({0}).
69    WrongExtendedKeyLength(usize),
70
71    /// provided key is not a standard BIP-32 extended pubkey
72    UnknownKeyType([u8; 4]),
73
74    /// extended pubkey contains {0}
75    #[from]
76    #[from(bc::secp256k1::Error)]
77    InvalidPubkey(InvalidPubkey<33>),
78
79    /// xpriv contains invalid byte for the secret key type ({0:#04x}) which must be set to zero.
80    InvalidType(u8),
81
82    /// xpriv contains invalid data with secret key value overflowing over field order.
83    InvalidSecretKey,
84}
85
86#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
87#[display(doc_comments)]
88pub enum XkeyParseError {
89    /// wrong Base58 encoding of extended pubkey data - {0}
90    #[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    /// invalid master key fingerprint - {0}
102    #[from]
103    InvalidMasterFp(hex::Error),
104
105    /// invalid terminal derivation format.
106    InvalidTerminal,
107
108    /// invalid keychain segment - {0}
109    #[from]
110    InvalidKeychain(SegParseError),
111
112    /// invalid index value in terminal derivation segment.
113    #[from]
114    InvalidIndex(IndexParseError),
115
116    /// no xpub key origin information.
117    NoOrigin,
118
119    /// no extended public key.
120    NoXpub,
121
122    /// mismatch between extended key network and network specified in the key origin.
123    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/// BIP32 chain code used for hierarchical derivation
140#[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/// Deterministic part of the extended public key.
157#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
158pub struct XpubCore {
159    /// Public key
160    pub public_key: CompressedPk,
161    /// BIP32 chain code used for hierarchical derivation
162    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    /// Returns the HASH160 of the chaincode
288    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    /// Constructs ECDSA public key valid in legacy context (compressed by default).
301    pub fn to_legacy_pk(&self) -> LegacyPk { LegacyPk::compressed(*self.core.public_key) }
302
303    /// Constructs ECDSA public key.
304    pub fn to_compr_pk(&self) -> CompressedPk { self.core.public_key }
305
306    /// Constructs BIP340 public key matching internal public key representation.
307    pub fn to_xonly_pk(&self) -> XOnlyPk { XOnlyPk::from(self.core.public_key) }
308
309    /// Attempts to derive an extended public key from a path.
310    ///
311    /// The `path` argument can be any type implementing `AsRef<ChildNumber>`, such as
312    /// `DerivationPath`, for instance.
313    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    /// Compute the scalar tweak added to this key to get a child key
322    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    /// Public->Public child key derivation
340    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/// Deterministic part of the extended public key.
380#[derive(Copy, Clone, Eq, PartialEq)]
381pub struct XprivCore {
382    /// Secret key
383    pub private_key: SecretKey,
384    /// BIP32 chain code used for hierarchical derivation
385    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    // TODO #42: Use dedicated `Seed` type
406    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    /// Attempts to derive an extended private key from a path.
533    ///
534    /// The `path` argument can be both of type `DerivationPath` or `Vec<ChildNumber>`.
535    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    /// Private->Private child key derivation
544    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                // Non-hardened key: compute public data and use that
552                hmac.update(
553                    &PublicKey::from_secret_key(SECP256K1, &self.core.private_key).serialize(),
554                );
555            }
556            DerivationIndex::Hardened(_) => {
557                // Hardened key: use only secret data to prevent public derivation
558                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    /// invalid derivation path - {0}
675    #[from]
676    DerivationPath(DerivationParseError),
677
678    /// invalid master key fingerprint - {0}
679    #[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    // TODO #42: Use dedicated `Seed` type
830    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}