Skip to content

Commit

Permalink
Merge #628: Adds Taproot BIP341 signature message and create a unifie…
Browse files Browse the repository at this point in the history
…d sighash cache for legacy, segwit and taproot inputs

c704ee7 [docs-only] Use backtick in addition to square parentheses for types references, clarify legacy, non_exhaustive comment, remove std:: (Riccardo Casatta)
f223be6 Rename access_witness to witness_mut and return Option (Riccardo Casatta)
c9bc0b9 [fmt-only] autoformatting with `rustfmt src/util/sighash.rs` (Riccardo Casatta)
0777491 Use get_or_insert_with in segwit_cache (Martin Habovstiak)
497dbfb Use get_or_insert_with in common_cache() (Martin Habovstiak)
ca80a5a Use get_or_insert_with in taproot_cache (Martin Habovstiak)
6e06a32 Wrap ErrorKind in Io enum variant, fix doc comment for the IO variant (Riccardo Casatta)
1a2b54f introduce constant KEY_VERSION_0 (Riccardo Casatta)
417cfe3 Derive common traits for structs and enum, make internal struct not pub (Riccardo Casatta)
55ce3dd Fix validation error if SINGLE with missing corresponding output, remove check_index and check with get().ok_or(), more details in errors (Riccardo Casatta)
2b3b22f impl Encodable for Annex to avoid allocation (Riccardo Casatta)
1a7afed Add Reserved variant to SigHashType for future use (ie SIGHASH_ANYPREVOUT) (Riccardo Casatta)
53d0e17 Deprecate bip143::SigHashCache in favor of sighash::SigHashCache (Riccardo Casatta)
15e3caf [test] Test also sighash legacy API with legacy tests (Riccardo Casatta)
24acfe3 Implement Bip341 signature hash, create unified SigHashCache for taproot, segwit and legacy inputs (Riccardo Casatta)
683b9c1 add [En|De]codable trait for sha256::Hash (Riccardo Casatta)

Pull request description:

  Adds https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki message signature algorithm

  The base is taken from `bip143::SigHashCache`, some code results duplicated but I think it's more clear to keep things separated

  Would mark some bullet point on #503

  Test vectors are taken by running https://github.com/bitcoin/bitcoin/blob/d1e4c56309aeb73772e3a9d779a9c157024c9e1e/test/functional/feature_taproot.py with a modified `TaprootSignatureHash` function to print intermediate values that I cannot found in the bip341 [test vector json](https://raw.githubusercontent.com/bitcoin-core/qa-assets/main/unit_test_data/script_assets_test.json)

  UPDATE: Latest version includes the suggestion from @sanket1729 to create a unified tool for signature message hash for legacy, segwit, and taproot inputs. In particular, makes sense for mixed segwit v0 and taproot v1 inputs because cached values could be shared

ACKs for top commit:
  sanket1729:
    ACK c704ee7. Reviewed the diff from a37de1a which I previously ACKed
  dr-orlovsky:
    utACK c704ee7 by diffing it to 6e06a32 having my ACK before.
  apoelstra:
    ACK c704ee7

Tree-SHA512: 35530995fe9d078acd0178cfca654ca980109f4502c91d578c1a0d5c6cafacab7db1ffd6216288eac99f6a763776cbc0298cfbdff00b5a83e98ec4b15aa764e8
  • Loading branch information
apoelstra committed Sep 15, 2021
2 parents 65d8bda + c704ee7 commit b6b60fc
Show file tree
Hide file tree
Showing 5 changed files with 952 additions and 103 deletions.
11 changes: 10 additions & 1 deletion src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,7 @@ mod tests {

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

#[test]
fn test_outpoint() {
Expand Down Expand Up @@ -1059,7 +1060,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 @@ -601,7 +601,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 @@ -757,6 +757,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.
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
///
/// 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()
}
}

#[cfg(test)]
#[allow(deprecated)]
mod tests {
use std::str::FromStr;
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

0 comments on commit b6b60fc

Please sign in to comment.