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: ACK4300cf2210
apoelstra: ACK4300cf2210
Tree-SHA512: 723fc302954514da0fa57a3890b9f62e9d8d1b25289b8db00611d8bc34c5000b9e54943f57b8e94befcaf72633ac078b2ff66a1da0c5bb483cfaa584e3cb6014
This commit is contained in:
commit
5bf2117dc4
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
Loading…
Reference in New Issue