diff --git a/CHANGELOG.md b/CHANGELOG.md index 53a3f8fa1..c1f386492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 9.0.0 - November 5, 2022 + +- Fixed a bug dealing with dissatisfying pkh inside thresh +- Changed the signature of `Satisfier::lookup_raw_pkh_pk` API. Only custom implementations + of `Satisfier` need to be updated. The psbt APIs are unchanged. +- Fixed a bug related to display of `raw_pk_h`. These descriptors are experimental + and only usable by opting via `ExtParams` while parsing string. # 8.0.0 - October 20, 2022 This release contains several significant API overhauls, as well as a bump diff --git a/Cargo.toml b/Cargo.toml index fe74d4120..caa945c8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "miniscript" -version = "8.0.0" +version = "9.0.0" authors = ["Andrew Poelstra , Sanket Kanjalkar "] license = "CC0-1.0" homepage = "https://github.com/rust-bitcoin/rust-miniscript/" @@ -60,3 +60,7 @@ required-features = ["std"] [[example]] name = "taproot" required-features = ["compiler","std"] + +[[example]] +name = "psbt_sign_finalize" +required-features = ["std"] \ No newline at end of file diff --git a/contrib/test.sh b/contrib/test.sh index 0f4605411..363aa822c 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -68,6 +68,7 @@ then cargo run --example psbt cargo run --example xpub_descriptors cargo run --example taproot --features=compiler + cargo run --example psbt_sign_finalize fi if [ "$DO_NO_STD" = true ] diff --git a/examples/psbt_sign_finalize.rs b/examples/psbt_sign_finalize.rs new file mode 100644 index 000000000..9876c92d6 --- /dev/null +++ b/examples/psbt_sign_finalize.rs @@ -0,0 +1,179 @@ +use std::collections::BTreeMap; +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; + +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::::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"); + println!( + "Master public key: {}", + _master_private_key.public_key(&secp256k1) + ); + + let backup1_private_key_str = "cWA34TkfWyHa3d4Vb2jNQvsWJGAHdCTNH73Rht7kAz6vQJcassky"; + let backup1_private = + PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key"); + + println!( + "Backup1 public key: {}", + backup1_private.public_key(&secp256k1) + ); + + let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh"; + let backup2_private = + PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key"); + + println!( + "Backup2 public key: {}", + backup2_private.public_key(&secp256k1) + ); + + let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6"; + let _backup3_private = + PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key"); + + println!( + "Backup3 public key: {}", + _backup3_private.public_key(&secp256k1) + ); + + 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::::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 = backup1_private.inner; + let sk2 = backup2_private.inner; + + // 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"); +} diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 9ebf41210..e691ff931 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -253,7 +253,7 @@ impl fmt::Debug for Terminal { match *self { Terminal::PkK(ref pk) => write!(f, "pk_k({:?})", pk), Terminal::PkH(ref pk) => write!(f, "pk_h({:?})", pk), - Terminal::RawPkH(ref pkh) => write!(f, "pk_h({:?})", pkh), + Terminal::RawPkH(ref pkh) => write!(f, "expr_raw_pk_h({:?})", pkh), Terminal::After(t) => write!(f, "after({})", t), Terminal::Older(t) => write!(f, "older({})", t), Terminal::Sha256(ref h) => write!(f, "sha256({})", h), @@ -307,7 +307,7 @@ impl fmt::Display for Terminal { match *self { Terminal::PkK(ref pk) => write!(f, "pk_k({})", pk), Terminal::PkH(ref pk) => write!(f, "pk_h({})", pk), - Terminal::RawPkH(ref pkh) => write!(f, "pk_h({})", pkh), + Terminal::RawPkH(ref pkh) => write!(f, "expr_raw_pk_h({})", pkh), Terminal::After(t) => write!(f, "after({})", t), Terminal::Older(t) => write!(f, "older({})", t), Terminal::Sha256(ref h) => write!(f, "sha256({})", h), diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index a80faea40..03f2e1b1c 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -60,8 +60,13 @@ pub trait Satisfier { None } - /// Given a raw `Pkh`, lookup corresponding `Pk` - fn lookup_raw_pkh_pk(&self, _: &hash160::Hash) -> Option { + /// Given a raw `Pkh`, lookup corresponding [`bitcoin::PublicKey`] + fn lookup_raw_pkh_pk(&self, _: &hash160::Hash) -> Option { + None + } + + /// Given a raw `Pkh`, lookup corresponding [`bitcoin::XOnlyPublicKey`] + fn lookup_raw_pkh_x_only_pk(&self, _: &hash160::Hash) -> Option { None } @@ -183,8 +188,8 @@ where self.get(&key.to_pubkeyhash(SigType::Ecdsa)).map(|x| x.1) } - fn lookup_raw_pkh_pk(&self, pk_hash: &hash160::Hash) -> Option { - self.get(pk_hash).map(|x| x.0.clone()) + fn lookup_raw_pkh_pk(&self, pk_hash: &hash160::Hash) -> Option { + self.get(pk_hash).map(|x| x.0.to_public_key()) } fn lookup_raw_pkh_ecdsa_sig( @@ -224,10 +229,14 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_tap_leaf_script_sig(p, h) } - fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { + fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { (**self).lookup_raw_pkh_pk(pkh) } + fn lookup_raw_pkh_x_only_pk(&self, pkh: &hash160::Hash) -> Option { + (**self).lookup_raw_pkh_x_only_pk(pkh) + } + fn lookup_raw_pkh_ecdsa_sig( &self, pkh: &hash160::Hash, @@ -290,10 +299,14 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey, S: Satisfier> Satisfier for &' (**self).lookup_tap_key_spend_sig() } - fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { + fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { (**self).lookup_raw_pkh_pk(pkh) } + fn lookup_raw_pkh_x_only_pk(&self, pkh: &hash160::Hash) -> Option { + (**self).lookup_raw_pkh_x_only_pk(pkh) + } + fn lookup_raw_pkh_ecdsa_sig( &self, pkh: &hash160::Hash, @@ -406,7 +419,7 @@ macro_rules! impl_tuple_satisfier { fn lookup_raw_pkh_pk( &self, key_hash: &hash160::Hash, - ) -> Option { + ) -> Option { let &($(ref $ty,)*) = self; $( if let Some(result) = $ty.lookup_raw_pkh_pk(key_hash) { @@ -416,6 +429,19 @@ macro_rules! impl_tuple_satisfier { None } + fn lookup_raw_pkh_x_only_pk( + &self, + key_hash: &hash160::Hash, + ) -> Option { + let &($(ref $ty,)*) = self; + $( + if let Some(result) = $ty.lookup_raw_pkh_x_only_pk(key_hash) { + return Some(result); + } + )* + None + } + fn lookup_tap_control_block_map( &self, ) -> Option<&BTreeMap> { @@ -560,20 +586,44 @@ impl Witness { } /// Turn a public key related to a pkh into (part of) a satisfaction - fn pkh_public_key>(sat: S, pkh: &hash160::Hash) -> Self { - match sat.lookup_raw_pkh_pk(pkh) { - Some(pk) => Witness::Stack(vec![pk.to_public_key().to_bytes()]), - // public key hashes are assumed to be unavailable - // instead of impossible since it is the same as pub-key hashes - None => Witness::Unavailable, + fn pkh_public_key, Ctx: ScriptContext>( + sat: S, + pkh: &hash160::Hash, + ) -> Self { + // public key hashes are assumed to be unavailable + // instead of impossible since it is the same as pub-key hashes + match Ctx::sig_type() { + SigType::Ecdsa => match sat.lookup_raw_pkh_pk(pkh) { + Some(pk) => Witness::Stack(vec![pk.to_bytes()]), + None => Witness::Unavailable, + }, + SigType::Schnorr => match sat.lookup_raw_pkh_x_only_pk(pkh) { + Some(pk) => Witness::Stack(vec![pk.serialize().to_vec()]), + None => Witness::Unavailable, + }, } } /// Turn a key/signature pair related to a pkh into (part of) a satisfaction - fn pkh_signature>(sat: S, pkh: &hash160::Hash) -> Self { - match sat.lookup_raw_pkh_ecdsa_sig(pkh) { - Some((pk, sig)) => Witness::Stack(vec![sig.to_vec(), pk.to_public_key().to_bytes()]), - None => Witness::Impossible, + fn pkh_signature, Ctx: ScriptContext>( + sat: S, + pkh: &hash160::Hash, + leaf_hash: &TapLeafHash, + ) -> Self { + match Ctx::sig_type() { + SigType::Ecdsa => match sat.lookup_raw_pkh_ecdsa_sig(pkh) { + Some((pk, sig)) => { + Witness::Stack(vec![sig.to_vec(), pk.to_public_key().to_bytes()]) + } + None => Witness::Impossible, + }, + SigType::Schnorr => match sat.lookup_raw_pkh_tap_leaf_script_sig(&(*pkh, *leaf_hash)) { + Some((pk, sig)) => Witness::Stack(vec![ + sig.to_vec(), + pk.to_x_only_pubkey().serialize().to_vec(), + ]), + None => Witness::Impossible, + }, } } @@ -933,12 +983,19 @@ impl Satisfaction { stack: Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash), has_sig: true, }, - Terminal::PkH(ref pk) => Satisfaction { - stack: Witness::pkh_signature(stfr, &pk.to_pubkeyhash(Ctx::sig_type())), - has_sig: true, - }, + Terminal::PkH(ref pk) => { + let wit = Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash); + let pk_bytes = match Ctx::sig_type() { + SigType::Ecdsa => pk.to_public_key().to_bytes(), + SigType::Schnorr => pk.to_x_only_pubkey().serialize().to_vec(), + }; + Satisfaction { + stack: Witness::combine(wit, Witness::Stack(vec![pk_bytes])), + has_sig: true, + } + } Terminal::RawPkH(ref pkh) => Satisfaction { - stack: Witness::pkh_signature(stfr, pkh), + stack: Witness::pkh_signature::<_, _, Ctx>(stfr, pkh, leaf_hash), has_sig: true, }, Terminal::After(t) => Satisfaction { @@ -1245,17 +1302,23 @@ impl Satisfaction { stack: Witness::push_0(), has_sig: false, }, - Terminal::PkH(ref pk) => Satisfaction { + Terminal::PkH(ref pk) => { + let pk_bytes = match Ctx::sig_type() { + SigType::Ecdsa => pk.to_public_key().to_bytes(), + SigType::Schnorr => pk.to_x_only_pubkey().serialize().to_vec(), + }; + Satisfaction { + stack: Witness::combine(Witness::push_0(), Witness::Stack(vec![pk_bytes])), + has_sig: false, + } + } + Terminal::RawPkH(ref pkh) => Satisfaction { stack: Witness::combine( Witness::push_0(), - Witness::pkh_public_key(stfr, &pk.to_pubkeyhash(Ctx::sig_type())), + Witness::pkh_public_key::<_, _, Ctx>(stfr, pkh), ), has_sig: false, }, - Terminal::RawPkH(ref pkh) => Satisfaction { - stack: Witness::combine(Witness::push_0(), Witness::pkh_public_key(stfr, pkh)), - has_sig: false, - }, Terminal::False => Satisfaction { stack: Witness::empty(), has_sig: false, diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 08c3efb1a..b6258c928 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -297,6 +297,14 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfie .copied() } + fn lookup_raw_pkh_pk(&self, pkh: &hash160::Hash) -> Option { + self.psbt.inputs[self.index] + .bip32_derivation + .iter() + .find(|&(pubkey, _)| pubkey.to_pubkeyhash(SigType::Ecdsa) == *pkh) + .map(|(pubkey, _)| bitcoin::PublicKey::new(*pubkey)) + } + fn lookup_tap_control_block_map( &self, ) -> Option<&BTreeMap> {