diff --git a/Cargo.lock b/Cargo.lock index e1bfba47..a120f452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -823,9 +823,9 @@ dependencies = [ [[package]] name = "miniscript" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f8542804cd816cad1aaea64e3e2e326e401269ad6dc6661d2f8be7b3f76e46" +checksum = "cc7da049151be4bc7f8da628f5e862e6fae3fe5d7c8201a746cb8373735e789b" dependencies = [ "bitcoin", "serde 1.0.117", diff --git a/Cargo.toml b/Cargo.toml index 9004a5f6..6b21373b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ bitcoin = { version = "~0.25.1", features = ["rand"] } bitcoin_hashes = "~0.9.3" # we need macro from here # # We have to fix version of miniscript as required by LNPBP-2 specification -miniscript = { version = "~3.0.0", features = ["compiler"] } +miniscript = { version = "~4.0.1", features = ["compiler"] } bech32 = "~0.7.2" lightning-invoice = { version = "~0.3.0", optional = true } chacha20poly1305 = "~0.7.0" diff --git a/src/bp/derivation/mod.rs b/src/bp/derivation/mod.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/bp/derivation/slip32.rs b/src/bp/derivation/slip32.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/bp/invoice.rs b/src/bp/invoice.rs new file mode 100644 index 00000000..65e5b045 --- /dev/null +++ b/src/bp/invoice.rs @@ -0,0 +1,128 @@ +// LNP/BP Core Library implementing LNPBP specifications & standards +// Written in 2020 by +// Dr. Maxim Orlovsky +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the MIT License +// along with this software. +// If not, see . + +use bitcoin::hashes::sha256d; +use bitcoin::secp256k1::Signature; +use bitcoin::Address; +use miniscript::{descriptor::DescriptorPublicKey, Descriptor, Miniscript}; +use url::Url; + +use crate::bp::blind::OutpointHash; +use crate::bp::chain::AssetId; +use crate::bp::{Chain, HashLock, P2pNetworkId, Psbt, ScriptPubkeyFormat}; +use crate::lnp::{tlv, Features}; +use crate::secp256k1; + +#[derive(Tlv)] +#[lnpbp_crate(crate)] +pub enum FieldType { + #[tlv(type = 0x01)] + Payers, +} + +#[derive(Lnp)] +#[tlv_types(FieldType)] +#[lnpbp_crate(crate)] +pub struct Invoice { + network: P2pNetworkId, + + /// List of beneficiary dests ordered in most desirable first order + beneficiaries: Vec, + + /// Optional list of payers authored to pay + #[tlv(type = FieldType::Payers)] + payers: Vec, + quantity: Quantity, + price: Option, + + /// If the price of the asset provided by fiat provider URL goes below this + /// limit the merchant will not accept the payment and it will become + /// expired + fiat_requirement: Option, + merchant: Option, + asset: Option, + purpose: Option, + details: Option
, + expiry: Option, + + #[tlv_unknown] + unknown: Vec, + signature: Option, +} + +#[non_exhaustive] +pub enum Beneficiary { + /// Addresses are usefult when you do not like to leak public key + /// information + Address(Address), + + /// Ssed by protocols that work with existing UTXOs and can assign some + /// client-validated data to them (like in RGB). We always hide the real + /// UTXO behind the hashed version (using some salt) + BlindUtxo(OutpointHash), + + /// Miniscript-based descriptors allowing custom derivation & key generation + Descriptor(Descriptor), + + /// Full transaction template in PSBT format + Psbt(Psbt), + + /// Lightning node receiving the payment. Not the same as lightning invoice + /// since many of the invoice data now will be part of [`Invoice`] here. + Lightning(LnAddress), + + /// Failback option for all future variants + Other(Vec), +} + +pub struct LnAddress { + node_id: secp256k1::PublicKey, + features: Features, + hash_lock: HashLock, + min_final_cltv_expiry: Option, + path_hints: Vec, +} + +/// Path hints for a lightning network payment, equal to the value of the `r` +/// key of the lightning BOLT-11 invoice +/// +pub struct LnPathHint { + node_id: secp256k1::PublicKey, + short_channel_id: ShortChannelId, + fee_base_msat: u32, + fee_proportional_millionths: u32, + cltv_expiry_delta: u16, +} + +pub enum AmountExt { + Normal(u64), + Milli(u64, u16), +} + +pub struct Details { + commitment: sha256d::Hash, + source: Url, +} + +pub struct Fiat { + iso4217: [u8; 3], + coins: u32, + fractions: u8, + price_provider: Url, +} + +pub struct Quantity { + min: Option, + max: Option, + default: u32, +} diff --git a/src/bp/mod.rs b/src/bp/mod.rs index 0e5d74e3..0735c5cd 100644 --- a/src/bp/mod.rs +++ b/src/bp/mod.rs @@ -17,6 +17,7 @@ pub mod blind; pub mod chain; pub mod dbc; pub mod hlc; +mod invoice; pub mod lex_order; pub mod psbt; pub mod resolvers; @@ -30,6 +31,7 @@ pub mod tagged_hash; pub use bip32::{DerivationInfo, DerivationTemplate}; pub use chain::{Chain, P2pNetworkId}; pub use hlc::{HashLock, HashPreimage}; +pub use invoice::Invoice; pub use lex_order::LexOrder; pub use psbt::Psbt; pub use scripts::{ diff --git a/src/bp/scripts/descriptors.rs b/src/bp/scripts/descriptors.rs new file mode 100644 index 00000000..44a0762a --- /dev/null +++ b/src/bp/scripts/descriptors.rs @@ -0,0 +1,262 @@ +// LNP/BP Core Library implementing LNPBP specifications & standards +// Written in 2020 by +// Dr. Maxim Orlovsky +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the MIT License +// along with this software. +// If not, see . + +use std::collections::HashMap; +use std::str::FromStr; + +use bitcoin::{self, blockdata::script::Error as ScriptError, Script}; +use hex::{self, FromHex}; +use miniscript::{self, Descriptor, Miniscript, ScriptContext, Terminal}; + +use super::TrackingKey; + +#[derive(Clone, PartialEq, Eq, Debug, Display, From, Error)] +#[display(doc_comments)] +pub enum Error { + /// Hex encoding error: {0} + #[from] + Hex(hex::Error), + + /// Bitcoin script error: {0} + #[from] + Script(ScriptError), + + /// Miniscript error + #[display("{0}")] + Miniscript(String), +} + +impl From for Error { + fn from(err: miniscript::Error) -> Self { + Error::Miniscript(err.to_string()) + } +} + +#[derive(Clone, PartialEq, Eq, Debug, StrictEncode, StrictDecode)] +pub struct DescriptorGenerator { + pub content: DescriptorContent, + pub types: DescriptorTypes, +} + +impl DescriptorGenerator { + pub fn descriptor(&self) -> String { + let single = self.content.is_singlesig(); + let mut d = vec![]; + if self.types.bare { + d.push(if single { "pk" } else { "bare" }); + } + if self.types.hashed { + d.push(if single { "pkh" } else { "sh" }); + } + if self.types.compat { + d.push(if single { "sh_wpkh" } else { "sh_wsh" }); + } + if self.types.segwit { + d.push(if single { "wpkh" } else { "wsh" }); + } + if self.types.taproot { + d.push("tpk"); + } + let data = match &self.content { + DescriptorContent::SingleSig(key) => key.to_string(), + DescriptorContent::MultiSig(threshold, keyset) => { + format!( + "thresh_m({},{})", + threshold, + keyset + .iter() + .map(TrackingKey::to_string) + .collect::>() + .join(",") + ) + } + DescriptorContent::LockScript(_, script) => script.clone(), + }; + format!("{}({})", d.join("|"), data) + } + + pub fn pubkey_scripts_count(&self) -> u32 { + self.types.bare as u32 + + self.types.hashed as u32 + + self.types.compat as u32 + + self.types.segwit as u32 + + self.types.taproot as u32 + } + + pub fn pubkey_scripts( + &self, + index: u32, + ) -> Result, Error> { + let mut scripts = HashMap::with_capacity(5); + let single = if let DescriptorContent::SingleSig(_) = self.content { + Some(self.content.public_key(index).expect("Can't fail")) + } else { + None + }; + if self.types.bare { + let d = if let Some(pk) = single { + Descriptor::Pk(pk) + } else { + Descriptor::Bare(self.content.miniscript(index)?) + }; + scripts.insert(DescriptorType::Bare, d.script_pubkey()); + } + if self.types.hashed { + let d = if let Some(pk) = single { + Descriptor::Pkh(pk) + } else { + Descriptor::Sh(self.content.miniscript(index)?) + }; + scripts.insert(DescriptorType::Hashed, d.script_pubkey()); + } + if self.types.compat { + let d = if let Some(pk) = single { + Descriptor::ShWpkh(pk) + } else { + Descriptor::ShWsh(self.content.miniscript(index)?) + }; + scripts.insert(DescriptorType::Compat, d.script_pubkey()); + } + if self.types.segwit { + let d = if let Some(pk) = single { + Descriptor::Wpkh(pk) + } else { + Descriptor::Wsh(self.content.miniscript(index)?) + }; + scripts.insert(DescriptorType::SegWit, d.script_pubkey()); + } + /* TODO: Enable once Taproot will go live + if self.taproot { + scripts.push(content.taproot()); + } + */ + Ok(scripts) + } +} + +#[derive( + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, + StrictEncode, + StrictDecode, +)] +pub enum DescriptorType { + Bare, + Hashed, + Compat, + SegWit, + Taproot, +} + +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, Debug, StrictEncode, StrictDecode, +)] +pub struct DescriptorTypes { + pub bare: bool, + pub hashed: bool, + pub compat: bool, + pub segwit: bool, + pub taproot: bool, +} + +impl DescriptorTypes { + pub fn has_match(&self, descriptor_type: DescriptorType) -> bool { + match descriptor_type { + DescriptorType::Bare => self.bare, + DescriptorType::Hashed => self.hashed, + DescriptorType::Compat => self.compat, + DescriptorType::SegWit => self.segwit, + DescriptorType::Taproot => self.taproot, + } + } +} + +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Debug, + Hash, + StrictEncode, + StrictDecode, +)] +pub enum DescriptorContent { + SingleSig(TrackingKey), + MultiSig(u8, Vec), + LockScript(SourceType, String), +} + +impl DescriptorContent { + pub fn is_singlesig(&self) -> bool { + match self { + DescriptorContent::SingleSig(_) => true, + _ => false, + } + } + + pub fn public_key(&self, index: u32) -> Option { + match self { + DescriptorContent::SingleSig(key) => Some(key.public_key(index)), + _ => None, + } + } + + pub fn miniscript( + &self, + index: u32, + ) -> Result, Error> + where + Ctx: ScriptContext, + { + Ok(match self { + DescriptorContent::SingleSig(key) => { + let pk = key.public_key(index); + Miniscript::from_ast(Terminal::PkK(pk))? + } + DescriptorContent::MultiSig(thresh, keyset) => { + let ks = keyset + .into_iter() + .map(|key| key.public_key(index)) + .collect(); + Miniscript::from_ast(Terminal::Multi(*thresh as usize, ks))? + } + DescriptorContent::LockScript(source_type, script) => { + match source_type { + SourceType::Binary => { + let script = Script::from(Vec::from_hex(script)?); + Miniscript::parse(&script)? + } + SourceType::Assembly => { + // TODO: Parse assembly + let script = Script::from(Vec::from_hex(script)?); + Miniscript::parse(&script)? + } + SourceType::Miniscript => Miniscript::from_str(script)?, + SourceType::Policy => { + // TODO: Compiler will require changes to LNP/BP + // policy::Concrete::from_str(script)?.compile()? + Miniscript::from_str(script)? + } + } + } + }) + } +} diff --git a/src/bp/scripts/mod.rs b/src/bp/scripts/mod.rs index 2c29fbdb..698b07bc 100644 --- a/src/bp/scripts/mod.rs +++ b/src/bp/scripts/mod.rs @@ -11,9 +11,9 @@ // along with this software. // If not, see . -pub mod descriptor; mod pubkey_parser; pub mod script_pubkey; +pub mod tweak; pub mod types; //pub use error::Error; diff --git a/src/bp/scripts/script_pubkey.rs b/src/bp/scripts/script_pubkey.rs index 7f69c57b..e3fe683b 100644 --- a/src/bp/scripts/script_pubkey.rs +++ b/src/bp/scripts/script_pubkey.rs @@ -59,6 +59,7 @@ pub enum ScriptPubkeyFormat { /// Initial standard used by Bitcoin Core (also codenamed "P2PK") /// that uses uncompressed public key serialization followed with /// `OP_CHECKSIG` code + /// Custom (i.e. non-standard) output with arbitrary script KeyChecksig, /// Script pubkey serialization according to widely accepted standard @@ -70,13 +71,6 @@ pub enum ScriptPubkeyFormat { /// Segwit script pubkey serialization according to BIP-141 /// Witness, - - /// Outputs containing OP_RETURN serialized according to - /// [Bitcoin Core-defined rules](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.12.0.md#relay-any-sequence-of-pushdatas-in-op_return-outputs-now-allowed) - OpReturn, - - /// Custom (i.e. non-standard) output with arbitrary script - Custom, } #[derive(Clone, PartialEq, Eq, Debug, Display)] diff --git a/src/bp/scripts/tracking.rs b/src/bp/scripts/tracking.rs new file mode 100644 index 00000000..3ea10fe1 --- /dev/null +++ b/src/bp/scripts/tracking.rs @@ -0,0 +1,395 @@ +// Bitcoin Pro: Professional bitcoin accounts & assets management +// Written in 2020 by +// Dr. Maxim Orlovsky +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the AGPL License +// along with this software. +// If not, see . + +use std::cmp::Ordering; +use std::fmt::{self, Display, Formatter}; +use std::io; +use std::iter::FromIterator; +use std::ops::RangeInclusive; + +use amplify::Wrapper; +use lnpbp::bitcoin::util::base58; +use lnpbp::bitcoin::util::bip32::{ + self, ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, +}; +use lnpbp::bp::bip32::Decode; +use lnpbp::strict_encoding::{self, StrictDecode, StrictEncode}; +use lnpbp::{bitcoin, secp256k1}; + +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + Display, + StrictEncode, + StrictDecode, +)] +#[display(TrackingKey::details)] +pub enum TrackingKey { + SingleKey(secp256k1::PublicKey), + HdKeySet(DerivationComponents), +} + +impl TrackingKey { + pub fn details(&self) -> String { + match self { + TrackingKey::SingleKey(ref pubkey) => pubkey.to_string(), + TrackingKey::HdKeySet(ref keyset) => keyset.to_string(), + } + } + + pub fn count(&self) -> u32 { + match self { + TrackingKey::SingleKey(_) => 1, + TrackingKey::HdKeySet(ref keyset) => keyset.count(), + } + } + + pub fn public_key(&self, index: u32) -> bitcoin::PublicKey { + match self { + TrackingKey::SingleKey(pk) => bitcoin::PublicKey { + compressed: true, + key: *pk, + }, + TrackingKey::HdKeySet(keyset) => keyset.public_key(index), + } + } +} + +// TODO: Consider moving the rest of the file to LNP/BP Core library + +/// Extended public and private key processing errors +#[derive(Copy, Clone, PartialEq, Eq, Debug, Display, From, Error)] +#[display(doc_comments)] +pub enum Error { + /// Error in BASE58 key encoding + #[from(base58::Error)] + Base58, + + /// A pk->pk derivation was attempted on a hardened key + CannotDeriveFromHardenedKey, + + /// A child number was provided ({0}) that was out of range + InvalidChildNumber(u32), + + /// Invalid child number format. + InvalidChildNumberFormat, + + /// Invalid derivation path format. + InvalidDerivationPathFormat, + + /// Unrecognized or unsupported extended key prefix (please check SLIP 32 + /// for possible values) + UnknownSlip32Prefix, + + /// Failure in tust bitcoin library + InteralFailure, +} + +impl From for Error { + fn from(err: bip32::Error) -> Self { + match err { + bip32::Error::CannotDeriveFromHardenedKey => { + Error::CannotDeriveFromHardenedKey + } + bip32::Error::InvalidChildNumber(no) => { + Error::InvalidChildNumber(no) + } + bip32::Error::InvalidChildNumberFormat => { + Error::InvalidChildNumberFormat + } + bip32::Error::InvalidDerivationPathFormat => { + Error::InvalidDerivationPathFormat + } + bip32::Error::Ecdsa(_) | bip32::Error::RngError(_) => { + Error::InteralFailure + } + } + } +} + +pub trait FromSlip32 { + fn from_slip32_str(s: &str) -> Result + where + Self: Sized; +} + +impl FromSlip32 for ExtendedPubKey { + fn from_slip32_str(s: &str) -> Result { + const VERSION_MAGIC_XPUB: [u8; 4] = [0x04, 0x88, 0xB2, 0x1E]; + const VERSION_MAGIC_YPUB: [u8; 4] = [0x04, 0x9D, 0x7C, 0xB2]; + const VERSION_MAGIC_ZPUB: [u8; 4] = [0x04, 0xB2, 0x47, 0x46]; + const VERSION_MAGIC_YPUB_MULTISIG: [u8; 4] = [0x02, 0x95, 0xb4, 0x3f]; + const VERSION_MAGIC_ZPUB_MULTISIG: [u8; 4] = [0x02, 0xaa, 0x7e, 0xd3]; + + const VERSION_MAGIC_TPUB: [u8; 4] = [0x04, 0x35, 0x87, 0xCF]; + const VERSION_MAGIC_UPUB: [u8; 4] = [0x04, 0x4A, 0x52, 0x62]; + const VERSION_MAGIC_VPUB: [u8; 4] = [0x04, 0x5F, 0x1C, 0xF6]; + const VERSION_MAGIC_UPUB_MULTISIG: [u8; 4] = [0x02, 0x42, 0x89, 0xef]; + const VERSION_MAGIC_VPUB_MULTISIG: [u8; 4] = [0x02, 0x57, 0x54, 0x83]; + + let mut data = base58::from_check(s)?; + + let mut prefix = [0u8; 4]; + prefix.copy_from_slice(&data[0..4]); + let slice = match prefix { + VERSION_MAGIC_XPUB + | VERSION_MAGIC_YPUB + | VERSION_MAGIC_ZPUB + | VERSION_MAGIC_YPUB_MULTISIG + | VERSION_MAGIC_ZPUB_MULTISIG => VERSION_MAGIC_XPUB, + + VERSION_MAGIC_TPUB + | VERSION_MAGIC_UPUB + | VERSION_MAGIC_VPUB + | VERSION_MAGIC_UPUB_MULTISIG + | VERSION_MAGIC_VPUB_MULTISIG => VERSION_MAGIC_TPUB, + + _ => Err(Error::UnknownSlip32Prefix)?, + }; + data[0..4].copy_from_slice(&slice); + + let xpub = ExtendedPubKey::decode(&data)?; + + Ok(xpub) + } +} + +impl FromSlip32 for ExtendedPrivKey { + fn from_slip32_str(s: &str) -> Result { + const VERSION_MAGIC_XPRV: [u8; 4] = [0x04, 0x88, 0xAD, 0xE4]; + const VERSION_MAGIC_YPRV: [u8; 4] = [0x04, 0x9D, 0x78, 0x78]; + const VERSION_MAGIC_ZPRV: [u8; 4] = [0x04, 0xB2, 0x43, 0x0C]; + const VERSION_MAGIC_YPRV_MULTISIG: [u8; 4] = [0x02, 0x95, 0xb0, 0x05]; + const VERSION_MAGIC_ZPRV_MULTISIG: [u8; 4] = [0x02, 0xaa, 0x7a, 0x99]; + + const VERSION_MAGIC_TPRV: [u8; 4] = [0x04, 0x35, 0x83, 0x94]; + const VERSION_MAGIC_UPRV: [u8; 4] = [0x04, 0x4A, 0x4E, 0x28]; + const VERSION_MAGIC_VPRV: [u8; 4] = [0x04, 0x5F, 0x18, 0xBC]; + const VERSION_MAGIC_UPRV_MULTISIG: [u8; 4] = [0x02, 0x42, 0x85, 0xb5]; + const VERSION_MAGIC_VPRV_MULTISIG: [u8; 4] = [0x02, 0x57, 0x50, 0x48]; + + let mut data = base58::from_check(s)?; + + let mut prefix = [0u8; 4]; + prefix.copy_from_slice(&data[0..4]); + let slice = match prefix { + VERSION_MAGIC_XPRV + | VERSION_MAGIC_YPRV + | VERSION_MAGIC_ZPRV + | VERSION_MAGIC_YPRV_MULTISIG + | VERSION_MAGIC_ZPRV_MULTISIG => VERSION_MAGIC_XPRV, + + VERSION_MAGIC_TPRV + | VERSION_MAGIC_UPRV + | VERSION_MAGIC_VPRV + | VERSION_MAGIC_UPRV_MULTISIG + | VERSION_MAGIC_VPRV_MULTISIG => VERSION_MAGIC_TPRV, + + _ => Err(Error::UnknownSlip32Prefix)?, + }; + data[0..4].copy_from_slice(&slice); + + let xprv = ExtendedPrivKey::decode(&data)?; + + Ok(xprv) + } +} + +pub trait HardenedNormalSplit { + fn hardened_normal_split(&self) -> (DerivationPath, Vec); +} + +impl HardenedNormalSplit for DerivationPath { + fn hardened_normal_split(&self) -> (DerivationPath, Vec) { + let mut terminal_path = vec![]; + let branch_path = self + .into_iter() + .rev() + .by_ref() + .skip_while(|child| { + if let ChildNumber::Normal { index } = child { + terminal_path.push(index); + true + } else { + false + } + }) + .cloned() + .collect::(); + let branch_path = branch_path.into_iter().rev().cloned().collect(); + let terminal_path = terminal_path.into_iter().rev().cloned().collect(); + (branch_path, terminal_path) + } +} + +#[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Debug, + StrictEncode, + StrictDecode, +)] +// master_xpub/branch_path=branch_xpub/terminal_path/index_ranges +pub struct DerivationComponents { + pub master_xpub: ExtendedPubKey, + pub branch_path: DerivationPath, + pub branch_xpub: ExtendedPubKey, + pub terminal_path: Vec, + pub index_ranges: Option>, +} + +impl DerivationComponents { + pub fn count(&self) -> u32 { + match self.index_ranges { + None => u32::MAX, + Some(ref ranges) => { + ranges.iter().fold(0u32, |sum, range| sum + range.count()) + } + } + } + + pub fn derivation_path(&self) -> DerivationPath { + self.branch_path.extend(self.terminal_path()) + } + + pub fn terminal_path(&self) -> DerivationPath { + DerivationPath::from_iter( + self.terminal_path + .iter() + .map(|i| ChildNumber::Normal { index: *i }), + ) + } + + pub fn index_ranges_string(&self) -> String { + self.index_ranges + .as_ref() + .map(|ranges| { + ranges + .iter() + .map(DerivationRange::to_string) + .collect::>() + .join(",") + }) + .unwrap_or_default() + } + + pub fn child(&self, child: u32) -> ExtendedPubKey { + let derivation = self + .terminal_path() + .into_child(ChildNumber::Normal { index: child }); + self.branch_xpub + .derive_pub(&lnpbp::SECP256K1, &derivation) + .expect("Non-hardened derivation does not fail") + } + + pub fn public_key(&self, index: u32) -> bitcoin::PublicKey { + self.child(index).public_key + } +} + +impl Display for DerivationComponents { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!( + f, + "[{}]{}/", + self.master_xpub.fingerprint(), + self.derivation_path() + .to_string() + .strip_prefix("m") + .unwrap_or(&self.derivation_path().to_string()) + )?; + if let Some(_) = self.index_ranges { + f.write_str(&self.index_ranges_string()) + } else { + f.write_str("*") + } + } +} + +#[derive(Wrapper, Clone, PartialEq, Eq, Hash, Debug, From)] +pub struct DerivationRange(RangeInclusive); + +impl PartialOrd for DerivationRange { + fn partial_cmp(&self, other: &Self) -> Option { + match self.start().partial_cmp(&other.start()) { + Some(Ordering::Equal) => self.end().partial_cmp(&other.end()), + other => other, + } + } +} + +impl Ord for DerivationRange { + fn cmp(&self, other: &Self) -> Ordering { + match self.start().cmp(&other.start()) { + Ordering::Equal => self.end().cmp(&other.end()), + other => other, + } + } +} + +impl DerivationRange { + pub fn count(&self) -> u32 { + let inner = self.as_inner(); + inner.end() - inner.start() + 1 + } + + pub fn start(&self) -> u32 { + *self.as_inner().start() + } + + pub fn end(&self) -> u32 { + *self.as_inner().end() + } +} + +impl Display for DerivationRange { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let inner = self.as_inner(); + if inner.start() == inner.end() { + write!(f, "{}", inner.start()) + } else { + write!(f, "{}-{}", inner.start(), inner.end()) + } + } +} + +impl StrictEncode for DerivationRange { + type Error = strict_encoding::Error; + + fn strict_encode( + &self, + mut e: E, + ) -> Result { + Ok(strict_encode_list!(e; self.start(), self.end())) + } +} + +impl StrictDecode for DerivationRange { + type Error = strict_encoding::Error; + + fn strict_decode(mut d: D) -> Result { + Ok(Self::from_inner(RangeInclusive::new( + u32::strict_decode(&mut d)?, + u32::strict_decode(&mut d)?, + ))) + } +} diff --git a/src/bp/scripts/descriptor.rs b/src/bp/scripts/tweak.rs similarity index 100% rename from src/bp/scripts/descriptor.rs rename to src/bp/scripts/tweak.rs