Repository: bitcoindevkit/bdk-ffi (language: python)

Issue:
Create a type safe descriptor builder
As discussed in the team chat with kirillzhukov, the `bdk` lib uses rust macros to provide a type safe way to construct valid descriptors. Since the macro approach won't work for `bdk-ffi` language bindings we should try to build a simple library, possibly as a new repo, that uses simple structs and enums to accomplish something similar to the `bdk::descriptor::dsl` module.


Discussion / hints:
Related to #98.
Yes it's related to the static templates, they both should result in some `Descriptor` structure that can be used when constructing a `Wallet`.
Had a related discussion on Discord `bdk-users` today and think a simple descriptor template api could look like below. These represent simple single key descriptors according to [BIP44],[BIP49],[BIP84], and [BIP86]. 

There's no BIP yet for multisig templates, but we could create a simple one that takes a given key threshold and array of keys, would also need to a `DescriptorKey` enum.

```udl
[Enum]
interface DescriptorKey {
  Public(DescriptorPublicKey key);
  Secret(DescriptorSecretKey key);
};

interface Descriptor {
  [Throws=Error]
  constructor(string descriptor);

  [Name=new_bip44]
  constructor(DescriptorSecretKey secret_key, KeychainKind keychain , u32 account);

  [Name=new_bip44_public]
  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain , u32 account);

  [Name=new_bip49]
  constructor(DescriptorSecretKey secret_key, KeychainKind keychain , u32 account);

  [Name=new_bip49_public]
  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain , u32 account);
  
  [Name=new_bip84]
  constructor(DescriptorSecretKey secret_key, KeychainKind keychain , u32 account);
  
  [Name=new_bip84_public]
  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain , u32 account);
  
  [Name=new_bip86]
  constructor(DescriptorSecretKey secret_key, KeychainKind keychain , u32 account);
  
  [Name=new_bip86_public]
  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain , u32 account);

  [Name=new_multisig]
  constructor(sequence<DescriptorKey> keys, u32 threshold);

  string to_string();
};
```

[BIP44]: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
[BIP49]: https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki
[BIP84]: https://github.com/bitcoin/bips/blob/master/bip-0084.mediawiki
[BIP86]: https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
Related link with the types of descriptors other wallets use: https://github.com/nvk/walletsrecovery.org
This looks great 👍 
Apart from the standard templates, we can have a custom descriptor option as well, so that a path and script type can be specified and the descriptor will be created for it.
- This will take care of existing wallets created with non standard path.
- It will also allow users to create wallets based on instructions from existing wallets which may not specify or inform the users of a descriptor nor a standard template but provide a path to use.
- API to create a single XPub descriptor or a single key descriptor has been requested many times by users and having a custom descriptor option will take care of the request.
> This looks great 👍 Apart from the standard templates, we can have a custom descriptor option as well, so that a path and script type can be specified and the descriptor will be created for it.

That's a great idea! But let's do it in a separate PR once we have the standard templates working ok? I'll think I'll move the multisig into it's own PR too since it might require more API discussion also. 
> That's a great idea! But let's do it in a separate PR once we have the standard templates working ok? I'll think I'll move the multisig into it's own PR too since it might require more API discussion also.

Sounds good 👍
@notmandatory mind if I take this on?
Now that I'm looking at the templates from BDK I see they only build for account 0 (if you want/need a different account you must build the descriptor manually).
> Now that I'm looking at the templates from BDK I see they only build for account 0 (if you want/need a different account you must build the descriptor manually).

Good point, I agree for the initial api let's stick with just what the `bdk` templates give us, so account 0 only. Before we support different accounts I want to get that supported in the`bdk` templates.
Only here to say that I support this effort of adding 'ready to go' descriptor types.
It was one of the hurdles for me in getting started, I had to go ask in discord to figure it out.

In my own Swift code I only got to describe two so far:

```swift
public enum DescriptorType {
    case singleKey_wpkh84 // Native Segwit
    case singleKey_tr86 // Taproot
}
```

You have more detailed knowledge about a wider range of types so I don't have much to add, other than please name them in an approachable and descriptive way. (Or at least add comments in the code).
This is a good point about the naming. In BDK the templates are named by the bipxx + public added if they are for creating public key watch only descriptors, so that's what I am proposing for bdk-ffi. But we'll certainly need to add docs for anyone who's unfamiliar with the bips.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4a8d953b..65ee924c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,6 +73,12 @@ dependencies = [
  "toml",
 ]
 
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
 [[package]]
 name = "async-trait"
 version = "0.1.59"
@@ -146,6 +152,7 @@ dependencies = [
 name = "bdk-ffi"
 version = "0.25.0"
 dependencies = [
+ "assert_matches",
  "bdk",
  "uniffi",
  "uniffi_build",
diff --git a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
index 1fb21320..218318fd 100644
--- a/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
+++ b/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit/AndroidLibTest.kt
@@ -36,8 +36,7 @@ class AndroidLibTest {
         }
     }
 
-    private val descriptor =
-        "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
+    private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
 
     private val databaseConfig = DatabaseConfig.Memory
 
diff --git a/bdk-ffi/Cargo.toml b/bdk-ffi/Cargo.toml
index 501de9df..99f46ec5 100644
--- a/bdk-ffi/Cargo.toml
+++ b/bdk-ffi/Cargo.toml
@@ -11,9 +11,11 @@ name = "bdkffi"
 
 [dependencies]
 bdk = { version = "0.25", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled", "rpc"] }
-
 uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
 uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
 
 [build-dependencies]
 uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] }
+
+[dev-dependencies]
+assert_matches = "1.5.0"
diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl
index f79aa168..5b14025c 100644
--- a/bdk-ffi/src/bdk.udl
+++ b/bdk-ffi/src/bdk.udl
@@ -203,7 +203,7 @@ dictionary ScriptAmount {
 
 interface Wallet {
   [Throws=BdkError]
-  constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config);
+  constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network, DatabaseConfig database_config);
 
   [Throws=BdkError]
   AddressInfo get_address(AddressIndex address_index);
@@ -357,6 +357,33 @@ interface DescriptorPublicKey {
   string as_string();
 };
 
+interface Descriptor {
+  [Throws=BdkError]
+  constructor(string descriptor, Network network);
+
+  [Name=new_bip44]
+  constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
+
+  [Name=new_bip44_public]
+  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
+
+  [Name=new_bip49]
+  constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
+
+  [Name=new_bip49_public]
+  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
+
+  [Name=new_bip84]
+  constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
+
+  [Name=new_bip84_public]
+  constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
+
+  string as_string();
+
+  string as_string_private();
+};
+
 interface Address {
   [Throws=BdkError]
   constructor(string address);
diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs
index 8beae717..ddaeb514 100644
--- a/bdk-ffi/src/lib.rs
+++ b/bdk-ffi/src/lib.rs
@@ -1,7 +1,7 @@
 use bdk::bitcoin::blockdata::script::Script as BdkScript;
 use bdk::bitcoin::hashes::hex::ToHex;
 use bdk::bitcoin::secp256k1::Secp256k1;
-use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
+use bdk::bitcoin::util::bip32::{DerivationPath as BdkDerivationPath, Fingerprint};
 use bdk::bitcoin::util::psbt::serialize::Serialize;
 use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
 use bdk::bitcoin::Sequence;
@@ -17,14 +17,18 @@ use bdk::blockchain::{
 use bdk::blockchain::{Blockchain as BdkBlockchain, Progress as BdkProgress};
 use bdk::database::any::{AnyDatabase, SledDbConfiguration, SqliteDbConfiguration};
 use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
-use bdk::descriptor::DescriptorXKey;
+use bdk::descriptor::{DescriptorXKey, ExtendedDescriptor, IntoWalletDescriptor};
 use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic, WordCount};
 use bdk::keys::{
     DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
     DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
+    KeyMap,
 };
 use bdk::miniscript::BareCtx;
 use bdk::psbt::PsbtUtils;
+use bdk::template::{
+    Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, DescriptorTemplate,
+};
 use bdk::wallet::tx_builder::ChangeSpendPolicy;
 use bdk::wallet::AddressIndex as BdkAddressIndex;
 use bdk::wallet::AddressInfo as BdkAddressInfo;
@@ -309,10 +313,6 @@ impl Blockchain {
     }
 }
 
-struct Wallet {
-    wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
-}
-
 /// A reference to a transaction output.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub struct OutPoint {
@@ -489,6 +489,11 @@ impl PartiallySignedTransaction {
     }
 }
 
+#[derive(Debug)]
+struct Wallet {
+    wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
+}
+
 /// A Bitcoin wallet.
 /// The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are:
 ///     1. Output descriptors from which it can derive addresses.
@@ -496,8 +501,8 @@ impl PartiallySignedTransaction {
 ///     3. Signers that can contribute signatures to addresses instantiated from the descriptors.
 impl Wallet {
     fn new(
-        descriptor: String,
-        change_descriptor: Option<String>,
+        descriptor: Arc<Descriptor>,
+        change_descriptor: Option<Arc<Descriptor>>,
         network: Network,
         database_config: DatabaseConfig,
     ) -> Result<Self, BdkError> {
@@ -507,6 +512,9 @@ impl Wallet {
             DatabaseConfig::Sqlite { config } => AnyDatabaseConfig::Sqlite(config),
         };
         let database = AnyDatabase::from_config(&any_database_config)?;
+        let descriptor: String = descriptor.as_string_private();
+        let change_descriptor: Option<String> = change_descriptor.map(|d| d.as_string_private());
+
         let wallet_mutex = Mutex::new(BdkWallet::new(
             &descriptor,
             change_descriptor.as_ref(),
@@ -1179,6 +1187,186 @@ impl DescriptorPublicKey {
     }
 }
 
+#[derive(Debug)]
+struct Descriptor {
+    pub extended_descriptor: ExtendedDescriptor,
+    pub key_map: KeyMap,
+}
+
+impl Descriptor {
+    fn new(descriptor: String, network: Network) -> Result<Self, BdkError> {
+        let secp = Secp256k1::new();
+        let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
+        Ok(Self {
+            extended_descriptor,
+            key_map,
+        })
+    }
+
+    fn new_bip44(
+        secret_key: Arc<DescriptorSecretKey>,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip44(derivable_key, keychain_kind).build(network).unwrap();
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorSecretKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn new_bip44_public(
+        public_key: Arc<DescriptorPublicKey>,
+        fingerprint: String,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
+        let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip44Public(derivable_key, fingerprint, keychain_kind)
+                        .build(network)
+                        .unwrap();
+
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorPublicKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn new_bip49(
+        secret_key: Arc<DescriptorSecretKey>,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip49(derivable_key, keychain_kind).build(network).unwrap();
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorSecretKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn new_bip49_public(
+        public_key: Arc<DescriptorPublicKey>,
+        fingerprint: String,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
+        let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip49Public(derivable_key, fingerprint, keychain_kind)
+                        .build(network)
+                        .unwrap();
+
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorPublicKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn new_bip84(
+        secret_key: Arc<DescriptorSecretKey>,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip84(derivable_key, keychain_kind).build(network).unwrap();
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorSecretKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn new_bip84_public(
+        public_key: Arc<DescriptorPublicKey>,
+        fingerprint: String,
+        keychain_kind: KeychainKind,
+        network: Network,
+    ) -> Self {
+        let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
+        let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
+
+        match derivable_key.deref() {
+            BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
+                let derivable_key = descriptor_x_key.xkey;
+                let (extended_descriptor, key_map, _) =
+                    Bip84Public(derivable_key, fingerprint, keychain_kind)
+                        .build(network)
+                        .unwrap();
+
+                Self {
+                    extended_descriptor,
+                    key_map,
+                }
+            }
+            BdkDescriptorPublicKey::Single(_) => {
+                unreachable!()
+            }
+        }
+    }
+
+    fn as_string_private(&self) -> String {
+        let descriptor = &self.extended_descriptor;
+        let key_map = &self.key_map;
+        descriptor.to_string_with_secret(key_map)
+    }
+
+    fn as_string(&self) -> String {
+        self.extended_descriptor.to_string()
+    }
+}
+
 uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
 
 // The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
@@ -1187,8 +1375,10 @@ uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
 #[cfg(test)]
 mod test {
     use crate::*;
+    use assert_matches::assert_matches;
     use bdk::bitcoin::Address;
-    use bdk::bitcoin::Network::Testnet;
+    use bdk::descriptor::DescriptorError::Key;
+    use bdk::keys::KeyError::InvalidNetwork;
     use bdk::wallet::get_funded_wallet;
     use std::str::FromStr;
     use std::sync::Mutex;
@@ -1239,7 +1429,7 @@ mod test {
                 .cloned()
                 .unwrap()
                 .script_pubkey,
-            Testnet,
+            Network::Testnet,
         )
         .unwrap();
         assert_eq!(
@@ -1262,7 +1452,7 @@ mod test {
 
     fn get_descriptor_secret_key() -> DescriptorSecretKey {
         let mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
-        DescriptorSecretKey::new(Testnet, Arc::new(mnemonic), None)
+        DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None)
     }
 
     fn derive_dsk(
@@ -1419,4 +1609,142 @@ mod test {
         assert!(tx_builder_result.psbt.fee_amount().is_some());
         assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
     }
+
+    #[test]
+    fn test_descriptor_templates() {
+        let master: Arc<DescriptorSecretKey> = Arc::new(get_descriptor_secret_key());
+        println!("Master: {:?}", master.as_string());
+        // tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
+
+        let handmade_public_44 = master
+            .derive(Arc::new(
+                DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(),
+            ))
+            .unwrap()
+            .as_public();
+        println!("Public 44: {}", handmade_public_44.as_string());
+        // Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
+
+        let handmade_public_49 = master
+            .derive(Arc::new(
+                DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(),
+            ))
+            .unwrap()
+            .as_public();
+        println!("Public 49: {}", handmade_public_49.as_string());
+        // Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
+
+        let handmade_public_84 = master
+            .derive(Arc::new(
+                DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(),
+            ))
+            .unwrap()
+            .as_public();
+        println!("Public 84: {}", handmade_public_84.as_string());
+        // Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
+
+        let template_private_44 =
+            Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet);
+        let template_private_49 =
+            Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet);
+        let template_private_84 =
+            Descriptor::new_bip84(master, KeychainKind::External, Network::Testnet);
+
+        // the extended public keys are the same when creating them manually as they are with the templates
+        println!("Template 49: {}", template_private_49.as_string());
+        println!("Template 44: {}", template_private_44.as_string());
+        println!("Template 84: {}", template_private_84.as_string());
+
+        // for the public versions of the templates these are incorrect, bug report and fix in bitcoindevkit/bdk#817 and bitcoindevkit/bdk#818
+        let template_public_44 = Descriptor::new_bip44_public(
+            handmade_public_44,
+            "d1d04177".to_string(),
+            KeychainKind::External,
+            Network::Testnet,
+        );
+        let template_public_49 = Descriptor::new_bip49_public(
+            handmade_public_49,
+            "d1d04177".to_string(),
+            KeychainKind::External,
+            Network::Testnet,
+        );
+        let template_public_84 = Descriptor::new_bip84_public(
+            handmade_public_84,
+            "d1d04177".to_string(),
+            KeychainKind::External,
+            Network::Testnet,
+        );
+
+        println!("Template public 49: {}", template_public_49.as_string());
+        println!("Template public 44: {}", template_public_44.as_string());
+        println!("Template public 84: {}", template_public_84.as_string());
+
+        // when using a public key, both as_string and as_string_private return the same string
+        assert_eq!(
+            template_public_44.as_string_private(),
+            template_public_44.as_string()
+        );
+        assert_eq!(
+            template_public_49.as_string_private(),
+            template_public_49.as_string()
+        );
+        assert_eq!(
+            template_public_84.as_string_private(),
+            template_public_84.as_string()
+        );
+
+        // when using as_string on a private key, we get the same result as when using it on a public key
+        // these currently fails as described in #817
+        // assert_eq!(
+        //     template_private_44.as_string(),
+        //     template_public_44.as_string()
+        // );
+        // assert_eq!(
+        //     template_private_49.as_string(),
+        //     template_public_49.as_string()
+        // );
+        // assert_eq!(
+        //     template_private_84.as_string(),
+        //     template_public_84.as_string()
+        // );
+    }
+
+    #[test]
+    fn test_descriptor_from_string() {
+        let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
+        let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
+
+        // Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error
+        assert!(descriptor1.is_ok());
+        assert_matches!(
+            descriptor2.unwrap_err(),
+            bdk::Error::Descriptor(Key(InvalidNetwork))
+        )
+    }
+
+    #[test]
+    fn test_wallet_from_descriptor() {
+        let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap();
+
+        let wallet1 = Wallet::new(
+            Arc::new(Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap()),
+            None,
+            Network::Testnet,
+            DatabaseConfig::Memory
+        );
+
+        let wallet2 = Wallet::new(
+            Arc::new(descriptor1),
+            None,
+            Network::Bitcoin,
+            DatabaseConfig::Memory,
+        );
+
+        // Creating a wallet using a Descriptor with an extended key that doesn't match the network provided in the wallet constructor will throw and InvalidNetwork Error
+        assert!(wallet1.is_ok());
+        assert_matches!(
+            wallet2.unwrap_err(),
+            bdk::Error::Descriptor(Key(InvalidNetwork))
+        )
+    }
 }
diff --git a/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift b/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
index c25e25d9..c76c87b3 100644
--- a/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
+++ b/bdk-swift/Tests/BitcoinDevKitTests/BitcoinDevKitTests.swift
@@ -3,7 +3,10 @@ import XCTest
 
 final class BitcoinDevKitTests: XCTestCase {
     func testMemoryWalletNewAddress() throws {
-        let desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
+        let desc = try Descriptor(
+            descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
+            network: Network.regtest
+        )
         let databaseConfig = DatabaseConfig.memory
         let wallet = try Wallet.init(descriptor: desc, changeDescriptor: nil, network: Network.regtest, databaseConfig: databaseConfig)
         let addressInfo = try wallet.getAddress(addressIndex: AddressIndex.new)


Test patch:
diff --git a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
index a66f8cd7..81eec3a3 100644
--- a/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
+++ b/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit/JvmLibTest.kt
@@ -28,8 +28,7 @@ class JvmLibTest {
         }
     }
 
-    private val descriptor =
-        "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
+    private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
 
     private val databaseConfig = DatabaseConfig.Memory