Added Sighash calculation for psbt

This commit is contained in:
sanket1729 2020-09-08 19:20:48 -05:00
parent e97dda0ffe
commit c1eafff9ef
2 changed files with 118 additions and 3 deletions

View File

@ -19,6 +19,7 @@ use blockdata::transaction::Transaction;
use util::psbt::raw; use util::psbt::raw;
use hashes::{self, sha256, hash160, sha256d, ripemd160}; use hashes::{self, sha256, hash160, sha256d, ripemd160};
use hash_types::Txid;
/// Support hash-preimages in psbt /// Support hash-preimages in psbt
#[derive(Debug)] #[derive(Debug)]
@ -66,7 +67,29 @@ pub enum Error {
preimage: Vec<u8>, preimage: Vec<u8>,
/// Hash value /// Hash value
hash: PsbtHash, 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<u8>,
/// Actual Witness script Hash
actual: Vec<u8>,
},
/// 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 { impl fmt::Display for Error {
@ -88,6 +111,18 @@ impl fmt::Display for Error {
Error::InvalidPreimageHashPair{ref preimage, ref hash} => { Error::InvalidPreimageHashPair{ref preimage, ref hash} => {
// directly using debug forms of psbthash enums // directly using debug forms of psbthash enums
write!(f, "Preimage {:?} does not match hash {:?}", preimage, hash ) 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")
} }
} }
} }

View File

@ -18,9 +18,12 @@
//! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki //! defined at https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
//! except we define PSBTs containing non-standard SigHash types as invalid. //! except we define PSBTs containing non-standard SigHash types as invalid.
use blockdata::script::Script; use blockdata::transaction::{SigHashType, Transaction};
use blockdata::transaction::Transaction; use hash_types::SigHash;
use consensus::{encode, Encodable, Decodable}; use consensus::{encode, Encodable, Decodable};
use util::bip143::SigHashCache;
use blockdata::script::{Builder, Script};
use blockdata::opcodes;
use std::io; use std::io;
@ -88,6 +91,83 @@ impl PartiallySignedTransaction {
Ok(()) 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<SigHash, self::Error> {
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 { impl Encodable for PartiallySignedTransaction {