derive/
derive.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::cmp::Ordering;
24use std::collections::BTreeSet;
25use std::fmt::{Debug, Display};
26use std::hash::Hash;
27use std::iter;
28use std::num::ParseIntError;
29use std::str::FromStr;
30
31use amplify::Wrapper;
32use bc::{
33    CompressedPk, ControlBlock, InternalPk, LeafScript, LegacyPk, PubkeyHash, RedeemScript,
34    ScriptPubkey, TapNodeHash, WitnessScript, XOnlyPk,
35};
36use indexmap::IndexMap;
37
38use crate::{
39    Address, AddressNetwork, AddressParseError, ControlBlockFactory, DerivationIndex, IdxBase,
40    IndexParseError, NormalIndex, TapTree, XpubAccount, XpubDerivable,
41};
42
43#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display, From)]
44#[wrapper(FromStr)]
45#[display(inner)]
46pub struct Keychain(u8);
47
48impl From<Keychain> for NormalIndex {
49    #[inline]
50    fn from(keychain: Keychain) -> Self { NormalIndex::from(keychain.0) }
51}
52
53impl From<Keychain> for DerivationIndex {
54    #[inline]
55    fn from(keychain: Keychain) -> Self { DerivationIndex::Normal(keychain.into()) }
56}
57
58impl Keychain {
59    pub const OUTER: Self = Keychain(0);
60    pub const INNER: Self = Keychain(1);
61
62    pub const fn with(idx: u8) -> Self { Keychain(idx) }
63}
64
65impl IdxBase for Keychain {
66    #[inline]
67    fn is_hardened(&self) -> bool { false }
68
69    #[inline]
70    fn child_number(&self) -> u32 { self.0 as u32 }
71
72    #[inline]
73    fn index(&self) -> u32 { self.0 as u32 }
74}
75
76#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Display)]
77#[display("&{keychain}/{index}")]
78pub struct Terminal {
79    pub keychain: Keychain,
80    pub index: NormalIndex,
81}
82
83impl Terminal {
84    pub fn new(keychain: impl Into<Keychain>, index: NormalIndex) -> Self {
85        Terminal {
86            keychain: keychain.into(),
87            index,
88        }
89    }
90    pub fn change(index: NormalIndex) -> Self { Self::new(1, index) }
91}
92
93#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
94#[display(doc_comments)]
95pub enum TerminalParseError {
96    /// terminal derivation path must start with keychain index prefixed with '&'.
97    NoKeychain,
98
99    /// keychain index in terminal derivation path is not a number.
100    #[from]
101    InvalidKeychain(ParseIntError),
102
103    #[from]
104    Index(IndexParseError),
105
106    /// derivation path '{0}' is not a terminal path - terminal path must contain exactly two
107    /// components.
108    InvalidComponents(String),
109}
110
111impl FromStr for Terminal {
112    type Err = TerminalParseError;
113
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        let mut iter = s.split('/');
116        match (iter.next(), iter.next(), iter.next()) {
117            (Some(keychain), Some(index), None) => {
118                if !keychain.starts_with('&') {
119                    return Err(TerminalParseError::NoKeychain);
120                }
121                Ok(Terminal::new(
122                    Keychain::from_str(keychain.trim_start_matches('&'))?,
123                    index.parse()?,
124                ))
125            }
126            _ => Err(TerminalParseError::InvalidComponents(s.to_owned())),
127        }
128    }
129}
130
131#[cfg(feature = "serde")]
132mod _serde {
133    use serde::de::Error;
134    use serde::{Deserialize, Deserializer, Serialize, Serializer};
135
136    use super::*;
137
138    impl Serialize for Keychain {
139        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140        where S: Serializer {
141            if serializer.is_human_readable() {
142                self.0.to_string().serialize(serializer)
143            } else {
144                self.0.serialize(serializer)
145            }
146        }
147    }
148
149    impl<'de> Deserialize<'de> for Keychain {
150        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151        where D: Deserializer<'de> {
152            if deserializer.is_human_readable() {
153                let s = String::deserialize(deserializer)?;
154                Self::from_str(&s).map_err(D::Error::custom)
155            } else {
156                Ok(Self(u8::deserialize(deserializer)?))
157            }
158        }
159    }
160
161    impl Serialize for Terminal {
162        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
163        where S: Serializer {
164            if serializer.is_human_readable() {
165                self.to_string().serialize(serializer)
166            } else {
167                let tuple = (self.keychain, self.index);
168                tuple.serialize(serializer)
169            }
170        }
171    }
172
173    impl<'de> Deserialize<'de> for Terminal {
174        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175        where D: Deserializer<'de> {
176            if deserializer.is_human_readable() {
177                let s = String::deserialize(deserializer)?;
178                Self::from_str(&s).map_err(D::Error::custom)
179            } else {
180                let d = <(Keychain, NormalIndex)>::deserialize(deserializer)?;
181                Ok(Self {
182                    keychain: d.0,
183                    index: d.1,
184                })
185            }
186        }
187    }
188}
189
190#[derive(Clone, Eq, PartialEq, Hash, Debug)]
191#[non_exhaustive]
192pub enum DerivedScript {
193    Bare(ScriptPubkey),
194    Bip13(RedeemScript),
195    Segwit(WitnessScript),
196    NestedKey(CompressedPk),
197    NestedScript(WitnessScript),
198    TaprootKeyOnly(InternalPk),
199    TaprootScript(InternalPk, TapTree),
200}
201
202impl DerivedScript {
203    pub fn to_script_pubkey(&self) -> ScriptPubkey {
204        match self {
205            DerivedScript::Bare(script_pubkey) => script_pubkey.clone(),
206            DerivedScript::Bip13(redeem_script) => redeem_script.to_script_pubkey(),
207            DerivedScript::Segwit(witness_script) => witness_script.to_script_pubkey(),
208            DerivedScript::NestedKey(_) => self
209                .to_redeem_script()
210                .expect("redeedm script must be defined for ShWpkh")
211                .to_script_pubkey(),
212
213            DerivedScript::NestedScript(witness_script) => {
214                witness_script.to_redeem_script().to_script_pubkey()
215            }
216            DerivedScript::TaprootKeyOnly(internal_key) => {
217                ScriptPubkey::p2tr_key_only(*internal_key)
218            }
219            DerivedScript::TaprootScript(internal_pk, tap_tree) => {
220                internal_pk.to_output_pk(Some(tap_tree.merkle_root())).0.to_script_pubkey()
221            }
222        }
223    }
224
225    pub fn to_redeem_script(&self) -> Option<RedeemScript> {
226        match self {
227            DerivedScript::Bare(_) => None,
228            DerivedScript::Bip13(redeem_script) => Some(redeem_script.clone()),
229            DerivedScript::Segwit(_) => None,
230            DerivedScript::NestedKey(pk) => Some(RedeemScript::from_checked(
231                ScriptPubkey::p2pkh(PubkeyHash::from(*pk)).into_inner().into_vec(),
232            )),
233            DerivedScript::NestedScript(witness_script) => Some(witness_script.to_redeem_script()),
234            DerivedScript::TaprootKeyOnly(_) => None,
235            DerivedScript::TaprootScript(_, _) => None,
236        }
237    }
238    pub fn as_witness_script(&self) -> Option<&WitnessScript> {
239        match self {
240            DerivedScript::Bare(_) => None,
241            DerivedScript::Bip13(_) => None,
242            DerivedScript::NestedKey(_) => None,
243            DerivedScript::Segwit(witness_script) | DerivedScript::NestedScript(witness_script) => {
244                Some(witness_script)
245            }
246            DerivedScript::TaprootKeyOnly(_) => None,
247            DerivedScript::TaprootScript(_, _) => None,
248        }
249    }
250    pub fn to_witness_script(&self) -> Option<WitnessScript> { self.as_witness_script().cloned() }
251
252    pub fn to_internal_pk(&self) -> Option<InternalPk> {
253        match self {
254            DerivedScript::Bare(_)
255            | DerivedScript::Bip13(_)
256            | DerivedScript::Segwit(_)
257            | DerivedScript::NestedKey(_)
258            | DerivedScript::NestedScript(_) => None,
259            DerivedScript::TaprootKeyOnly(internal_key) => Some(*internal_key),
260            DerivedScript::TaprootScript(internal_key, _) => Some(*internal_key),
261        }
262    }
263
264    pub fn as_tap_tree(&self) -> Option<&TapTree> {
265        match self {
266            DerivedScript::Bare(_)
267            | DerivedScript::Bip13(_)
268            | DerivedScript::Segwit(_)
269            | DerivedScript::NestedKey(_)
270            | DerivedScript::NestedScript(_)
271            | DerivedScript::TaprootKeyOnly(_) => None,
272            DerivedScript::TaprootScript(_, tap_tree) => Some(tap_tree),
273        }
274    }
275
276    pub fn to_tap_tree(&self) -> Option<TapTree> { self.as_tap_tree().cloned() }
277
278    pub fn to_leaf_scripts(&self) -> IndexMap<ControlBlock, LeafScript> {
279        let (Some(internal_pk), Some(tap_tree)) = (self.to_internal_pk(), self.to_tap_tree())
280        else {
281            return empty!();
282        };
283        ControlBlockFactory::with(internal_pk, tap_tree).collect()
284    }
285
286    #[inline]
287    pub fn to_tap_root(&self) -> Option<TapNodeHash> {
288        self.to_tap_tree().as_ref().map(TapTree::merkle_root)
289    }
290}
291
292#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
293#[cfg_attr(
294    feature = "serde",
295    derive(serde::Serialize, serde::Deserialize),
296    serde(rename_all = "camelCase")
297)]
298#[display("{addr}{terminal}")]
299pub struct DerivedAddr {
300    pub addr: Address,
301    pub terminal: Terminal,
302}
303
304impl Ord for DerivedAddr {
305    fn cmp(&self, other: &Self) -> Ordering { self.terminal.cmp(&other.terminal) }
306}
307
308impl PartialOrd for DerivedAddr {
309    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
310}
311
312impl DerivedAddr {
313    pub fn new(addr: Address, keychain: Keychain, index: NormalIndex) -> Self {
314        DerivedAddr {
315            addr,
316            terminal: Terminal::new(keychain, index),
317        }
318    }
319}
320
321#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
322#[display(inner)]
323pub enum DerivedAddrParseError {
324    #[display("address must be followed by a & and derivation information")]
325    NoSeparator,
326
327    #[from]
328    Address(AddressParseError),
329
330    #[from]
331    Terminal(TerminalParseError),
332}
333
334impl FromStr for DerivedAddr {
335    type Err = DerivedAddrParseError;
336
337    fn from_str(s: &str) -> Result<Self, Self::Err> {
338        let pos = s.find('&').ok_or(DerivedAddrParseError::NoSeparator)?;
339        let (addr, terminal) = s.split_at(pos);
340        Ok(DerivedAddr {
341            addr: addr.parse()?,
342            terminal: terminal.parse()?,
343        })
344    }
345}
346
347pub trait Derive<D> {
348    // TODO: Make D an associated type (since each descriptor must derive only one type of keys).
349
350    fn default_keychain(&self) -> Keychain;
351
352    fn keychains(&self) -> BTreeSet<Keychain>;
353
354    fn derive(
355        &self,
356        keychain: impl Into<Keychain>,
357        index: impl Into<NormalIndex>,
358    ) -> impl Iterator<Item = D>;
359
360    fn derive_range(
361        &self,
362        keychain: impl Into<Keychain>,
363        from: impl Into<NormalIndex>,
364        to: impl Into<NormalIndex>,
365    ) -> impl Iterator<Item = D> {
366        let from = from.into().child_number();
367        let to = to.into().child_number();
368        let keychain = keychain.into();
369        (from..to)
370            .flat_map(move |index| self.derive(keychain, NormalIndex::normal_unchecked(index)))
371    }
372}
373
374pub trait DeriveKey<D>: Derive<D> + Clone + Eq + Hash + Debug + Display {
375    fn xpub_spec(&self) -> &XpubAccount;
376}
377
378pub trait DeriveLegacy: DeriveKey<LegacyPk> {}
379impl<T: DeriveKey<LegacyPk>> DeriveLegacy for T {}
380
381pub trait DeriveCompr: DeriveKey<CompressedPk> {}
382impl<T: DeriveKey<CompressedPk>> DeriveCompr for T {}
383
384pub trait DeriveXOnly: DeriveKey<XOnlyPk> {}
385impl<T: DeriveKey<XOnlyPk>> DeriveXOnly for T {}
386
387pub trait DeriveScripts: Derive<DerivedScript> {
388    /// Derives addresses for a given index.
389    ///
390    /// If the descriptor is not representable in form of an address (uses non-standard script etc),
391    /// returns an empty iterator.
392    fn derive_address(
393        &self,
394        network: AddressNetwork,
395        keychain: impl Into<Keychain>,
396        index: impl Into<NormalIndex>,
397    ) -> impl Iterator<Item = Address> {
398        self.derive(keychain, index)
399            .flat_map(move |spk| Address::with(&spk.to_script_pubkey(), network).ok())
400    }
401
402    /// Derives addresses for a range of indexes.
403    ///
404    /// If the descriptor is not representable in form of an address (uses non-standard script etc),
405    /// returns an empty iterator.
406    fn derive_address_range(
407        &self,
408        network: AddressNetwork,
409        keychain: impl Into<Keychain>,
410        from: impl Into<NormalIndex>,
411        to: impl Into<NormalIndex>,
412    ) -> impl Iterator<Item = Address> {
413        self.derive_range(keychain, from, to)
414            .flat_map(move |spk| Address::with(&spk.to_script_pubkey(), network).ok())
415    }
416}
417impl<T: Derive<DerivedScript>> DeriveScripts for T {}
418
419impl DeriveKey<LegacyPk> for XpubDerivable {
420    fn xpub_spec(&self) -> &XpubAccount { self.spec() }
421}
422
423impl DeriveKey<CompressedPk> for XpubDerivable {
424    fn xpub_spec(&self) -> &XpubAccount { self.spec() }
425}
426
427impl DeriveKey<XOnlyPk> for XpubDerivable {
428    fn xpub_spec(&self) -> &XpubAccount { self.spec() }
429}
430
431impl Derive<LegacyPk> for XpubDerivable {
432    #[inline]
433    fn default_keychain(&self) -> Keychain { self.keychains.first() }
434
435    #[inline]
436    fn keychains(&self) -> BTreeSet<Keychain> { self.keychains.to_set() }
437
438    fn derive(
439        &self,
440        keychain: impl Into<Keychain>,
441        index: impl Into<NormalIndex>,
442    ) -> impl Iterator<Item = LegacyPk> {
443        iter::once(self.xpub().derive_pub([keychain.into().into(), index.into()]).to_legacy_pk())
444    }
445}
446
447impl Derive<CompressedPk> for XpubDerivable {
448    #[inline]
449    fn default_keychain(&self) -> Keychain { self.keychains.first() }
450
451    #[inline]
452    fn keychains(&self) -> BTreeSet<Keychain> { self.keychains.to_set() }
453
454    fn derive(
455        &self,
456        keychain: impl Into<Keychain>,
457        index: impl Into<NormalIndex>,
458    ) -> impl Iterator<Item = CompressedPk> {
459        iter::once(self.xpub().derive_pub([keychain.into().into(), index.into()]).to_compr_pk())
460    }
461}
462
463impl Derive<XOnlyPk> for XpubDerivable {
464    #[inline]
465    fn default_keychain(&self) -> Keychain { self.keychains.first() }
466
467    #[inline]
468    fn keychains(&self) -> BTreeSet<Keychain> { self.keychains.to_set() }
469
470    fn derive(
471        &self,
472        keychain: impl Into<Keychain>,
473        index: impl Into<NormalIndex>,
474    ) -> impl Iterator<Item = XOnlyPk> {
475        iter::once(self.xpub().derive_pub([keychain.into().into(), index.into()]).to_xonly_pk())
476    }
477}
478
479pub trait DeriveSet {
480    type Legacy: DeriveLegacy;
481    type Compr: DeriveCompr;
482    type XOnly: DeriveXOnly;
483}
484
485impl DeriveSet for XpubDerivable {
486    type Legacy = XpubDerivable;
487    type Compr = XpubDerivable;
488    type XOnly = XpubDerivable;
489}