]> Untitled Git - bdk/commitdiff
Fix the recovery of a descriptor given a PSBT
authorAlekos Filini <alekos.filini@gmail.com>
Tue, 29 Sep 2020 16:18:50 +0000 (18:18 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 2 Oct 2020 15:52:11 +0000 (17:52 +0200)
This commit upgrades `rust-miniscript` with a fix to only return the prefix that
matches a `hd_keypath` instead of the full derivation path, and then adapts the
signer code accordingly.

This commit closes #108 and #109.

Cargo.toml
src/descriptor/mod.rs
src/wallet/mod.rs
src/wallet/signer.rs

index 3c633af514bf66e0833172b7f93e646c2dc7f16b..ef1a0d2a48eb56cbcd8643b56a9d848336a3b192 100644 (file)
@@ -28,7 +28,7 @@ tiny-bip39 = { version = "^0.7", optional = true }
 
 [patch.crates-io]
 bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" }
-miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", branch = "descriptor-public-key" }
+miniscript = { git = "https://github.com/MagicalBitcoin/rust-miniscript", rev = "d0322ac" }
 
 # Platform-specific dependencies
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
index c9a3f88d39ed4781349edf7416491f987ddd3519..ba4c341092fc12b34f16a501f3301b81087db82c 100644 (file)
@@ -358,13 +358,12 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
                 derive_path = index
                     .get_key_value(&root_fingerprint)
                     .and_then(|(fingerprint, path)| xpub.matches(*fingerprint, path))
-                    .map(|prefix_path| prefix_path.into_iter().cloned().collect::<Vec<_>>())
                     .map(|prefix| {
                         index
                             .get(&xpub.root_fingerprint())
                             .unwrap()
                             .into_iter()
-                            .skip(prefix.len())
+                            .skip(prefix.into_iter().count())
                             .cloned()
                             .collect()
                     });
index 55d77642105a168258c07f53242b37c5fb60c0d2..9991faba407871104a73b24e0b48ab46e074eb38 100644 (file)
@@ -2404,6 +2404,21 @@ mod test {
         assert_eq!(extracted.input[0].witness.len(), 2);
     }
 
+    #[test]
+    fn test_sign_single_xprv_bip44_path() {
+        let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/44'/0'/0'/0/*)");
+        let addr = wallet.get_new_address().unwrap();
+        let (psbt, _) = wallet
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
+            .unwrap();
+
+        let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
+        assert_eq!(finalized, true);
+
+        let extracted = signed_psbt.extract_tx();
+        assert_eq!(extracted.input[0].witness.len(), 2);
+    }
+
     #[test]
     fn test_sign_single_xprv_sh_wpkh() {
         let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");
index 12905151af734da65b8bc0b45252e49438275b1f..0618d76336a239e6bc83a2450132dd16b4b0dda4 100644 (file)
@@ -132,6 +132,8 @@ impl From<Fingerprint> for SignerId<DescriptorPublicKey> {
 pub enum SignerError {
     /// The private key is missing for the required public key
     MissingKey,
+    /// The private key in use has the right fingerprint but derives differently than expected
+    InvalidKey,
     /// The user canceled the operation
     UserCanceled,
     /// The sighash is missing in the PSBT input
@@ -199,20 +201,30 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
             return Err(SignerError::InputIndexOutOfRange);
         }
 
-        let deriv_path = match psbt.inputs[input_index]
+        let (public_key, deriv_path) = match psbt.inputs[input_index]
             .hd_keypaths
             .iter()
-            .filter_map(|(_, &(fingerprint, ref path))| self.matches(fingerprint.clone(), &path))
+            .filter_map(|(pk, &(fingerprint, ref path))| {
+                if self.matches(fingerprint.clone(), &path).is_some() {
+                    Some((pk, path))
+                } else {
+                    None
+                }
+            })
             .next()
         {
-            Some(deriv_path) => deriv_path,
-            None => return Ok(()), // TODO: should report an error maybe?
+            Some((pk, full_path)) => (pk, full_path.clone()),
+            None => return Ok(()),
         };
 
         let ctx = Secp256k1::signing_only();
 
         let derived_key = self.xkey.derive_priv(&ctx, &deriv_path).unwrap();
-        derived_key.private_key.sign(psbt, Some(input_index))
+        if &derived_key.private_key.public_key(&ctx) != public_key {
+            Err(SignerError::InvalidKey)
+        } else {
+            derived_key.private_key.sign(psbt, Some(input_index))
+        }
     }
 
     fn sign_whole_tx(&self) -> bool {