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