Implement Witness commitment check for Block. Remove MerkleRoot implementations for types implementing BitcoinHash as

it is misleading. MerkleRoot is defined instead for a Block.
This commit is contained in:
Tamas Blummer 2019-01-20 18:02:47 +01:00
parent 51aba8bb21
commit d8c93d9935
3 changed files with 70 additions and 20 deletions

View File

@ -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
@ -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)]
@ -170,6 +226,7 @@ mod tests {
use blockdata::block::{Block, BlockHeader};
use consensus::encode::{deserialize, serialize};
use util::hash::MerkleRoot;
#[test]
fn block_test() {
@ -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);
}
@ -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);
}

View File

@ -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
@ -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
@ -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
@ -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

View File

@ -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