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