Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Taproot BIP341 signature message and create a unified sighash cache for legacy, segwit and taproot inputs #628

Merged
merged 16 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ mod tests {

use hash_types::*;
use SigHashType;
use util::sighash::SigHashCache;

#[test]
fn test_outpoint() {
Expand Down Expand Up @@ -1004,7 +1005,15 @@ mod tests {
raw_expected.reverse();
let expected_result = SigHash::from_slice(&raw_expected[..]).unwrap();

let actual_result = tx.signature_hash(input_index, &script, hash_type as u32);
let actual_result = if raw_expected[0] % 2 == 0 {
// tx.signature_hash and cache.legacy_signature_hash are the same, this if helps to test
// both the codepaths without repeating the test code
tx.signature_hash(input_index, &script, hash_type as u32)
} else {
let cache = SigHashCache::new(&tx);
cache.legacy_signature_hash(input_index, &script, hash_type as u32).unwrap()
};

assert_eq!(actual_result, expected_result);
}

Expand Down
16 changes: 14 additions & 2 deletions src/consensus/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use prelude::*;
use core::{fmt, mem, u32, convert::From};
#[cfg(feature = "std")] use std::error;

use hashes::{sha256d, Hash};
use hashes::{sha256d, Hash, sha256};
use hash_types::{BlockHash, FilterHash, TxMerkleNode, FilterHeader};

use io::{self, Cursor, Read};
Expand Down Expand Up @@ -600,7 +600,7 @@ impl_vec!(u64);
#[cfg(feature = "std")] impl_vec!((u32, Address));
#[cfg(feature = "std")] impl_vec!(AddrV2Message);

fn consensus_encode_with_size<S: io::Write>(data: &[u8], mut s: S) -> Result<usize, io::Error> {
pub(crate) fn consensus_encode_with_size<S: io::Write>(data: &[u8], mut s: S) -> Result<usize, io::Error> {
let vi_len = VarInt(data.len() as u64).consensus_encode(&mut s)?;
s.emit_slice(&data)?;
Ok(vi_len + data.len())
Expand Down Expand Up @@ -756,6 +756,18 @@ impl Decodable for sha256d::Hash {
}
}

impl Encodable for sha256::Hash {
fn consensus_encode<S: io::Write>(&self, s: S) -> Result<usize, io::Error> {
self.into_inner().consensus_encode(s)
}
}

impl Decodable for sha256::Hash {
fn consensus_decode<D: io::Read>(d: D) -> Result<Self, Error> {
Ok(Self::from_inner(<<Self as Hash>::Inner>::consensus_decode(d)?))
}
}

// Tests
#[cfg(test)]
mod tests {
Expand Down
118 changes: 18 additions & 100 deletions src/util/bip143.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
//! signatures, which are placed in the scriptSig.
//!

use hashes::{Hash, sha256d};
use hashes::Hash;
use hash_types::SigHash;
use blockdata::script::Script;
use blockdata::transaction::{Transaction, TxIn, SigHashType};
Expand All @@ -29,11 +29,12 @@ use prelude::*;

use io;
use core::ops::{Deref, DerefMut};
use util::sighash;

/// Parts of a sighash which are common across inputs or signatures, and which are
/// sufficient (in conjunction with a private key) to sign the transaction
#[derive(Clone, PartialEq, Eq, Debug)]
#[deprecated(since="0.24.0", note="please use `SigHashCache` instead")]
#[deprecated(since="0.24.0", note="please use [sighash::SigHashCache] instead")]
pub struct SighashComponents {
tx_version: i32,
tx_locktime: u32,
Expand Down Expand Up @@ -107,121 +108,34 @@ impl SighashComponents {
}

/// A replacement for SigHashComponents which supports all sighash modes
#[deprecated(since="0.27.0", note="please use [sighash::SigHashCache] instead")]
pub struct SigHashCache<R: Deref<Target=Transaction>> {
/// Access to transaction required for various introspection
tx: R,
/// Hash of all the previous outputs, computed as required
hash_prevouts: Option<sha256d::Hash>,
/// Hash of all the input sequence nos, computed as required
hash_sequence: Option<sha256d::Hash>,
/// Hash of all the outputs in this transaction, computed as required
hash_outputs: Option<sha256d::Hash>,
cache: sighash::SigHashCache<R>,
}

#[allow(deprecated)]
impl<R: Deref<Target=Transaction>> SigHashCache<R> {
/// Compute the sighash components from an unsigned transaction and auxiliary
/// in a lazy manner when required.
/// For the generated sighashes to be valid, no fields in the transaction may change except for
/// script_sig and witnesses.
pub fn new(tx: R) -> Self {
SigHashCache {
tx: tx,
hash_prevouts: None,
hash_sequence: None,
hash_outputs: None,
}
}

/// Calculate hash for prevouts
pub fn hash_prevouts(&mut self) -> sha256d::Hash {
let hash_prevout = &mut self.hash_prevouts;
let input = &self.tx.input;
*hash_prevout.get_or_insert_with(|| {
let mut enc = sha256d::Hash::engine();
for txin in input {
txin.previous_output.consensus_encode(&mut enc).unwrap();
}
sha256d::Hash::from_engine(enc)
})
}

/// Calculate hash for input sequence values
pub fn hash_sequence(&mut self) -> sha256d::Hash {
let hash_sequence = &mut self.hash_sequence;
let input = &self.tx.input;
*hash_sequence.get_or_insert_with(|| {
let mut enc = sha256d::Hash::engine();
for txin in input {
txin.sequence.consensus_encode(&mut enc).unwrap();
}
sha256d::Hash::from_engine(enc)
})
}

/// Calculate hash for outputs
pub fn hash_outputs(&mut self) -> sha256d::Hash {
let hash_output = &mut self.hash_outputs;
let output = &self.tx.output;
*hash_output.get_or_insert_with(|| {
let mut enc = sha256d::Hash::engine();
for txout in output {
txout.consensus_encode(&mut enc).unwrap();
}
sha256d::Hash::from_engine(enc)
})
Self { cache: sighash::SigHashCache::new(tx) }
}

/// Encode the BIP143 signing data for any flag type into a given object implementing a
/// std::io::Write trait.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit (please don't change): after #603 this is not entirely correct anymore

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c704ee7 (Since other changes were needed)

pub fn encode_signing_data_to<Write: io::Write>(
&mut self,
mut writer: Write,
writer: Write,
input_index: usize,
script_code: &Script,
value: u64,
sighash_type: SigHashType,
) -> Result<(), encode::Error> {
let zero_hash = sha256d::Hash::default();

let (sighash, anyone_can_pay) = sighash_type.split_anyonecanpay_flag();

self.tx.version.consensus_encode(&mut writer)?;

if !anyone_can_pay {
self.hash_prevouts().consensus_encode(&mut writer)?;
} else {
zero_hash.consensus_encode(&mut writer)?;
}

if !anyone_can_pay && sighash != SigHashType::Single && sighash != SigHashType::None {
self.hash_sequence().consensus_encode(&mut writer)?;
} else {
zero_hash.consensus_encode(&mut writer)?;
}

{
let txin = &self.tx.input[input_index];

txin
.previous_output
.consensus_encode(&mut writer)?;
script_code.consensus_encode(&mut writer)?;
value.consensus_encode(&mut writer)?;
txin.sequence.consensus_encode(&mut writer)?;
}

if sighash != SigHashType::Single && sighash != SigHashType::None {
self.hash_outputs().consensus_encode(&mut writer)?;
} else if sighash == SigHashType::Single && input_index < self.tx.output.len() {
let mut single_enc = SigHash::engine();
self.tx.output[input_index].consensus_encode(&mut single_enc)?;
SigHash::from_engine(single_enc).consensus_encode(&mut writer)?;
} else {
zero_hash.consensus_encode(&mut writer)?;
}

self.tx.lock_time.consensus_encode(&mut writer)?;
sighash_type.as_u32().consensus_encode(&mut writer)?;
self.cache
.segwit_encode_signing_data_to(writer, input_index, script_code, value, sighash_type.into())
.expect("input_index greater than tx input len");
Ok(())
}

Expand All @@ -241,11 +155,15 @@ impl<R: Deref<Target=Transaction>> SigHashCache<R> {
}
}

#[allow(deprecated)]
impl<R: DerefMut<Target=Transaction>> SigHashCache<R> {
/// When the SigHashCache is initialized with a mutable reference to a transaction instead of a
/// regular reference, this method is available to allow modification to the witnesses.
///
/// This allows in-line signing such as
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: comment belongs above line 163

/// panics if `input_index` is out of bounds with respect of the number of inputs
///
/// ```
/// use bitcoin::blockdata::transaction::{Transaction, SigHashType};
/// use bitcoin::util::bip143::SigHashCache;
Expand All @@ -263,13 +181,14 @@ impl<R: DerefMut<Target=Transaction>> SigHashCache<R> {
/// }
/// ```
pub fn access_witness(&mut self, input_index: usize) -> &mut Vec<Vec<u8>> {
&mut self.tx.input[input_index].witness
self.cache.witness_mut(input_index).unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe you can just define an empty vec in the Self type that gets returned if input_index is out of bound. It's better than the unchecked access, and perhaps better than adding an unwrap / panic.

definitely a nit though, as this is deprecated code anyways.

}
}

#[cfg(test)]
#[allow(deprecated)]
mod tests {
use std::str::FromStr;
RCasatta marked this conversation as resolved.
Show resolved Hide resolved
use hash_types::SigHash;
use blockdata::script::Script;
use blockdata::transaction::Transaction;
Expand All @@ -282,8 +201,7 @@ mod tests {
use super::*;

fn p2pkh_hex(pk: &str) -> Script {
let pk = Vec::from_hex(pk).unwrap();
let pk = PublicKey::from_slice(pk.as_slice()).unwrap();
let pk: PublicKey = PublicKey::from_str(pk).unwrap();
let witness_script = Address::p2pkh(&pk, Network::Bitcoin).script_pubkey();
witness_script
}
Expand Down
1 change: 1 addition & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod psbt;
pub mod taproot;
pub mod uint;
pub mod bip158;
pub mod sighash;

pub(crate) mod endian;

Expand Down