diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 4f0b8603..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); @@ -736,6 +777,12 @@ interface DescriptorPublicKey { DescriptorPublicKey extend([ByRef] DerivationPath path); 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)] @@ -768,6 +815,16 @@ 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(); + + /// 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 413df82a..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 { @@ -266,6 +268,28 @@ 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() + } + + 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/keys.rs b/bdk-ffi/src/keys.rs index 6804e42c..878ae4f4 100644 --- a/bdk-ffi/src/keys.rs +++ b/bdk-ffi/src/keys.rs @@ -223,6 +223,14 @@ impl DescriptorPublicKey { pub(crate) fn as_string(&self) -> String { self.0.to_string() } + + 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)] 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;