diff --git a/src/util/psbt/error.rs b/src/util/psbt/error.rs index 1e006bc1..a23f5156 100644 --- a/src/util/psbt/error.rs +++ b/src/util/psbt/error.rs @@ -19,6 +19,7 @@ use blockdata::transaction::Transaction; use util::psbt::raw; use hashes::{self, sha256, hash160, sha256d, ripemd160}; +use hash_types::Txid; /// Support hash-preimages in psbt #[derive(Debug)] @@ -66,7 +67,29 @@ pub enum Error { preimage: Vec, /// Hash value hash: PsbtHash, - } + }, + /// If NonWitnessUtxo is used, the nonWitnessUtxo txid must + /// be the same of prevout txid + InvalidNonWitnessUtxo{ + /// Pre-image + prevout_txid: Txid, + /// Hash value + non_witness_utxo_txid: Txid, + }, + /// Incorrect P2sh/p2wsh script hash for the witness/redeem + /// script + InvalidWitnessScript{ + /// Expected Witness/Redeem Script Hash + // returns a vec to unify the p2wsh(sha2) and p2sh(hash160) + expected: Vec, + /// Actual Witness script Hash + actual: Vec, + }, + /// Currently only p2wpkh and p2wsh scripts are possible in segwit + UnrecognizedWitnessProgram, + /// The psbt input must either have an associated nonWitnessUtxo or + /// a WitnessUtxo + MustHaveSpendingUtxo, } impl fmt::Display for Error { @@ -88,6 +111,18 @@ impl fmt::Display for Error { Error::InvalidPreimageHashPair{ref preimage, ref hash} => { // directly using debug forms of psbthash enums write!(f, "Preimage {:?} does not match hash {:?}", preimage, hash ) + }, + Error::InvalidNonWitnessUtxo{ref prevout_txid, ref non_witness_utxo_txid} => { + write!(f, "NonWitnessUtxo txid {} must be the same as prevout txid {}", non_witness_utxo_txid, prevout_txid) + }, + Error::InvalidWitnessScript{ref expected, ref actual} => { + write!(f, "Invalid Witness/Redeem script: Expected {:?}, got {:?}", expected, actual) + } + Error::UnrecognizedWitnessProgram => { + f.write_str("Witness program must be p2wpkh/p2wsh") + } + Error::MustHaveSpendingUtxo => { + f.write_str("Input must either WitnessUtxo/ NonWitnessUtxo") } } } diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 3252976a..efd8010f 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -18,9 +18,12 @@ //! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki //! except we define PSBTs containing non-standard SigHash types as invalid. -use blockdata::script::Script; -use blockdata::transaction::Transaction; +use blockdata::transaction::{SigHashType, Transaction}; +use hash_types::SigHash; use consensus::{encode, Encodable, Decodable}; +use util::bip143::SigHashCache; +use blockdata::script::{Builder, Script}; +use blockdata::opcodes; use std::io; @@ -88,6 +91,83 @@ impl PartiallySignedTransaction { Ok(()) } + + /// Calculate the Sighash for the Psbt Input at idx depending on whether input spends + /// spends a segwit or not + /// #Panics: + /// Panics if the index >= number of inputs in psbt + pub fn signature_hash(&self, idx: usize) -> Result { + let inp = &self.inputs[idx]; + let sighash_type = inp.sighash_type.unwrap_or_else(|| SigHashType::All); + // Compute Script code for the Script. When this script is used as scriptPubkey, + // the Script Code determines what goes into sighash. + // For non-p2sh non-segwit outputs and non-p2sh wrapped segwit-outputs + // script code is the public key. + let sighash = if let Some(ref non_witness_utxo) = inp.non_witness_utxo { + let spent_outpoint = self.global.unsigned_tx.input[idx].previous_output; + if spent_outpoint.txid != non_witness_utxo.txid() { + return Err(Error::InvalidNonWitnessUtxo { + prevout_txid: spent_outpoint.txid, + non_witness_utxo_txid: non_witness_utxo.txid() + }); + } + let script_pubkey = &non_witness_utxo.output[spent_outpoint.vout as usize].script_pubkey; + if let Some(ref redeem_script) = inp.redeem_script { + if redeem_script.to_p2sh() != *script_pubkey { + return Err(Error::InvalidWitnessScript{ + expected: script_pubkey.to_bytes(), + actual: redeem_script.to_p2sh().into_bytes(), + }); + } + self.global.unsigned_tx.signature_hash( + idx, &redeem_script, sighash_type.as_u32() + ) + } else { + self.global.unsigned_tx.signature_hash( + idx, &script_pubkey, sighash_type.as_u32() + ) + } + } else if let Some(ref witness_utxo) = inp.witness_utxo { + let script_pubkey = if let Some(ref redeem_script) = inp.redeem_script { + if redeem_script.to_p2sh() != witness_utxo.script_pubkey { + return Err(Error::InvalidWitnessScript{ + expected: witness_utxo.script_pubkey.to_bytes(), + actual: redeem_script.to_p2sh().into_bytes(), + }); + } + redeem_script + } else { + &witness_utxo.script_pubkey + }; + + let amt = witness_utxo.value; + let mut sighash_cache = SigHashCache::new(&self.global.unsigned_tx); + if let Some(ref witness_script) = inp.witness_script { + if witness_script.to_v0_p2wsh() != *script_pubkey { + return Err(Error::InvalidWitnessScript{ + expected: script_pubkey.clone().into_bytes(), + actual: witness_script.to_p2sh().into_bytes(), + }); + } + sighash_cache.signature_hash(idx, &witness_script, amt, sighash_type) + } else if script_pubkey.is_v0_p2wpkh() { + // Indirect way to get script code + let builder = Builder::new(); + let script_code = builder + .push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_HASH160) + .push_slice(&script_pubkey[2..22]) + .push_opcode(opcodes::all::OP_EQUALVERIFY) + .into_script(); + sighash_cache.signature_hash(idx, &script_code, amt, sighash_type) + } else { + return Err(Error::UnrecognizedWitnessProgram); + } + } else { + return Err(Error::MustHaveSpendingUtxo); + }; + Ok(sighash) + } } impl Encodable for PartiallySignedTransaction {