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:
parent
490c88934e
commit
1faf0ed0a0
@ -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]
|
||||||
|
@ -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()
|
||||||
});
|
});
|
||||||
|
@ -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/*))");
|
||||||
|
@ -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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user