]> Untitled Git - bdk/commitdiff
[descriptors] Transform a descriptor into its "public" version
authorAlekos Filini <alekos.filini@gmail.com>
Sun, 10 May 2020 15:42:02 +0000 (17:42 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Sun, 10 May 2020 15:46:54 +0000 (17:46 +0200)
examples/repl.rs
src/cli.rs
src/descriptor/extended_key.rs
src/descriptor/keys.rs [new file with mode: 0644]
src/descriptor/mod.rs
src/wallet/mod.rs

index 5a78bed3ff5f724f16f7c48aa0ecbc1913083319..5eaf3f4275931c86b24f4a7b71efb36743bb9454 100644 (file)
@@ -101,7 +101,12 @@ async fn main() {
                         continue;
                     }
 
-                    cli::handle_matches(&Arc::clone(&wallet), matches.unwrap()).await;
+                    if let Some(s) = cli::handle_matches(&Arc::clone(&wallet), matches.unwrap())
+                        .await
+                        .unwrap()
+                    {
+                        println!("{}", s);
+                    }
                 }
                 Err(ReadlineError::Interrupted) => continue,
                 Err(ReadlineError::Eof) => break,
@@ -114,6 +119,8 @@ async fn main() {
 
     // rl.save_history("history.txt").unwrap();
     } else {
-        cli::handle_matches(&wallet, matches).await;
+        if let Some(s) = cli::handle_matches(&wallet, matches).await.unwrap() {
+            println!("{}", s);
+        }
     }
 }
index 5b24544ca70cfcd16416c64e2c1b9273dbee8d4a..9dc37a3206272dcbd511091a900ab9557dde4b4c 100644 (file)
@@ -119,6 +119,10 @@ pub fn make_cli_subcommands<'a, 'b>() -> App<'a, 'b> {
             SubCommand::with_name("policies")
                 .about("Returns the available spending policies for the descriptor")
             )
+        .subcommand(
+            SubCommand::with_name("public_descriptor")
+                .about("Returns the public version of the wallet's descriptor(s)")
+            )
         .subcommand(
             SubCommand::with_name("sign")
                 .about("Signs and tries to finalize a PSBT")
@@ -271,6 +275,20 @@ where
             serde_json::to_string(&wallet.policies(ScriptType::External)?).unwrap(),
             serde_json::to_string(&wallet.policies(ScriptType::Internal)?).unwrap(),
         )))
+    } else if let Some(_sub_matches) = matches.subcommand_matches("public_descriptor") {
+        let external = match wallet.public_descriptor(ScriptType::External)? {
+            Some(desc) => format!("{}", desc),
+            None => "<NONE>".into(),
+        };
+        let internal = match wallet.public_descriptor(ScriptType::Internal)? {
+            Some(desc) => format!("{}", desc),
+            None => "<NONE>".into(),
+        };
+
+        Ok(Some(format!(
+            "External: {}\nInternal:{}",
+            external, internal
+        )))
     } else if let Some(sub_matches) = matches.subcommand_matches("sign") {
         let psbt = base64::decode(sub_matches.value_of("psbt").unwrap()).unwrap();
         let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
index 4645eea0de72d450dbb11e3f62837582ce385c80..70cc208e9081cb641bbac16734c589882e330f9b 100644 (file)
@@ -199,6 +199,15 @@ impl FromStr for DescriptorExtendedKey {
             }
         };
 
+        if secret.is_none()
+            && path.into_iter().any(|child| match child {
+                ChildNumber::Hardened { .. } => true,
+                _ => false,
+            })
+        {
+            return Err(super::Error::HardenedDerivationOnXpub);
+        }
+
         Ok(DescriptorExtendedKey {
             master_fingerprint,
             master_derivation,
diff --git a/src/descriptor/keys.rs b/src/descriptor/keys.rs
new file mode 100644 (file)
index 0000000..7ff028d
--- /dev/null
@@ -0,0 +1,181 @@
+use bitcoin::secp256k1::{All, Secp256k1};
+use bitcoin::{PrivateKey, PublicKey};
+
+use bitcoin::util::bip32::{
+    ChildNumber, DerivationPath, ExtendedPrivKey, ExtendedPubKey, Fingerprint,
+};
+
+use super::error::Error;
+use super::extended_key::DerivationIndex;
+use super::DescriptorExtendedKey;
+
+pub(super) trait Key: std::fmt::Debug + std::fmt::Display {
+    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
+    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
+    fn as_secret_key(&self) -> Option<PrivateKey>;
+    fn xprv(&self) -> Option<ExtendedPrivKey>;
+    fn full_path(&self, index: u32) -> Option<DerivationPath>;
+    fn is_fixed(&self) -> bool;
+
+    fn has_secret(&self) -> bool {
+        self.xprv().is_some() || self.as_secret_key().is_some()
+    }
+
+    fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
+        Ok(Box::new(self.as_public_key(secp, None)?))
+    }
+}
+
+impl Key for PublicKey {
+    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        None
+    }
+
+    fn as_public_key(
+        &self,
+        _secp: &Secp256k1<All>,
+        _index: Option<u32>,
+    ) -> Result<PublicKey, Error> {
+        Ok(PublicKey::clone(self))
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        None
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        None
+    }
+
+    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
+        None
+    }
+
+    fn is_fixed(&self) -> bool {
+        true
+    }
+}
+
+impl Key for PrivateKey {
+    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        None
+    }
+
+    fn as_public_key(
+        &self,
+        secp: &Secp256k1<All>,
+        _index: Option<u32>,
+    ) -> Result<PublicKey, Error> {
+        Ok(self.public_key(secp))
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        Some(PrivateKey::clone(self))
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        None
+    }
+
+    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
+        None
+    }
+
+    fn is_fixed(&self) -> bool {
+        true
+    }
+}
+
+impl Key for DescriptorExtendedKey {
+    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
+        if let Some(fing) = self.master_fingerprint {
+            Some(fing.clone())
+        } else {
+            Some(self.root_xpub(secp).fingerprint())
+        }
+    }
+
+    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
+        Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
+    }
+
+    fn public(&self, secp: &Secp256k1<All>) -> Result<Box<dyn Key>, Error> {
+        if self.final_index == DerivationIndex::Hardened {
+            return Err(Error::HardenedDerivationOnXpub);
+        }
+
+        if self.xprv().is_none() {
+            return Ok(Box::new(self.clone()));
+        }
+
+        // copy the part of the path that can be derived on the xpub
+        let path = self
+            .path
+            .into_iter()
+            .rev()
+            .take_while(|child| match child {
+                ChildNumber::Normal { .. } => true,
+                _ => false,
+            })
+            .cloned()
+            .collect::<Vec<_>>();
+        // take the prefix that has to be derived on the xprv
+        let master_derivation_add = self
+            .path
+            .into_iter()
+            .take(self.path.as_ref().len() - path.len())
+            .cloned()
+            .collect::<Vec<_>>();
+        let has_derived = !master_derivation_add.is_empty();
+
+        let derived_xprv = self
+            .secret
+            .as_ref()
+            .unwrap()
+            .derive_priv(secp, &master_derivation_add)?;
+        let pubkey = ExtendedPubKey::from_private(secp, &derived_xprv);
+
+        let master_derivation = self
+            .master_derivation
+            .as_ref()
+            .map_or(vec![], |path| path.as_ref().to_vec())
+            .into_iter()
+            .chain(master_derivation_add.into_iter())
+            .collect::<Vec<_>>();
+        let master_derivation = match &master_derivation[..] {
+            &[] => None,
+            child_vec => Some(child_vec.into()),
+        };
+
+        let master_fingerprint = match self.master_fingerprint {
+            Some(desc) => Some(desc.clone()),
+            None if has_derived => Some(self.fingerprint(secp).unwrap()),
+            _ => None,
+        };
+
+        Ok(Box::new(DescriptorExtendedKey {
+            master_fingerprint,
+            master_derivation,
+            pubkey,
+            secret: None,
+            path: path.into(),
+            final_index: self.final_index,
+        }))
+    }
+
+    fn as_secret_key(&self) -> Option<PrivateKey> {
+        None
+    }
+
+    fn xprv(&self) -> Option<ExtendedPrivKey> {
+        self.secret
+    }
+
+    fn full_path(&self, index: u32) -> Option<DerivationPath> {
+        Some(self.full_path(index))
+    }
+
+    fn is_fixed(&self) -> bool {
+        self.final_index == DerivationIndex::Fixed
+    }
+}
index 71d9ae1642d2848cfa5ec53e9e0729880088dea4..a2db112d6f41b45addadc9fd8e0d08e3ba93de63 100644 (file)
@@ -19,13 +19,16 @@ use crate::psbt::utils::PSBTUtils;
 pub mod checksum;
 pub mod error;
 pub mod extended_key;
+mod keys;
 pub mod policy;
 
 pub use self::checksum::get_checksum;
-pub use self::error::Error;
+use self::error::Error;
 pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
 pub use self::policy::Policy;
 
+use self::keys::Key;
+
 trait MiniscriptExtractPolicy {
     fn extract_policy(
         &self,
@@ -105,109 +108,6 @@ where
     }
 }
 
-trait Key: std::fmt::Debug {
-    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint>;
-    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error>;
-    fn as_secret_key(&self) -> Option<PrivateKey>;
-    fn xprv(&self) -> Option<ExtendedPrivKey>;
-    fn full_path(&self, index: u32) -> Option<DerivationPath>;
-    fn is_fixed(&self) -> bool;
-
-    fn has_secret(&self) -> bool {
-        self.xprv().is_some() || self.as_secret_key().is_some()
-    }
-}
-
-impl Key for PublicKey {
-    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        None
-    }
-
-    fn as_public_key(
-        &self,
-        _secp: &Secp256k1<All>,
-        _index: Option<u32>,
-    ) -> Result<PublicKey, Error> {
-        Ok(PublicKey::clone(self))
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        None
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        None
-    }
-
-    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
-        None
-    }
-
-    fn is_fixed(&self) -> bool {
-        true
-    }
-}
-
-impl Key for PrivateKey {
-    fn fingerprint(&self, _secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        None
-    }
-
-    fn as_public_key(
-        &self,
-        secp: &Secp256k1<All>,
-        _index: Option<u32>,
-    ) -> Result<PublicKey, Error> {
-        Ok(self.public_key(secp))
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        Some(PrivateKey::clone(self))
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        None
-    }
-
-    fn full_path(&self, _index: u32) -> Option<DerivationPath> {
-        None
-    }
-
-    fn is_fixed(&self) -> bool {
-        true
-    }
-}
-
-impl Key for DescriptorExtendedKey {
-    fn fingerprint(&self, secp: &Secp256k1<All>) -> Option<Fingerprint> {
-        if let Some(fing) = self.master_fingerprint {
-            Some(fing.clone())
-        } else {
-            Some(self.root_xpub(secp).fingerprint())
-        }
-    }
-
-    fn as_public_key(&self, secp: &Secp256k1<All>, index: Option<u32>) -> Result<PublicKey, Error> {
-        Ok(self.derive_xpub(secp, index.unwrap_or(0))?.public_key)
-    }
-
-    fn as_secret_key(&self) -> Option<PrivateKey> {
-        None
-    }
-
-    fn xprv(&self) -> Option<ExtendedPrivKey> {
-        self.secret
-    }
-
-    fn full_path(&self, index: u32) -> Option<DerivationPath> {
-        Some(self.full_path(index))
-    }
-
-    fn is_fixed(&self) -> bool {
-        self.final_index == DerivationIndex::Fixed
-    }
-}
-
 #[serde(try_from = "&str", into = "String")]
 #[derive(Debug, Serialize, Deserialize)]
 pub struct ExtendedDescriptor {
@@ -221,6 +121,12 @@ pub struct ExtendedDescriptor {
     ctx: Secp256k1<All>,
 }
 
+impl fmt::Display for ExtendedDescriptor {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.internal)
+    }
+}
+
 impl std::clone::Clone for ExtendedDescriptor {
     fn clone(&self) -> Self {
         Self {
@@ -421,6 +327,35 @@ impl ExtendedDescriptor {
             _ => false,
         }
     }
+
+    pub fn as_public_version(&self) -> Result<ExtendedDescriptor, Error> {
+        let keys: RefCell<BTreeMap<String, Box<dyn Key>>> = RefCell::new(BTreeMap::new());
+
+        let translatefpk = |string: &String| -> Result<_, Error> {
+            let public = self.keys.get(string).unwrap().public(&self.ctx)?;
+
+            let result = format!("{}", public);
+            keys.borrow_mut().insert(string.clone(), public);
+
+            Ok(result)
+        };
+        let translatefpkh = |string: &String| -> Result<_, Error> {
+            let public = self.keys.get(string).unwrap().public(&self.ctx)?;
+
+            let result = format!("{}", public);
+            keys.borrow_mut().insert(string.clone(), public);
+
+            Ok(result)
+        };
+
+        let internal = self.internal.translate_pk(translatefpk, translatefpkh)?;
+
+        Ok(ExtendedDescriptor {
+            internal,
+            keys: keys.into_inner(),
+            ctx: self.ctx.clone(),
+        })
+    }
 }
 
 impl ExtractPolicy for ExtendedDescriptor {
index dac90aeee1255d041e9556d2da3d10825607fc15..c04512304a3cb81ebfcdcd0f233e3ca73c17139a 100644 (file)
@@ -496,6 +496,17 @@ where
         }
     }
 
+    pub fn public_descriptor(
+        &self,
+        script_type: ScriptType,
+    ) -> Result<Option<ExtendedDescriptor>, Error> {
+        match (script_type, self.change_descriptor.as_ref()) {
+            (ScriptType::External, _) => Ok(Some(self.descriptor.as_public_version()?)),
+            (ScriptType::Internal, None) => Ok(None),
+            (ScriptType::Internal, Some(desc)) => Ok(Some(desc.as_public_version()?)),
+        }
+    }
+
     // Internals
 
     #[cfg(not(target_arch = "wasm32"))]