Fix the recovery of a descriptor given a PSBT

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.
This commit is contained in:
Alekos Filini 2020-09-29 18:18:50 +02:00
parent 490c88934e
commit 1faf0ed0a0
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
4 changed files with 34 additions and 8 deletions

View File

@ -28,7 +28,7 @@ tiny-bip39 = { version = "^0.7", optional = true }
[patch.crates-io] [patch.crates-io]
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin/", rev = "478e091" } 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 # Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@ -358,13 +358,12 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
derive_path = index derive_path = index
.get_key_value(&root_fingerprint) .get_key_value(&root_fingerprint)
.and_then(|(fingerprint, path)| xpub.matches(*fingerprint, path)) .and_then(|(fingerprint, path)| xpub.matches(*fingerprint, path))
.map(|prefix_path| prefix_path.into_iter().cloned().collect::<Vec<_>>())
.map(|prefix| { .map(|prefix| {
index index
.get(&xpub.root_fingerprint()) .get(&xpub.root_fingerprint())
.unwrap() .unwrap()
.into_iter() .into_iter()
.skip(prefix.len()) .skip(prefix.into_iter().count())
.cloned() .cloned()
.collect() .collect()
}); });

View File

@ -2404,6 +2404,21 @@ mod test {
assert_eq!(extracted.input[0].witness.len(), 2); 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] #[test]
fn test_sign_single_xprv_sh_wpkh() { fn test_sign_single_xprv_sh_wpkh() {
let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))"); let (wallet, _, _) = get_funded_wallet("sh(wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*))");

View File

@ -132,6 +132,8 @@ impl From<Fingerprint> for SignerId<DescriptorPublicKey> {
pub enum SignerError { pub enum SignerError {
/// The private key is missing for the required public key /// The private key is missing for the required public key
MissingKey, MissingKey,
/// The private key in use has the right fingerprint but derives differently than expected
InvalidKey,
/// The user canceled the operation /// The user canceled the operation
UserCanceled, UserCanceled,
/// The sighash is missing in the PSBT input /// The sighash is missing in the PSBT input
@ -199,20 +201,30 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
let deriv_path = match psbt.inputs[input_index] let (public_key, deriv_path) = match psbt.inputs[input_index]
.hd_keypaths .hd_keypaths
.iter() .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() .next()
{ {
Some(deriv_path) => deriv_path, Some((pk, full_path)) => (pk, full_path.clone()),
None => return Ok(()), // TODO: should report an error maybe? None => return Ok(()),
}; };
let ctx = Secp256k1::signing_only(); let ctx = Secp256k1::signing_only();
let derived_key = self.xkey.derive_priv(&ctx, &deriv_path).unwrap(); 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 { fn sign_whole_tx(&self) -> bool {