From 7fa6ac12898ca002e3ad92e81b2b967e987fe7bf Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 12 Nov 2024 15:00:01 -0600 Subject: [PATCH 1/4] feat: add is_multipath to descriptorpublickey --- bdk-ffi/src/bdk.udl | 2 ++ bdk-ffi/src/keys.rs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 4f0b8603..0b468373 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -736,6 +736,8 @@ interface DescriptorPublicKey { DescriptorPublicKey extend([ByRef] DerivationPath path); string as_string(); + + boolean is_multipath(); }; [Traits=(Display)] diff --git a/bdk-ffi/src/keys.rs b/bdk-ffi/src/keys.rs index 6804e42c..d0feb084 100644 --- a/bdk-ffi/src/keys.rs +++ b/bdk-ffi/src/keys.rs @@ -223,6 +223,10 @@ impl DescriptorPublicKey { pub(crate) fn as_string(&self) -> String { self.0.to_string() } + + pub(crate) fn is_multipath(&self) -> bool { + self.0.is_multipath() + } } #[cfg(test)] From 92289e35bb8109cee328ac82b2e3bed7ff4bb8c9 Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 12 Nov 2024 15:04:50 -0600 Subject: [PATCH 2/4] feat: add master-fingerprint to descriptorpublickey --- bdk-ffi/src/bdk.udl | 4 ++++ bdk-ffi/src/keys.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 0b468373..167e8313 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -737,7 +737,11 @@ interface DescriptorPublicKey { string as_string(); +/// Whether or not this key has multiple derivation paths. boolean is_multipath(); + +/// The fingerprint of the master key associated with this key, `0x00000000` if none. + string master_fingerprint(); }; [Traits=(Display)] diff --git a/bdk-ffi/src/keys.rs b/bdk-ffi/src/keys.rs index d0feb084..878ae4f4 100644 --- a/bdk-ffi/src/keys.rs +++ b/bdk-ffi/src/keys.rs @@ -227,6 +227,10 @@ impl DescriptorPublicKey { pub(crate) fn is_multipath(&self) -> bool { self.0.is_multipath() } + + pub(crate) fn master_fingerprint(&self) -> String { + self.0.master_fingerprint().to_string() + } } #[cfg(test)] From b15b30f923ad19a9302f2a7c09949e02e60c436a Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 13 Nov 2024 08:42:47 -0600 Subject: [PATCH 3/4] feat: add is_multipath to descriptor --- bdk-ffi/src/bdk.udl | 3 +++ bdk-ffi/src/descriptor.rs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 167e8313..42b30425 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -774,6 +774,9 @@ interface Descriptor { constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network); string to_string_with_secret(); + +/// Whether or not this key has multiple derivation paths. + boolean is_multipath(); }; // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index 413df82a..5ea809af 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -266,6 +266,10 @@ impl Descriptor { let key_map = &self.key_map; descriptor.to_string_with_secret(key_map) } + + pub(crate) fn is_multipath(&self) -> bool { + self.extended_descriptor.is_multipath() + } } impl Display for Descriptor { From 53f93ada6fe2c2c60c7595c7b16e1620e3600e53 Mon Sep 17 00:00:00 2001 From: Matthew Date: Thu, 14 Nov 2024 12:16:54 -0600 Subject: [PATCH 4/4] feat: add into_single_descriptors to descriptor --- bdk-ffi/src/bdk.udl | 54 ++++++++++- bdk-ffi/src/descriptor.rs | 20 ++++ bdk-ffi/src/error.rs | 189 ++++++++++++++++++++++++++++++++++++++ bdk-ffi/src/lib.rs | 1 + 4 files changed, 261 insertions(+), 3 deletions(-) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 42b30425..2418d8a5 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -169,6 +169,47 @@ interface LoadWithPersistError { CouldNotLoad(); }; +[Error] +interface MiniscriptError { + AbsoluteLockTime(); + AddrError(string error_message); + AddrP2shError(string error_message); + AnalysisError(string error_message); + AtOutsideOr(); + BadDescriptor(string error_message); + BareDescriptorAddr(); + CmsTooManyKeys(u32 keys); + ContextError(string error_message); + CouldNotSatisfy(); + ExpectedChar(string char); + ImpossibleSatisfaction(); + InvalidOpcode(); + InvalidPush(); + LiftError(string error_message); + MaxRecursiveDepthExceeded(); + MissingSig(); + MultiATooManyKeys(u64 keys); + MultiColon(); + MultipathDescLenMismatch(); + NonMinimalVerify(string error_message); + NonStandardBareScript(); + NonTopLevel(string error_message); + ParseThreshold(); + PolicyError(string error_message); + PubKeyCtxError(); + RelativeLockTime(); + Script(string error_message); + Secp(string error_message); + Threshold(); + TrNoScriptCode(); + Trailing(string error_message); + TypeCheck(string error_message); + Unexpected(string error_message); + UnexpectedStart(); + UnknownWrapper(string char); + Unprintable(u8 byte); +}; + [Error] interface PersistenceError { Write(string error_message); @@ -737,10 +778,10 @@ interface DescriptorPublicKey { string as_string(); -/// Whether or not this key has multiple derivation paths. + /// Whether or not this key has multiple derivation paths. boolean is_multipath(); -/// The fingerprint of the master key associated with this key, `0x00000000` if none. + /// The fingerprint of the master key associated with this key, `0x00000000` if none. string master_fingerprint(); }; @@ -775,8 +816,15 @@ interface Descriptor { string to_string_with_secret(); -/// Whether or not this key has multiple derivation paths. + /// Whether or not this key has multiple derivation paths. boolean is_multipath(); + + /// Get as many descriptors as different paths in this descriptor. + /// + /// For multipath descriptors it will return as many descriptors as there is + /// "parallel" paths. For regular descriptors it will just return itself. + [Throws=MiniscriptError] + sequence to_single_descriptors(); }; // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/descriptor.rs b/bdk-ffi/src/descriptor.rs index 5ea809af..141880fd 100644 --- a/bdk-ffi/src/descriptor.rs +++ b/bdk-ffi/src/descriptor.rs @@ -14,8 +14,10 @@ use bdk_wallet::template::{ }; use bdk_wallet::KeychainKind; +use crate::error::MiniscriptError; use std::fmt::Display; use std::str::FromStr; +use std::sync::Arc; #[derive(Debug)] pub struct Descriptor { @@ -270,6 +272,24 @@ impl Descriptor { pub(crate) fn is_multipath(&self) -> bool { self.extended_descriptor.is_multipath() } + + pub(crate) fn to_single_descriptors(&self) -> Result>, MiniscriptError> { + self.extended_descriptor + .clone() + .into_single_descriptors() + .map_err(MiniscriptError::from) + .map(|descriptors| { + descriptors + .into_iter() + .map(|desc| { + Arc::new(Descriptor { + extended_descriptor: desc, + key_map: self.key_map.clone(), + }) + }) + .collect() + }) + } } impl Display for Descriptor { diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 668d7314..7aab6eaa 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -409,6 +409,120 @@ pub enum LoadWithPersistError { CouldNotLoad, } +#[derive(Debug, thiserror::Error)] +pub enum MiniscriptError { + #[error("absolute locktime error")] + AbsoluteLockTime, + + #[error("address error: {error_message}")] + AddrError { error_message: String }, + + #[error("p2sh address error: {error_message}")] + AddrP2shError { error_message: String }, + + #[error("analysis error: {error_message}")] + AnalysisError { error_message: String }, + + #[error("@ found outside of OR")] + AtOutsideOr, + + #[error("bad descriptor: {error_message}")] + BadDescriptor { error_message: String }, + + #[error("bare descriptor address")] + BareDescriptorAddr, + + #[error("too many keys in checkmultisig: {keys}")] + CmsTooManyKeys { keys: u32 }, + + #[error("context error: {error_message}")] + ContextError { error_message: String }, + + #[error("could not satisfy")] + CouldNotSatisfy, + + #[error("expected character: {char}")] + ExpectedChar { char: String }, + + #[error("impossible satisfaction")] + ImpossibleSatisfaction, + + #[error("invalid opcode")] + InvalidOpcode, + + #[error("invalid push")] + InvalidPush, + + #[error("lift error: {error_message}")] + LiftError { error_message: String }, + + #[error("maximum recursive depth exceeded")] + MaxRecursiveDepthExceeded, + + #[error("missing signature")] + MissingSig, + + #[error("too many keys in multi-a: {keys}")] + MultiATooManyKeys { keys: u64 }, + + #[error("multiple colons in fragment name")] + MultiColon, + + #[error("multipath descriptor length mismatch")] + MultipathDescLenMismatch, + + #[error("non-minimal verify: {error_message}")] + NonMinimalVerify { error_message: String }, + + #[error("non-standard bare script")] + NonStandardBareScript, + + #[error("non top-level: {error_message}")] + NonTopLevel { error_message: String }, + + #[error("parse threshold error")] + ParseThreshold, + + #[error("policy error: {error_message}")] + PolicyError { error_message: String }, + + #[error("pubkey context error")] + PubKeyCtxError, + + #[error("relative locktime error")] + RelativeLockTime, + + #[error("script error: {error_message}")] + Script { error_message: String }, + + #[error("secp256k1 error: {error_message}")] + Secp { error_message: String }, + + #[error("threshold error")] + Threshold, + + #[error("no script code for taproot")] + TrNoScriptCode, + + #[error("trailing data: {error_message}")] + Trailing { error_message: String }, + + #[error("type check error: {error_message}")] + TypeCheck { error_message: String }, + + #[error("unexpected: {error_message}")] + Unexpected { error_message: String }, + + #[error("unexpected start")] + UnexpectedStart, + + #[error("unknown wrapper: {char}")] + UnknownWrapper { char: String }, + + #[error("unprintable character: {byte}")] + Unprintable { byte: u8 }, +} + #[derive(Debug, thiserror::Error)] pub enum PersistenceError { #[error("writing to persistence error: {error_message}")] @@ -1063,6 +1177,81 @@ impl From> for LoadWithPersistEr } } +impl From for MiniscriptError { + fn from(error: bdk_wallet::miniscript::Error) -> Self { + use bdk_wallet::miniscript::Error as BdkMiniscriptError; + match error { + BdkMiniscriptError::AbsoluteLockTime(_) => MiniscriptError::AbsoluteLockTime, + BdkMiniscriptError::AddrError(e) => MiniscriptError::AddrError { + error_message: e.to_string(), + }, + BdkMiniscriptError::AddrP2shError(e) => MiniscriptError::AddrP2shError { + error_message: e.to_string(), + }, + BdkMiniscriptError::AnalysisError(e) => MiniscriptError::AnalysisError { + error_message: e.to_string(), + }, + BdkMiniscriptError::AtOutsideOr(_) => MiniscriptError::AtOutsideOr, + BdkMiniscriptError::BadDescriptor(s) => { + MiniscriptError::BadDescriptor { error_message: s } + } + BdkMiniscriptError::BareDescriptorAddr => MiniscriptError::BareDescriptorAddr, + BdkMiniscriptError::CmsTooManyKeys(n) => MiniscriptError::CmsTooManyKeys { keys: n }, + BdkMiniscriptError::ContextError(e) => MiniscriptError::ContextError { + error_message: e.to_string(), + }, + BdkMiniscriptError::CouldNotSatisfy => MiniscriptError::CouldNotSatisfy, + BdkMiniscriptError::ExpectedChar(c) => MiniscriptError::ExpectedChar { + char: c.to_string(), + }, + BdkMiniscriptError::ImpossibleSatisfaction => MiniscriptError::ImpossibleSatisfaction, + BdkMiniscriptError::InvalidOpcode(_) => MiniscriptError::InvalidOpcode, + BdkMiniscriptError::InvalidPush(_) => MiniscriptError::InvalidPush, + BdkMiniscriptError::LiftError(e) => MiniscriptError::LiftError { + error_message: e.to_string(), + }, + BdkMiniscriptError::MaxRecursiveDepthExceeded => { + MiniscriptError::MaxRecursiveDepthExceeded + } + BdkMiniscriptError::MissingSig(_) => MiniscriptError::MissingSig, + BdkMiniscriptError::MultiATooManyKeys(n) => { + MiniscriptError::MultiATooManyKeys { keys: n } + } + BdkMiniscriptError::MultiColon(_) => MiniscriptError::MultiColon, + BdkMiniscriptError::MultipathDescLenMismatch => { + MiniscriptError::MultipathDescLenMismatch + } + BdkMiniscriptError::NonMinimalVerify(s) => { + MiniscriptError::NonMinimalVerify { error_message: s } + } + BdkMiniscriptError::NonStandardBareScript => MiniscriptError::NonStandardBareScript, + BdkMiniscriptError::NonTopLevel(s) => MiniscriptError::NonTopLevel { error_message: s }, + BdkMiniscriptError::ParseThreshold(_) => MiniscriptError::ParseThreshold, + BdkMiniscriptError::PolicyError(e) => MiniscriptError::PolicyError { + error_message: e.to_string(), + }, + BdkMiniscriptError::PubKeyCtxError(_, _) => MiniscriptError::PubKeyCtxError, + BdkMiniscriptError::RelativeLockTime(_) => MiniscriptError::RelativeLockTime, + BdkMiniscriptError::Script(e) => MiniscriptError::Script { + error_message: e.to_string(), + }, + BdkMiniscriptError::Secp(e) => MiniscriptError::Secp { + error_message: e.to_string(), + }, + BdkMiniscriptError::Threshold(_) => MiniscriptError::Threshold, + BdkMiniscriptError::TrNoScriptCode => MiniscriptError::TrNoScriptCode, + BdkMiniscriptError::Trailing(s) => MiniscriptError::Trailing { error_message: s }, + BdkMiniscriptError::TypeCheck(s) => MiniscriptError::TypeCheck { error_message: s }, + BdkMiniscriptError::Unexpected(s) => MiniscriptError::Unexpected { error_message: s }, + BdkMiniscriptError::UnexpectedStart => MiniscriptError::UnexpectedStart, + BdkMiniscriptError::UnknownWrapper(c) => MiniscriptError::UnknownWrapper { + char: c.to_string(), + }, + BdkMiniscriptError::Unprintable(b) => MiniscriptError::Unprintable { byte: b }, + } + } +} + impl From for PersistenceError { fn from(error: std::io::Error) -> Self { PersistenceError::Write { diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 2a082439..4254ca54 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -30,6 +30,7 @@ use crate::error::EsploraError; use crate::error::ExtractTxError; use crate::error::FromScriptError; use crate::error::LoadWithPersistError; +use crate::error::MiniscriptError; use crate::error::PersistenceError; use crate::error::PsbtError; use crate::error::PsbtParseError;