CouldNotSatisfy in alternative thresh path. How to debug? #487
-
Dear all,
I've have managed to deposit and spend coins on an address but I couldn't spend it in alternative threshold path. I've tried various code and BDK library but eventually I returned to using just rust-miniscript. I can't overcome error In that regard I have some questions:
UPD: I've simplified policy and removed UPD: regarding nSequence and older there is a hint from here, |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 5 replies
-
Unfortunately, there is no easy way to do this. The satisfier code tries all possibilities of satisfaction, there is no direct way to report this as of now. I do agree with having a simple API that outputs all possible satisfied constraints given a psbt is valuable even if a final witness construction is not possible because of other reasons. Can you share your psbt? I can help in debugging. |
Beta Was this translation helpful? Give feedback.
-
So this is my simplified example for descriptor
in which I want to use second branch with one Here is my non-finalized PSBT. I placed there one partial signature for the first key in alternative branch.
And of course here is full example which may be ran from use std::collections::BTreeMap;
use std::fmt;
use std::str::FromStr;
use bitcoin::consensus::serialize;
use bitcoin::util::sighash::SighashCache;
use bitcoin::{PackedLockTime, PrivateKey};
use bitcoind::bitcoincore_rpc::jsonrpc::base64;
use bitcoind::bitcoincore_rpc::RawTx;
use miniscript::bitcoin::consensus::encode::deserialize;
use miniscript::bitcoin::hashes::hex::FromHex;
use miniscript::bitcoin::util::psbt;
use miniscript::bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
use miniscript::bitcoin::{
self, secp256k1, Address, Network, OutPoint, Script, Sequence, Transaction, TxIn, TxOut,
};
use miniscript::psbt::{PsbtExt, PsbtInputExt};
use miniscript::Descriptor;
// note use of re-exported dependencies
#[derive(Debug, PartialEq)]
pub enum DescError {
/// PSBT was not able to finalize
PsbtFinalizeError,
/// Problem with address computation
AddressComputationError,
/// Error while parsing the descriptor
DescParseError,
}
impl fmt::Display for DescError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DescError::PsbtFinalizeError => f.write_str("PSBT was not able to finalize"),
DescError::AddressComputationError => f.write_str("Problem with address computation"),
DescError::DescParseError => f.write_str("Not able to parse the descriptor"),
}
}
}
fn main() {
let secp256k1 = secp256k1::Secp256k1::new();
let s = "wsh(t:or_c(pk(027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de),v:thresh(1,pkh(032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813),a:pkh(03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9),a:pkh(025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28))))#7hut9ukn";
let bridge_descriptor = Descriptor::from_str(&s).unwrap();
//let bridge_descriptor = Descriptor::<bitcoin::PublicKey>::from_str(&s).expect("parse descriptor string");
assert!(bridge_descriptor.sanity_check().is_ok());
println!(
"Bridge pubkey script: {}",
bridge_descriptor.script_pubkey()
);
println!(
"Bridge address: {}",
bridge_descriptor.address(Network::Regtest).unwrap()
);
println!(
"Weight for witness satisfaction cost {}",
bridge_descriptor.max_satisfaction_weight().unwrap()
);
let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw";
let _master_private_key =
PrivateKey::from_str(master_private_key_str).expect("Can't create private key");
let backup1_private_key_str = "cWA34TkfWyHa3d4Vb2jNQvsWJGAHdCTNH73Rht7kAz6vQJcassky";
let backup1_private =
PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key");
let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh";
let backup2_private =
PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key");
let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6";
let _backup3_private =
PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key");
let spend_tx = Transaction {
version: 2,
lock_time: PackedLockTime(5000),
input: vec![],
output: vec![],
};
// Spend one input and spend one output for simplicity.
let mut psbt = Psbt {
unsigned_tx: spend_tx,
unknown: BTreeMap::new(),
proprietary: BTreeMap::new(),
xpub: BTreeMap::new(),
version: 0,
inputs: vec![],
outputs: vec![],
};
let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f50500000000220020c0ebf552acd2a6f5dee4e067daaef17b3521e283aeaa44a475278617e3d2238a0247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000";
let depo_tx: Transaction = deserialize(&Vec::<u8>::from_hex(hex_tx).unwrap()).unwrap();
let receiver = Address::from_str("bcrt1qsdks5za4t6sevaph6tz9ddfjzvhkdkxe9tfrcy").unwrap();
let amount = 100000000;
let (outpoint, witness_utxo) = get_vout(&depo_tx, bridge_descriptor.script_pubkey());
let mut txin = TxIn::default();
txin.previous_output = outpoint;
txin.sequence = Sequence::from_height(26); //Sequence::MAX; //
psbt.unsigned_tx.input.push(txin);
psbt.unsigned_tx.output.push(TxOut {
script_pubkey: receiver.script_pubkey(),
value: amount / 5 - 500,
});
psbt.unsigned_tx.output.push(TxOut {
script_pubkey: bridge_descriptor.script_pubkey(),
value: amount * 4 / 5,
});
// Generating signatures & witness data
let mut input = psbt::Input::default();
input
.update_with_descriptor_unchecked(&bridge_descriptor)
.unwrap();
input.witness_utxo = Some(witness_utxo.clone());
psbt.inputs.push(input);
psbt.outputs.push(psbt::Output::default());
let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
let msg = psbt
.sighash_msg(0, &mut sighash_cache, None)
.unwrap()
.to_secp_msg();
// Fixme: Take a parameter
let hash_ty = bitcoin::EcdsaSighashType::All;
let sk1 = secp256k1::SecretKey::from_slice(&backup1_private.to_bytes()).unwrap();
let sk2 = secp256k1::SecretKey::from_slice(&backup2_private.to_bytes()).unwrap();
// Finally construct the signature and add to psbt
let sig1 = secp256k1.sign_ecdsa(&msg, &sk1);
let pk1 = backup1_private.public_key(&secp256k1);
assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok());
// Second key just in case
let sig2 = secp256k1.sign_ecdsa(&msg, &sk2);
let pk2 = backup2_private.public_key(&secp256k1);
assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok());
psbt.inputs[0].partial_sigs.insert(
pk1,
bitcoin::EcdsaSig {
sig: sig1,
hash_ty: hash_ty,
},
);
println!("{:#?}", psbt);
let serialized = serialize(&psbt);
println!("{}", base64::encode(&serialized));
psbt.finalize_mut(&secp256k1).unwrap();
println!("{:#?}", psbt);
let tx = psbt.extract_tx();
println!("{}", tx.raw_hex());
}
// Find the Outpoint by spk
fn get_vout(tx: &Transaction, spk: Script) -> (OutPoint, TxOut) {
for (i, txout) in tx.clone().output.into_iter().enumerate() {
if spk == txout.script_pubkey {
return (OutPoint::new(tx.txid(), i as u32), txout);
}
}
panic!("Only call get vout on functions which have the expected outpoint");
} |
Beta Was this translation helpful? Give feedback.
-
Thanks for the detailed example. TLDR: finalize psbt API for I think I figured out the issue. Unfortunately, this is a bug in the current miniscript psbt API. The psbt finalize API tries to infer minsicript from the raw In trying to satisfy |
Beta Was this translation helpful? Give feedback.
Thanks for the detailed example. TLDR: finalize psbt API for
pkh
inside thresh wherepkh
is not being satisfied has a bug. As in your example, only 1 of pkh is satisfied other two are dissatisfied. I am working on a patch.I think I figured out the issue. Unfortunately, this is a bug in the current miniscript psbt API. The psbt finalize API tries to infer minsicript from the raw
bitcoin::Script
, so it needs some information to maintain the mapping from pubkey_hash to public key. Right now, the finalized API looks all keys inpartial_sigs
field to find whichbitcoin::PublicKey
hashes to the given public key hash. While this works with satisfying pkh fragments, it does not work for dissatis…