Psbt: Add finalizer and extractor#119
Conversation
5dd1e1f to
1b8fbd2
Compare
src/miniscript/satisfy.rs
Outdated
| pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result<BitcoinSig, Error> { | ||
| let (flag, sig) = rawsig.split_last().unwrap(); | ||
| let flag = bitcoin::SigHashType::from_u32(*flag as u32); | ||
| let sig = match secp256k1::Signature::from_der(sig) { |
There was a problem hiding this comment.
Will
let sig = secp256k1::Signature::from_der(sig)?;
work?
There was a problem hiding this comment.
Yup, that should be cleaner. We already have a From implementation from the Secp error
src/psbt/mod.rs
Outdated
| #[derive(Debug)] | ||
| pub enum InputError { | ||
| /// Public key parsing error | ||
| InvalidPubkey { pubkey: Vec<u8> }, |
There was a problem hiding this comment.
We should retain the secp256k1::Error as well here
src/psbt/mod.rs
Outdated
| /// Public key parsing error | ||
| InvalidPubkey { pubkey: Vec<u8> }, | ||
| /// Redeem script does not match the p2sh hash | ||
| InvalidRedeemScript, |
There was a problem hiding this comment.
We should retain the expected and actual hashes here
src/psbt/mod.rs
Outdated
| /// Redeem script does not match the p2sh hash | ||
| InvalidRedeemScript, | ||
| /// Witness script does not match the p2wsh hash | ||
| InvalidWitnessScript, |
There was a problem hiding this comment.
We should retain the expected and actual hashes here
src/psbt/mod.rs
Outdated
| InputError::MissingWitness => write!(f, "PSBT is missing witness"), | ||
| InputError::MissingRedeemScript => write!(f, "PSBT is Redeem script"), | ||
| InputError::MissingUtxo => { | ||
| write!(f, "PSBT is missing both witness and non-witnes UTXO") |
b2e9d43 to
c60f158
Compare
| use Terminal; | ||
|
|
||
| /// Type alias for a signature/hashtype pair | ||
| pub type BitcoinSig = (secp256k1::Signature, bitcoin::SigHashType); |
There was a problem hiding this comment.
Shouldn't BitcoinSig type and bitcoinsig_from_rawsig function belong to rust-bitcoin, and not miniscript lib?
There was a problem hiding this comment.
Conceptually yes. But I think along with that we would also want to add new APIs to use this type instead.
| /// Helper function to create BitcoinSig from Rawsig | ||
| /// Useful for downstream when implementing Satisfier. | ||
| /// Returns underlying secp if the Signature is not of correct formart | ||
| pub fn bitcoinsig_from_rawsig(rawsig: &[u8]) -> Result<BitcoinSig, Error> { |
There was a problem hiding this comment.
It's a pity we do not have TryFrom in rust 1.22... Otherwise impl TryFrom<&[u8]> for BitcoinSig would look more native.
| Ok(()) | ||
| } | ||
|
|
||
| pub fn finalize(psbt: &mut Psbt) -> Result<(), super::Error> { |
There was a problem hiding this comment.
Mutably borrowing the PSBT will leave it in a potentially invalid state in case of an error. I'd rather consume and return the PSBT.
There was a problem hiding this comment.
We do check the validity of the psbt before returning.
Somehow, it seems that allocating a new psbt when we are only adding two fields per input(final sciptsig and final witness) seems wrong.
There was a problem hiding this comment.
While working on PSBTs in rust-bitcoin, I am not getting why finalize shouldn't be part of PartiallySignedTransaction structure with access to self
https://github.com/rust-bitcoin/rust-bitcoin/pull/472/files#diff-77e0d8cfeb8d4f84f6f901f6fc14139fR337
There was a problem hiding this comment.
I think the method finalize could belong there, but rust-miniscript offers a way to construct the final witness and final scriptsig in a generic way by the satisfier trait.
We don't want to introduce a rust-miniscript dependancy to rust-bitcoin.
Maybe one way might be to have the method at both places, the generic finalizer in rust-miniscript and one method in rust-bitcoin that only fills in the required fields.
There was a problem hiding this comment.
The more I think about this, I think there definitely should be a finalize method in rust-bitcoin that works for all scripts. Miniscript is more of helper to the finalizer. The job of a finalizer is to fill in the final values for witness and script_sig even if they are not miniscripts.
I think the API should be as follows:
rust-miniscript:
struct finalized_witness((Script, Vec<Vec<u8>>));
fn finalize_miniscript(&psb, S: Satisfier) -> Vec<finalized_witness>
// Final script_sig and witness
// Only works when all scripts used are miniscripts
This should also address @sgeisler concern about taking in mutable reference.
rust-bitcoin:
fn finalize(&mut self, final_witness: Vec<finalized_witness>)
// This fills in the values in psbt.
cc @apoelstra
There was a problem hiding this comment.
fn finalize(&mut self, witness: Vec<u8>, script_sig: Script)
Do I understand it correctly that this finalize method would work on per-input basis? Otherwise the witness/script sig had to be matched to an input which would make the finalization at least O(n^2). This also means that miniscript finalize had to return multiple (Script, Vec<u8>) tuples (one for each input).
EDIT: and isn't the witness Vec<Vec<u8>>?
There was a problem hiding this comment.
Yup, my bad. Edited the original post. We can discuss the API even further. Maybe it should only work on a per input basis, but I wanted to share my opinion on how to the split between rust-miniscript's role and rust-bitcoin's role on finalizing.
src/psbt/finalizer.rs
Outdated
| } | ||
| None => { | ||
| return Err(InputError::MissingPubkey); | ||
| } |
There was a problem hiding this comment.
more idiomatic (and readable) to move the return out of the match so it is clear there are no non-returning branches
return match partial_sig_contains_pk {
Some((pk, _sig)) => Ok(Descriptor::Pkh(pk.to_owned())),
None => Err(InputError::MissingPubkey),
};
There was a problem hiding this comment.
also every single return in this function could be removed since they appear at the end of the block (you need to change a couple ifs to else ifs). This would also be clearer since it emphasizes that this giant if statement is the only thing going on.
There was a problem hiding this comment.
Removing all returns as it is the last statement
There was a problem hiding this comment.
Rather, should I keep the short circuit return paths? Maybe those are more clear
There was a problem hiding this comment.
I only saw one that I'd consider a "short-circuit return path", which is the one I complained about below because it was immediately followed by an .unwrap().
src/psbt/finalizer.rs
Outdated
| if inp.redeem_script.is_none() { | ||
| return Err(InputError::MissingRedeemScript); | ||
| } | ||
| let redeem_script = inp.redeem_script.as_ref().unwrap(); |
There was a problem hiding this comment.
This is_none followed by unwrap should be replaced with a match (which will add an indentation level but will eliminate a panic case)
| let script_pubkey = | ||
| get_scriptpubkey(psbt, index).map_err(|e| Error::InputError(e, index))?; | ||
| let empty_script_sig = Script::new(); | ||
| let empty_witness = Vec::new(); |
There was a problem hiding this comment.
nit: i would drop both of these variables, they are only used once and both have longer names than the expressions they replace :P
There was a problem hiding this comment.
I think I need a let binding for the following?
fn_name(&Script::new())
2c20d3d to
1a3db30
Compare
Implemented psbt finalizer and extractor. Fixes #117 .
This PR also has some small refactors.
Timelocks: There is no special witness required frompsbt. The transaction input containssequenceand transaction has the correspondinglocktime.Hashlocks: Awaiting on BIP174: add hash preimage fields to inputs bitcoin/bips#955 and to be merged, psbt support in rust-bitcoin.Sigs: Partial sigs contain the data required for the finalizer.The interpreter signature checks rely on sighash calculation from #116. Once the sighash functionality is added this PR can be updated to use it. Currently, this returns true. We can even merge this first and add the check in a followup PR once #116 is in.