Skip to content

Commit

Permalink
Implement Witness commitment check for Block. Remove MerkleRoot imple…
Browse files Browse the repository at this point in the history
…mentations for types implementing BitcoinHash as

it is misleading. MerkleRoot is defined instead for a Block.
  • Loading branch information
tamasblummer committed Feb 1, 2019
1 parent 51aba8b commit d8c93d9
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 20 deletions.
71 changes: 67 additions & 4 deletions src/blockdata/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ use bitcoin_hashes::{sha256d, Hash};

use util;
use util::Error::{SpvBadTarget, SpvBadProofOfWork};
use util::hash::BitcoinHash;
use util::hash::{BitcoinHash, MerkleRoot, bitcoin_merkle_root};
use util::uint::Uint256;
use consensus::encode::VarInt;
use consensus::encode::{VarInt, Encodable};
use network::constants::Network;
use blockdata::transaction::Transaction;
use blockdata::constants::max_target;
use bitcoin_hashes::HashEngine;

/// A block header, which contains all the block's information except
/// the actual transactions
Expand Down Expand Up @@ -60,6 +61,61 @@ pub struct Block {
pub txdata: Vec<Transaction>
}

impl Block {
/// check if merkle root of header matches merkle root of the transaction list
pub fn check_merkle_root (&self) -> bool {
self.header.merkle_root == self.merkle_root()
}

/// check if witness commitment in coinbase is matching the transaction list
pub fn check_witness_commitment(&self) -> bool {

// witness commitment is optional if there are no transactions using SegWit in the block
if self.txdata.iter().all(|t| t.input.iter().all(|i| i.witness.is_empty())) {
return true;
}
if self.txdata.len() > 0 {
let coinbase = &self.txdata[0];
if coinbase.is_coin_base() {
// commitment is in the last output that starts with below magic
if let Some(pos) = coinbase.output.iter()
.rposition(|o| {
o.script_pubkey.len () >= 38 &&
o.script_pubkey[0..6] == [0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed] }) {
let commitment = sha256d::Hash::from_slice(&coinbase.output[pos].script_pubkey.as_bytes()[6..38]).unwrap();
// witness reserved value is in coinbase input witness
if coinbase.input[0].witness.len() == 1 && coinbase.input[0].witness[0].len() == 32 {
let witness_root = self.witness_root();
return commitment == Self::compute_witness_commitment(&witness_root, coinbase.input[0].witness[0].as_slice())
}
}
}
}
false
}

/// compute witness commitment for the transaction list
pub fn compute_witness_commitment (witness_root: &sha256d::Hash, witness_reserved_value: &[u8]) -> sha256d::Hash {
let mut encoder = sha256d::Hash::engine();
witness_root.consensus_encode(&mut encoder).unwrap();
encoder.input(witness_reserved_value);
sha256d::Hash::from_engine(encoder)
}

/// Merkle root of transactions hashed for witness
pub fn witness_root(&self) -> sha256d::Hash {
let mut txhashes = vec!(sha256d::Hash::default());
txhashes.extend(self.txdata.iter().skip(1).map(|t|t.bitcoin_hash()));
bitcoin_merkle_root(txhashes)
}
}

impl MerkleRoot for Block {
fn merkle_root(&self) -> sha256d::Hash {
bitcoin_merkle_root(self.txdata.iter().map(|obj| obj.txid()).collect())
}
}

/// A block header with txcount attached, which is given in the `headers`
/// network message.
#[derive(PartialEq, Eq, Clone, Debug)]
Expand Down Expand Up @@ -170,6 +226,7 @@ mod tests {

use blockdata::block::{Block, BlockHeader};
use consensus::encode::{deserialize, serialize};
use util::hash::MerkleRoot;

#[test]
fn block_test() {
Expand All @@ -187,13 +244,16 @@ mod tests {
let real_decode = decode.unwrap();
assert_eq!(real_decode.header.version, 1);
assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash);
// [test] TODO: actually compute the merkle root
assert_eq!(real_decode.header.merkle_root, real_decode.merkle_root());
assert_eq!(serialize(&real_decode.header.merkle_root), merkle);
assert_eq!(real_decode.header.time, 1231965655);
assert_eq!(real_decode.header.bits, 486604799);
assert_eq!(real_decode.header.nonce, 2067413810);
// [test] TODO: check the transaction data


// should be also ok for a non-witness block as commitment is optional in that case
assert!(real_decode.check_witness_commitment());

assert_eq!(serialize(&real_decode), some_block);
}

Expand All @@ -212,11 +272,14 @@ mod tests {
assert_eq!(real_decode.header.version, 0x20000000); // VERSIONBITS but no bits set
assert_eq!(serialize(&real_decode.header.prev_blockhash), prevhash);
assert_eq!(serialize(&real_decode.header.merkle_root), merkle);
assert_eq!(real_decode.header.merkle_root, real_decode.merkle_root());
assert_eq!(real_decode.header.time, 1472004949);
assert_eq!(real_decode.header.bits, 0x1a06d450);
assert_eq!(real_decode.header.nonce, 1879759182);
// [test] TODO: check the transaction data

assert!(real_decode.check_witness_commitment());

assert_eq!(serialize(&real_decode), segwit_block);
}

Expand Down
7 changes: 3 additions & 4 deletions src/blockdata/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ use blockdata::transaction::{OutPoint, Transaction, TxOut, TxIn};
use blockdata::block::{Block, BlockHeader};
use network::constants::Network;
use util::misc::hex_bytes;
use util::hash::MerkleRoot;
use util::uint::Uint256;

/// The maximum allowable sequence number
Expand Down Expand Up @@ -98,7 +97,7 @@ pub fn genesis_block(network: Network) -> Block {
header: BlockHeader {
version: 1,
prev_blockhash: Default::default(),
merkle_root: txdata.merkle_root(),
merkle_root: txdata[0].txid(),
time: 1231006505,
bits: 0x1d00ffff,
nonce: 2083236893
Expand All @@ -112,7 +111,7 @@ pub fn genesis_block(network: Network) -> Block {
header: BlockHeader {
version: 1,
prev_blockhash: Default::default(),
merkle_root: txdata.merkle_root(),
merkle_root: txdata[0].txid(),
time: 1296688602,
bits: 0x1d00ffff,
nonce: 414098458
Expand All @@ -126,7 +125,7 @@ pub fn genesis_block(network: Network) -> Block {
header: BlockHeader {
version: 1,
prev_blockhash: Default::default(),
merkle_root: txdata.merkle_root(),
merkle_root: txdata[0].txid(),
time: 1296688602,
bits: 0x207fffff,
nonce: 2
Expand Down
12 changes: 0 additions & 12 deletions src/util/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,6 @@ pub fn bitcoin_merkle_root(data: Vec<sha256d::Hash>) -> sha256d::Hash {
bitcoin_merkle_root(next)
}

impl<'a, T: BitcoinHash> MerkleRoot for &'a [T] {
fn merkle_root(&self) -> sha256d::Hash {
bitcoin_merkle_root(self.iter().map(|obj| obj.bitcoin_hash()).collect())
}
}

impl <T: BitcoinHash> MerkleRoot for Vec<T> {
fn merkle_root(&self) -> sha256d::Hash {
(&self[..]).merkle_root()
}
}

/// Objects which are referred to by hash
pub trait BitcoinHash {
/// Produces a Sha256dHash which can be used to refer to the object
Expand Down

0 comments on commit d8c93d9

Please sign in to comment.