Skip to content

Commit 1baf29f

Browse files
committed
Add psbt finalizer support
1 parent 91becfe commit 1baf29f

File tree

3 files changed

+128
-16
lines changed

3 files changed

+128
-16
lines changed

src/psbt/finalizer.rs

+106-16
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,82 @@
1919
//! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki`
2020
//!
2121
22+
use bitcoin::schnorr::XOnlyPublicKey;
23+
use util::{script_is_v1_tr, witness_size};
24+
2225
use super::{sanity_check, Psbt};
2326
use super::{Error, InputError, PsbtInputSatisfier};
2427
use bitcoin::blockdata::witness::Witness;
2528
use bitcoin::secp256k1::{self, Secp256k1};
29+
use bitcoin::util::taproot::LeafVersion;
2630
use bitcoin::{self, PublicKey, Script};
2731
use descriptor::DescriptorTrait;
2832
use interpreter;
2933
use Descriptor;
3034
use Miniscript;
31-
use {BareCtx, Legacy, Segwitv0};
35+
use Satisfier;
36+
use {BareCtx, Legacy, Segwitv0, Tap};
37+
38+
// Satisfy the taproot descriptor. It is not possible to infer the complete
39+
// descriptor from psbt because the information about all the scripts might not
40+
// be present. Also, currently the spec does not support hidden branches, so
41+
// inferring a descriptor is not possible
42+
fn construct_tap_witness(
43+
spk: &Script,
44+
sat: &PsbtInputSatisfier,
45+
allow_mall: bool,
46+
) -> Result<Vec<Vec<u8>>, InputError> {
47+
assert!(script_is_v1_tr(&spk));
48+
49+
// try the script spend path first
50+
if let Some(sig) =
51+
<PsbtInputSatisfier as Satisfier<XOnlyPublicKey>>::lookup_tap_key_spend_sig(sat)
52+
{
53+
return Ok(vec![sig.to_vec()]);
54+
}
55+
// Next script spends
56+
let (mut min_wit, mut min_wit_len) = (None, None);
57+
if let Some(block_map) =
58+
<PsbtInputSatisfier as Satisfier<XOnlyPublicKey>>::lookup_tap_control_block_map(sat)
59+
{
60+
for (control_block, (script, ver)) in block_map {
61+
if *ver != LeafVersion::TapScript {
62+
// We don't know how to satisfy non default version scripts yet
63+
continue;
64+
}
65+
let ms = match Miniscript::<XOnlyPublicKey, Tap>::parse_insane(script) {
66+
Ok(ms) => ms,
67+
Err(..) => continue, // try another script
68+
};
69+
let mut wit = if allow_mall {
70+
match ms.satisfy_malleable(sat) {
71+
Ok(ms) => ms,
72+
Err(..) => continue,
73+
}
74+
} else {
75+
match ms.satisfy(sat) {
76+
Ok(ms) => ms,
77+
Err(..) => continue,
78+
}
79+
};
80+
wit.push(ms.encode().into_bytes());
81+
wit.push(control_block.serialize());
82+
let wit_len = Some(witness_size(&wit));
83+
if min_wit_len.is_some() && wit_len > min_wit_len {
84+
continue;
85+
} else {
86+
// store the minimum
87+
min_wit = Some(wit);
88+
min_wit_len = wit_len;
89+
}
90+
}
91+
min_wit.ok_or(InputError::CouldNotSatisfyTr)
92+
} else {
93+
// No control blocks found
94+
Err(InputError::CouldNotSatisfyTr)
95+
}
96+
}
97+
3298
// Get the scriptpubkey for the psbt input
3399
fn get_scriptpubkey(psbt: &Psbt, index: usize) -> Result<&Script, InputError> {
34100
let script_pubkey;
@@ -299,16 +365,28 @@ pub fn finalize_helper<C: secp256k1::Verification>(
299365

300366
// Actually construct the witnesses
301367
for index in 0..psbt.inputs.len() {
302-
// Get a descriptor for this input
303-
let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?;
368+
let (witness, script_sig) = {
369+
let spk = get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?;
370+
let sat = PsbtInputSatisfier::new(&psbt, index);
304371

305-
//generate the satisfaction witness and scriptsig
306-
let (witness, script_sig) = if !allow_mall {
307-
desc.get_satisfaction(PsbtInputSatisfier::new(&psbt, index))
308-
} else {
309-
desc.get_satisfaction_mall(PsbtInputSatisfier::new(&psbt, index))
310-
}
311-
.map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))?;
372+
if script_is_v1_tr(spk) {
373+
// Deal with tr case separately, unfortunately we cannot infer the full descriptor for Tr
374+
let wit = construct_tap_witness(spk, &sat, allow_mall)
375+
.map_err(|e| Error::InputError(e, index))?;
376+
(wit, Script::new())
377+
} else {
378+
// Get a descriptor for this input.
379+
let desc = get_descriptor(&psbt, index).map_err(|e| Error::InputError(e, index))?;
380+
381+
//generate the satisfaction witness and scriptsig
382+
if !allow_mall {
383+
desc.get_satisfaction(PsbtInputSatisfier::new(&psbt, index))
384+
} else {
385+
desc.get_satisfaction_mall(PsbtInputSatisfier::new(&psbt, index))
386+
}
387+
.map_err(|e| Error::InputError(InputError::MiniscriptError(e), index))?
388+
}
389+
};
312390

313391
let input = &mut psbt.inputs[index];
314392
//Fill in the satisfactions
@@ -323,12 +401,24 @@ pub fn finalize_helper<C: secp256k1::Verification>(
323401
Some(witness)
324402
};
325403
//reset everything
326-
input.redeem_script = None;
327-
input.partial_sigs.clear();
328-
input.sighash_type = None;
329-
input.redeem_script = None;
330-
input.bip32_derivation.clear();
331-
input.witness_script = None;
404+
input.partial_sigs.clear(); // 0x02
405+
input.sighash_type = None; // 0x03
406+
input.redeem_script = None; // 0x04
407+
input.witness_script = None; // 0x05
408+
input.bip32_derivation.clear(); // 0x05
409+
// finalized witness 0x06 and 0x07 are not clear
410+
// 0x09 Proof of reserves not yet supported
411+
input.ripemd160_preimages.clear(); // 0x0a
412+
input.sha256_preimages.clear(); // 0x0b
413+
input.hash160_preimages.clear(); // 0x0c
414+
input.hash256_preimages.clear(); // 0x0d
415+
// psbt v2 fields till 0x012 not supported
416+
input.tap_key_sig = None; // 0x013
417+
input.tap_script_sigs.clear(); // 0x014
418+
input.tap_scripts.clear(); // 0x015
419+
input.tap_key_origins.clear(); // 0x16
420+
input.tap_internal_key = None; // x017
421+
input.tap_merkle_root = None; // 0x018
332422
}
333423
// Double check everything with the interpreter
334424
// This only checks whether the script will be executed

src/psbt/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ pub enum InputError {
4545
SecpErr(bitcoin::secp256k1::Error),
4646
/// Key errors
4747
KeyErr(bitcoin::util::key::Error),
48+
/// Could not satisfy taproot descriptor
49+
/// This error is returned when both script path and key paths could not be
50+
/// satisfied. We cannot return a detailed error because we try all miniscripts
51+
/// in script spend path, we cannot know which miniscript failed.
52+
CouldNotSatisfyTr,
4853
/// Error doing an interpreter-check on a finalized psbt
4954
Interpreter(interpreter::Error),
5055
/// Redeem script does not match the p2sh hash
@@ -160,6 +165,9 @@ impl fmt::Display for InputError {
160165
sighashflag {:?} rather than required {:?}",
161166
pubkey.key, got, required
162167
),
168+
InputError::CouldNotSatisfyTr => {
169+
write!(f, "Could not satisfy Tr descriptor")
170+
}
163171
}
164172
}
165173
}

src/util.rs

+14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use bitcoin;
2+
use bitcoin::blockdata::opcodes;
23
use bitcoin::blockdata::script;
4+
use bitcoin::blockdata::script::Instruction;
35
use bitcoin::Script;
46
use miniscript::context;
57

@@ -46,3 +48,15 @@ impl MsKeyBuilder for script::Builder {
4648
}
4749
}
4850
}
51+
52+
// This belongs in rust-bitcoin, make this upstream later
53+
pub(crate) fn script_is_v1_tr(s: &Script) -> bool {
54+
let mut iter = s.instructions_minimal();
55+
if s.len() != 32
56+
|| iter.next() != Some(Ok(Instruction::Op(opcodes::all::OP_PUSHNUM_1)))
57+
|| iter.next() != Some(Ok(Instruction::Op(opcodes::all::OP_PUSHBYTES_32)))
58+
{
59+
return false;
60+
}
61+
return true;
62+
}

0 commit comments

Comments
 (0)