Skip to content

Commit

Permalink
blockchain: Add CalcMerkleRoot
Browse files Browse the repository at this point in the history
  • Loading branch information
cfromknecht authored and kcalvinalvin committed May 9, 2023
1 parent 8a58730 commit c1dc927
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 5 deletions.
57 changes: 56 additions & 1 deletion blockchain/merkle.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.
//
// The above stored as a linear array is as follows:
//
// [h1 h2 h3 h4 h12 h34 root]
// [h1 h2 h3 h4 h12 h34 root]
//
// As the above shows, the merkle root is always the last element in the array.
//
Expand Down Expand Up @@ -154,6 +154,61 @@ func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash
return merkles
}

// CalcMerkleRoot computes the merkle root over a set of hashed leaves. The
// interior nodes are computed opportunistically as the leaves are added to the
// abstract tree to reduce the total number of allocations. Throughout the
// computation, this computation only requires storing O(log n) interior
// nodes.
//
// This method differs from BuildMerkleTreeStore in that the interior nodes are
// discarded instead of being returned along with the root. CalcMerkleRoot is
// slightly slower than BuildMerkleTreeStore, but requires significantly less
// memory and fewer allocations.
//
// A merkle tree is a tree in which every non-leaf node is the hash of its
// children nodes. A diagram depicting how this works for bitcoin transactions
// where h(x) is a double sha256 follows:
//
// root = h1234 = h(h12 + h34)
// / \
// h12 = h(h1 + h2) h34 = h(h3 + h4)
// / \ / \
// h1 = h(tx1) h2 = h(tx2) h3 = h(tx3) h4 = h(tx4)
//
// The additional bool parameter indicates if we are generating the merkle tree
// using witness transaction id's rather than regular transaction id's. This
// also presents an additional case wherein the wtxid of the coinbase transaction
// is the zeroHash.
func CalcMerkleRoot(transactions []*btcutil.Tx, witness bool) chainhash.Hash {
// Iteratively push the leaves onto the rolling merkle tree.
merkle := NewStump(len(transactions))
for i, tx := range transactions {
// If we're computing a witness merkle root, instead of the
// regular txid, we use the modified wtxid which includes a
// transaction's witness data within the digest. Additionally,
// the coinbase's wtxid is all zeroes.
switch {
case witness && i == 0:
var zeroHash chainhash.Hash
merkle.add(zeroHash)
case witness:
wSha := tx.MsgTx().WitnessHash()
merkle.add(wSha)
default:
hash := tx.Hash()
merkle.add(*hash)
}
}

if len(transactions) != 1 && len(transactions)%2 != 0 {
tx := transactions[len(transactions)-1]
hash := tx.Hash()
merkle.add(*hash)
}

return merkle.Roots[0]
}

// ExtractWitnessCommitment attempts to locate, and return the witness
// commitment for a block. The witness commitment is of the form:
// SHA256(witness root || witness nonce). The function additionally returns a
Expand Down
16 changes: 12 additions & 4 deletions blockchain/merkle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@ import (
// TestMerkle tests the BuildMerkleTreeStore API.
func TestMerkle(t *testing.T) {
block := btcutil.NewBlock(&Block100000)
merkles := BuildMerkleTreeStore(block.Transactions(), false)
calculatedMerkleRoot := merkles[len(merkles)-1]
calcMerkleRoot := CalcMerkleRoot(block.Transactions(), false)
merkleStoreTree := BuildMerkleTreeStore(block.Transactions(), false)
merkleStoreRoot := merkleStoreTree[len(merkleStoreTree)-1]

if calcMerkleRoot != *merkleStoreRoot {
t.Fatalf("BuildMerkleTreeStore root does not match "+
"CalcMerkleRoot, store: %v calc: %v", merkleStoreRoot,
calcMerkleRoot)
}

wantMerkle := &Block100000.Header.MerkleRoot
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
if !wantMerkle.IsEqual(&calcMerkleRoot) {
t.Errorf("BuildMerkleTreeStore: merkle root mismatch - "+
"got %v, want %v", calculatedMerkleRoot, wantMerkle)
"got %v, want %v", calcMerkleRoot, wantMerkle)
}
}

Expand Down

0 comments on commit c1dc927

Please sign in to comment.