Merge rust-bitcoin/rust-bitcoin#1995: Improve segwit signature hash API

4300cf2210 Add p2wpkh and p2wsh signature hash functions (Tobin C. Harding)

Pull request description:

  The word "segwit" refers to segwit v0 and taproot but these functions are version specific. Add `v0` into the function names.

  This is similar to #1994, both based on recent post of mine to bitcoin dev mailing list.

ACKs for top commit:
  stevenroose:
    ACK 4300cf2210
  apoelstra:
    ACK 4300cf2210

Tree-SHA512: 723fc302954514da0fa57a3890b9f62e9d8d1b25289b8db00611d8bc34c5000b9e54943f57b8e94befcaf72633ac078b2ff66a1da0c5bb483cfaa584e3cb6014
This commit is contained in:
Andrew Poelstra 2023-08-21 14:43:24 +00:00
commit 5bf2117dc4
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 128 additions and 35 deletions

View File

@ -38,11 +38,10 @@ fn compute_sighash_p2wpkh(raw_tx: &[u8], inp_idx: usize, value: u64) {
let wpkh = pk.wpubkey_hash().expect("compressed key"); let wpkh = pk.wpubkey_hash().expect("compressed key");
println!("Script pubkey hash: {:x}", wpkh); println!("Script pubkey hash: {:x}", wpkh);
let spk = ScriptBuf::new_v0_p2wpkh(&wpkh); let spk = ScriptBuf::new_v0_p2wpkh(&wpkh);
let script_code = spk.p2wpkh_script_code().expect("spk is known to be p2wpkh");
let mut cache = sighash::SighashCache::new(&tx); let mut cache = sighash::SighashCache::new(&tx);
let sighash = cache let sighash = cache
.segwit_signature_hash(inp_idx, &script_code, Amount::from_sat(value), sig.hash_ty) .p2wpkh_signature_hash(inp_idx, &spk, Amount::from_sat(value), sig.hash_ty)
.expect("failed to compute sighash"); .expect("failed to compute sighash");
println!("Segwit p2wpkh sighash: {:x}", sighash); println!("Segwit p2wpkh sighash: {:x}", sighash);
let msg = secp256k1::Message::from(sighash); let msg = secp256k1::Message::from(sighash);
@ -124,7 +123,7 @@ fn compute_sighash_p2wsh(raw_tx: &[u8], inp_idx: usize, value: u64) {
assert!((70..=72).contains(&sig_len), "signature length {} out of bounds", sig_len); assert!((70..=72).contains(&sig_len), "signature length {} out of bounds", sig_len);
//here we assume that all sighash_flags are the same. Can they be different? //here we assume that all sighash_flags are the same. Can they be different?
let sighash = cache let sighash = cache
.segwit_signature_hash(inp_idx, witness_script, Amount::from_sat(value), sig.hash_ty) .p2wsh_signature_hash(inp_idx, witness_script, Amount::from_sat(value), sig.hash_ty)
.expect("failed to compute sighash"); .expect("failed to compute sighash");
println!("Segwit p2wsh sighash: {:x} ({})", sighash, sig.hash_ty); println!("Segwit p2wsh sighash: {:x} ({})", sighash, sig.hash_ty);
} }

View File

@ -239,6 +239,9 @@ pub enum Error {
/// Invalid Sighash type. /// Invalid Sighash type.
InvalidSighashType(u32), InvalidSighashType(u32),
/// Script is not a witness program for a p2wpkh output.
NotP2wpkhScript,
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -254,6 +257,7 @@ impl fmt::Display for Error {
PrevoutKind => write!(f, "a single prevout has been provided but all prevouts are needed without `ANYONECANPAY`"), PrevoutKind => write!(f, "a single prevout has been provided but all prevouts are needed without `ANYONECANPAY`"),
WrongAnnex => write!(f, "annex must be at least one byte long and the first bytes must be `0x50`"), WrongAnnex => write!(f, "annex must be at least one byte long and the first bytes must be `0x50`"),
InvalidSighashType(hash_ty) => write!(f, "Invalid taproot signature hash type : {} ", hash_ty), InvalidSighashType(hash_ty) => write!(f, "Invalid taproot signature hash type : {} ", hash_ty),
NotP2wpkhScript => write!(f, "script is not a script pubkey for a p2wpkh output"),
} }
} }
} }
@ -271,7 +275,8 @@ impl std::error::Error for Error {
| PrevoutIndex | PrevoutIndex
| PrevoutKind | PrevoutKind
| WrongAnnex | WrongAnnex
| InvalidSighashType(_) => None, | InvalidSighashType(_)
| NotP2wpkhScript => None,
} }
} }
} }
@ -761,7 +766,25 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
/// Encodes the BIP143 signing data for any flag type into a given object implementing a /// Encodes the BIP143 signing data for any flag type into a given object implementing a
/// [`std::io::Write`] trait. /// [`std::io::Write`] trait.
#[deprecated(since = "0.31.0", note = "use segwit_v0_encode_signing_data_to instead")]
pub fn segwit_encode_signing_data_to<Write: io::Write>( pub fn segwit_encode_signing_data_to<Write: io::Write>(
&mut self,
writer: Write,
input_index: usize,
script_code: &Script,
value: Amount,
sighash_type: EcdsaSighashType,
) -> Result<(), Error> {
self.segwit_v0_encode_signing_data_to(writer, input_index, script_code, value, sighash_type)
}
/// Encodes the BIP143 signing data for any flag type into a given object implementing a
/// [`std::io::Write`] trait.
///
/// `script_code` is dependent on the type of the spend transaction. For p2wpkh use
/// [`Script::p2wpkh_script_code`], for p2wsh just pass in the witness script. (Also see
/// [`Self::p2wpkh_signature_hash`] and [`SighashCache::p2wsh_signature_hash`].)
pub fn segwit_v0_encode_signing_data_to<Write: io::Write>(
&mut self, &mut self,
mut writer: Write, mut writer: Write,
input_index: usize, input_index: usize,
@ -821,6 +844,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
} }
/// Computes the BIP143 sighash for any flag type. /// Computes the BIP143 sighash for any flag type.
#[deprecated(since = "0.31.0", note = "use segwit_v0_signature_hash instead")]
pub fn segwit_signature_hash( pub fn segwit_signature_hash(
&mut self, &mut self,
input_index: usize, input_index: usize,
@ -829,7 +853,7 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
sighash_type: EcdsaSighashType, sighash_type: EcdsaSighashType,
) -> Result<SegwitV0Sighash, Error> { ) -> Result<SegwitV0Sighash, Error> {
let mut enc = SegwitV0Sighash::engine(); let mut enc = SegwitV0Sighash::engine();
self.segwit_encode_signing_data_to( self.segwit_v0_encode_signing_data_to(
&mut enc, &mut enc,
input_index, input_index,
script_code, script_code,
@ -839,6 +863,49 @@ impl<R: Borrow<Transaction>> SighashCache<R> {
Ok(SegwitV0Sighash::from_engine(enc)) Ok(SegwitV0Sighash::from_engine(enc))
} }
/// Computes the BIP143 sighash to spend a p2wpkh transaction for any flag type.
///
/// `script_pubkey` is the `scriptPubkey` (native segwit) of the spend transaction
/// ([`TxOut::script_pubkey`]) or the `redeemScript` (wrapped segwit).
pub fn p2wpkh_signature_hash(
&mut self,
input_index: usize,
script_pubkey: &Script,
value: Amount,
sighash_type: EcdsaSighashType,
) -> Result<SegwitV0Sighash, Error> {
let script_code = script_pubkey.p2wpkh_script_code().ok_or(Error::NotP2wpkhScript)?;
let mut enc = SegwitV0Sighash::engine();
self.segwit_v0_encode_signing_data_to(
&mut enc,
input_index,
&script_code,
value,
sighash_type,
)?;
Ok(SegwitV0Sighash::from_engine(enc))
}
/// Computes the BIP143 sighash to spend a p2wsh transaction for any flag type.
pub fn p2wsh_signature_hash(
&mut self,
input_index: usize,
witness_script: &Script,
value: Amount,
sighash_type: EcdsaSighashType,
) -> Result<SegwitV0Sighash, Error> {
let mut enc = SegwitV0Sighash::engine();
self.segwit_v0_encode_signing_data_to(
&mut enc,
input_index,
witness_script,
value,
sighash_type,
)?;
Ok(SegwitV0Sighash::from_engine(enc))
}
/// Encodes the legacy signing data from which a signature hash for a given input index with a /// Encodes the legacy signing data from which a signature hash for a given input index with a
/// given sighash flag can be computed. /// given sighash flag can be computed.
/// ///
@ -1194,13 +1261,10 @@ mod tests {
use hex::FromHex; use hex::FromHex;
use super::*; use super::*;
use crate::address::Address;
use crate::blockdata::locktime::absolute; use crate::blockdata::locktime::absolute;
use crate::consensus::deserialize; use crate::consensus::deserialize;
use crate::crypto::key::PublicKey;
use crate::crypto::sighash::{LegacySighash, TapSighash}; use crate::crypto::sighash::{LegacySighash, TapSighash};
use crate::internal_macros::hex; use crate::internal_macros::hex;
use crate::network::Network;
use crate::taproot::TapLeafHash; use crate::taproot::TapLeafHash;
extern crate serde_json; extern crate serde_json;
@ -1739,11 +1803,6 @@ mod tests {
} }
} }
fn p2pkh_hex(pk: &str) -> ScriptBuf {
let pk: PublicKey = PublicKey::from_str(pk).unwrap();
Address::p2pkh(&pk, Network::Bitcoin).script_pubkey()
}
#[test] #[test]
fn bip143_p2wpkh() { fn bip143_p2wpkh() {
let tx = deserialize::<Transaction>( let tx = deserialize::<Transaction>(
@ -1755,13 +1814,12 @@ mod tests {
), ),
).unwrap(); ).unwrap();
let witness_script = let spk = ScriptBuf::from_hex("00141d0f172a0ecb48aee1be1f2687d2963ae33f71a1").unwrap();
p2pkh_hex("025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357");
let value = Amount::from_sat(600_000_000); let value = Amount::from_sat(600_000_000);
let mut cache = SighashCache::new(&tx); let mut cache = SighashCache::new(&tx);
assert_eq!( assert_eq!(
cache.segwit_signature_hash(1, &witness_script, value, EcdsaSighashType::All).unwrap(), cache.p2wpkh_signature_hash(1, &spk, value, EcdsaSighashType::All).unwrap(),
"c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670" "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670"
.parse::<SegwitV0Sighash>() .parse::<SegwitV0Sighash>()
.unwrap(), .unwrap(),
@ -1796,13 +1854,13 @@ mod tests {
), ),
).unwrap(); ).unwrap();
let witness_script = let redeem_script =
p2pkh_hex("03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873"); ScriptBuf::from_hex("001479091972186c449eb1ded22b78e40d009bdf0089").unwrap();
let value = Amount::from_sat(1_000_000_000); let value = Amount::from_sat(1_000_000_000);
let mut cache = SighashCache::new(&tx); let mut cache = SighashCache::new(&tx);
assert_eq!( assert_eq!(
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(), cache.p2wpkh_signature_hash(0, &redeem_script, value, EcdsaSighashType::All).unwrap(),
"64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6" "64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6"
.parse::<SegwitV0Sighash>() .parse::<SegwitV0Sighash>()
.unwrap(), .unwrap(),
@ -1827,8 +1885,10 @@ mod tests {
); );
} }
#[test] // Note, if you are looking at the test vectors in BIP-143 and wondering why there is a `cf`
fn bip143_p2wsh_nested_in_p2sh() { // prepended to all the script_code hex it is the length byte, it gets added when we consensus
// encode a script.
fn bip143_p2wsh_nested_in_p2sh_data() -> (Transaction, ScriptBuf, Amount) {
let tx = deserialize::<Transaction>(&hex!( let tx = deserialize::<Transaction>(&hex!(
"010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000\ "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000\
ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f\ ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f\
@ -1845,16 +1905,27 @@ mod tests {
56ae", 56ae",
) )
.unwrap(); .unwrap();
let value = Amount::from_sat(987_654_321);
let value = Amount::from_sat(987_654_321);
(tx, witness_script, value)
}
#[test]
fn bip143_p2wsh_nested_in_p2sh_sighash_type_all() {
let (tx, witness_script, value) = bip143_p2wsh_nested_in_p2sh_data();
let mut cache = SighashCache::new(&tx); let mut cache = SighashCache::new(&tx);
assert_eq!( assert_eq!(
cache.segwit_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(), cache.p2wsh_signature_hash(0, &witness_script, value, EcdsaSighashType::All).unwrap(),
"185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c" "185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c"
.parse::<SegwitV0Sighash>() .parse::<SegwitV0Sighash>()
.unwrap(), .unwrap(),
); );
// We only test the cache intermediate values for `EcdsaSighashType::All` because they are
// not the same as the BIP test vectors for all the rest of the sighash types. These fields
// are private so it does not effect sighash cache usage, we do test against the produced
// sighash for all sighash types.
let cache = cache.segwit_cache(); let cache = cache.segwit_cache();
// Parse hex into Vec because BIP143 test vector displays forwards but our sha256d::Hash displays backwards. // Parse hex into Vec because BIP143 test vector displays forwards but our sha256d::Hash displays backwards.
assert_eq!( assert_eq!(
@ -1873,4 +1944,34 @@ mod tests {
.unwrap()[..], .unwrap()[..],
); );
} }
macro_rules! check_bip143_p2wsh_nested_in_p2sh {
($($test_name:ident, $sighash_type:ident, $sighash:literal);* $(;)?) => {
$(
#[test]
fn $test_name() {
use EcdsaSighashType::*;
let (tx, witness_script, value) = bip143_p2wsh_nested_in_p2sh_data();
let mut cache = SighashCache::new(&tx);
assert_eq!(
cache
.p2wsh_signature_hash(0, &witness_script, value, $sighash_type)
.unwrap(),
$sighash
.parse::<SegwitV0Sighash>()
.unwrap(),
);
}
)*
}
}
check_bip143_p2wsh_nested_in_p2sh! {
// EcdsaSighashType::All tested above.
bip143_p2wsh_nested_in_p2sh_sighash_none, None, "e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36";
bip143_p2wsh_nested_in_p2sh_sighash_single, Single, "1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea";
bip143_p2wsh_nested_in_p2sh_sighash_all_plus_anyonecanpay, AllPlusAnyoneCanPay, "2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e";
bip143_p2wsh_nested_in_p2sh_sighash_none_plus_anyonecanpay, NonePlusAnyoneCanPay, "781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a";
bip143_p2wsh_nested_in_p2sh_sighash_single_plus_anyonecanpay, SinglePlusAnyoneCanPay, "511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b";
}
} }

View File

@ -335,27 +335,20 @@ impl Psbt {
Ok((Message::from(sighash), hash_ty)) Ok((Message::from(sighash), hash_ty))
} }
Wpkh => { Wpkh => {
let script_code = spk.p2wpkh_script_code().ok_or(SignError::NotWpkh)?; let sighash = cache.p2wpkh_signature_hash(input_index, spk, utxo.value, hash_ty)?;
let sighash =
cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)?;
Ok((Message::from(sighash), hash_ty)) Ok((Message::from(sighash), hash_ty))
} }
ShWpkh => { ShWpkh => {
let script_code = input let redeem_script = input.redeem_script.as_ref().expect("checked above");
.redeem_script
.as_ref()
.expect("checked above")
.p2wpkh_script_code()
.ok_or(SignError::NotWpkh)?;
let sighash = let sighash =
cache.segwit_signature_hash(input_index, &script_code, utxo.value, hash_ty)?; cache.p2wpkh_signature_hash(input_index, redeem_script, utxo.value, hash_ty)?;
Ok((Message::from(sighash), hash_ty)) Ok((Message::from(sighash), hash_ty))
} }
Wsh | ShWsh => { Wsh | ShWsh => {
let script_code = let witness_script =
input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?;
let sighash = let sighash =
cache.segwit_signature_hash(input_index, script_code, utxo.value, hash_ty)?; cache.p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty)?;
Ok((Message::from(sighash), hash_ty)) Ok((Message::from(sighash), hash_ty))
} }
Tr => { Tr => {