From 260001ccaf65e524ff2edc405759deea74ff452d Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 13 Apr 2022 14:51:26 -0400 Subject: [PATCH 1/5] Make in-memory Merkle tree immutable --- internal/merkle/inmemory/tree.go | 36 +++++++++++++-------------- internal/merkle/inmemory/tree_test.go | 28 ++++++++++----------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/internal/merkle/inmemory/tree.go b/internal/merkle/inmemory/tree.go index 090b4bb57e..d50e7f1786 100644 --- a/internal/merkle/inmemory/tree.go +++ b/internal/merkle/inmemory/tree.go @@ -29,22 +29,21 @@ type Tree struct { } // New returns a new empty Merkle tree. -func New(hasher merkle.LogHasher) *Tree { - return &Tree{hasher: hasher} +func New(hasher merkle.LogHasher) Tree { + return Tree{hasher: hasher} } // AppendData adds the leaf hash of the given entry to the end of the tree. -func (t *Tree) AppendData(data []byte) { - t.Append(t.hasher.HashLeaf(data)) +func (t Tree) AppendData(data []byte) Tree { + return t.Append(t.hasher.HashLeaf(data)) } // Append adds the given leaf hash to the end of the tree. -func (t *Tree) Append(hash []byte) { - level := 0 - for ; (t.size>>level)&1 == 1; level++ { - row := append(t.hashes[level], hash) - hash = t.hasher.HashChildren(row[len(row)-2], hash) - t.hashes[level] = row +func (t Tree) Append(hash []byte) Tree { + level, width := 0, t.size + for ; width&1 == 1; width, level = width/2, level+1 { + t.hashes[level] = append(t.hashes[level][:width], hash) + hash = t.hasher.HashChildren(t.hashes[level][width-1], hash) } if level > len(t.hashes) { panic("gap in tree appends") @@ -52,29 +51,30 @@ func (t *Tree) Append(hash []byte) { t.hashes = append(t.hashes, nil) } - t.hashes[level] = append(t.hashes[level], hash) + t.hashes[level] = append(t.hashes[level][:width], hash) t.size++ + return t } // Size returns the current number of leaves in the tree. -func (t *Tree) Size() uint64 { +func (t Tree) Size() uint64 { return t.size } // LeafHash returns the leaf hash at the given index. // Requires 0 <= index < Size(), otherwise panics. -func (t *Tree) LeafHash(index uint64) []byte { +func (t Tree) LeafHash(index uint64) []byte { return t.hashes[0][index] } // Hash returns the current root hash of the tree. -func (t *Tree) Hash() []byte { +func (t Tree) Hash() []byte { return t.HashAt(t.size) } // HashAt returns the root hash at the given size. // Requires 0 <= size <= Size(), otherwise panics. -func (t *Tree) HashAt(size uint64) []byte { +func (t Tree) HashAt(size uint64) []byte { if size == 0 { return t.hasher.EmptyRoot() } @@ -90,7 +90,7 @@ func (t *Tree) HashAt(size uint64) []byte { // InclusionProof returns the inclusion proof for the given leaf index in the // tree of the given size. Requires 0 <= index < size <= Size(), otherwise may // panic. -func (t *Tree) InclusionProof(index, size uint64) ([][]byte, error) { +func (t Tree) InclusionProof(index, size uint64) ([][]byte, error) { nodes, err := proof.Inclusion(index, size) if err != nil { return nil, err @@ -100,7 +100,7 @@ func (t *Tree) InclusionProof(index, size uint64) ([][]byte, error) { // ConsistencyProof returns the consistency proof between the two given tree // sizes. Requires 0 <= size1 <= size2 <= Size(), otherwise may panic. -func (t *Tree) ConsistencyProof(size1, size2 uint64) ([][]byte, error) { +func (t Tree) ConsistencyProof(size1, size2 uint64) ([][]byte, error) { nodes, err := proof.Consistency(size1, size2) if err != nil { return nil, err @@ -108,7 +108,7 @@ func (t *Tree) ConsistencyProof(size1, size2 uint64) ([][]byte, error) { return nodes.Rehash(t.getNodes(nodes.IDs), t.hasher.HashChildren) } -func (t *Tree) getNodes(ids []compact.NodeID) [][]byte { +func (t Tree) getNodes(ids []compact.NodeID) [][]byte { hashes := make([][]byte, len(ids)) for i, id := range ids { hashes[i] = t.hashes[id.Level][id.Index] diff --git a/internal/merkle/inmemory/tree_test.go b/internal/merkle/inmemory/tree_test.go index 262b33d537..0f9df1baec 100644 --- a/internal/merkle/inmemory/tree_test.go +++ b/internal/merkle/inmemory/tree_test.go @@ -133,7 +133,7 @@ func decodeHexStringOrPanic(hs string) []byte { return data } -func makeEmptyTree() *Tree { +func makeEmptyTree() Tree { return New(rfc6962.DefaultHasher) } @@ -148,7 +148,7 @@ func makeFuzzTestData() [][]byte { return data } -func getRootAsString(mt *Tree, size uint64) string { +func getRootAsString(mt Tree, size uint64) string { if size > mt.Size() { // Doesn't matter what this is as long as it could never be a valid // hex encoding of a hash @@ -328,7 +328,7 @@ func TestEmptyTreeHash(t *testing.T) { } } -func validateTree(mt *Tree, l uint64, t *testing.T) { +func validateTree(mt Tree, l uint64, t *testing.T) { if got, want := mt.Size(), l+1; got != want { t.Errorf("Incorrect leaf count %d, expecting %d", got, want) } @@ -359,7 +359,7 @@ func TestBuildTreeBuildOneAtATime(t *testing.T) { // Add to the tree, checking after each leaf for l := uint64(0); l < 8; l++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) validateTree(mt, l, t) } } @@ -368,14 +368,14 @@ func TestBuildTreeBuildAllAtOnce(t *testing.T) { mt := makeEmptyTree() for l := 0; l < 3; l++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) } // Check the intermediate state validateTree(mt, 2, t) for l := 3; l < 8; l++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) } // Check the final state @@ -387,7 +387,7 @@ func TestBuildTreeBuildTwoChunks(t *testing.T) { // Add to the tree, checking after each leaf for l := 0; l < 8; l++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) } validateTree(mt, 7, t) @@ -444,7 +444,7 @@ func TestMerkleTreeRootFuzz(t *testing.T) { mt := makeEmptyTree() for l := int64(0); l < treeSize; l++ { - mt.AppendData(data[l]) + mt = mt.AppendData(data[l]) } // Since the tree is evaluated lazily, the order of queries is significant. @@ -475,7 +475,7 @@ func TestMerkleTreePathFuzz(t *testing.T) { mt := makeEmptyTree() for l := int64(0); l < treeSize; l++ { - mt.AppendData(data[l]) + mt = mt.AppendData(data[l]) } // Since the tree is evaluated lazily, the order of queries is significant. @@ -518,7 +518,7 @@ func TestMerkleTreeConsistencyFuzz(t *testing.T) { mt := makeEmptyTree() for l := int64(0); l < treeSize; l++ { - mt.AppendData(data[l]) + mt = mt.AppendData(data[l]) } // Since the tree is evaluated lazily, the order of queries is significant. @@ -559,7 +559,7 @@ func TestMerkleTreePathBuildOnce(t *testing.T) { mt := makeEmptyTree() for i := 0; i < 8; i++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) } if size := mt.Size(); size != 8 { @@ -602,13 +602,13 @@ func TestMerkleTreePathBuildIncrementally(t *testing.T) { mt := makeEmptyTree() for i := 0; i < 8; i++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) } mt2 := makeEmptyTree() for i := uint64(0); i < 8; i++ { - mt2.AppendData(decodeHexStringOrPanic(leafInputs[i])) + mt2 = mt2.AppendData(decodeHexStringOrPanic(leafInputs[i])) for j := uint64(0); j < i+1; j++ { p1, err := mt.InclusionProof(j, i+1) @@ -645,7 +645,7 @@ func TestProofConsistencyTestVectors(t *testing.T) { mt := makeEmptyTree() for i := 0; i < 8; i++ { - mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) + mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) } if size := mt.Size(); size != 8 { From d6a978d9488516fca7810bc0bd14848032042d48 Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 13 Apr 2022 15:04:28 -0400 Subject: [PATCH 2/5] Make Append* methods variadic --- internal/merkle/inmemory/tree.go | 21 ++++++++---- internal/merkle/inmemory/tree_test.go | 48 +++++++++------------------ 2 files changed, 30 insertions(+), 39 deletions(-) diff --git a/internal/merkle/inmemory/tree.go b/internal/merkle/inmemory/tree.go index d50e7f1786..ee1a415d3d 100644 --- a/internal/merkle/inmemory/tree.go +++ b/internal/merkle/inmemory/tree.go @@ -33,13 +33,23 @@ func New(hasher merkle.LogHasher) Tree { return Tree{hasher: hasher} } -// AppendData adds the leaf hash of the given entry to the end of the tree. -func (t Tree) AppendData(data []byte) Tree { - return t.Append(t.hasher.HashLeaf(data)) +// AppendData adds the leaf hashes of the given entries to the end of the tree. +func (t Tree) AppendData(entries ...[]byte) Tree { + for _, entry := range entries { + t.appendImpl(t.hasher.HashLeaf(entry)) + } + return t } -// Append adds the given leaf hash to the end of the tree. -func (t Tree) Append(hash []byte) Tree { +// Append adds the given leaf hashes to the end of the tree. +func (t Tree) Append(hashes ...[]byte) Tree { + for _, hash := range hashes { + t.appendImpl(hash) + } + return t +} + +func (t *Tree) appendImpl(hash []byte) { level, width := 0, t.size for ; width&1 == 1; width, level = width/2, level+1 { t.hashes[level] = append(t.hashes[level][:width], hash) @@ -53,7 +63,6 @@ func (t Tree) Append(hash []byte) Tree { t.hashes[level] = append(t.hashes[level][:width], hash) t.size++ - return t } // Size returns the current number of leaves in the tree. diff --git a/internal/merkle/inmemory/tree_test.go b/internal/merkle/inmemory/tree_test.go index 0f9df1baec..3941c9e338 100644 --- a/internal/merkle/inmemory/tree_test.go +++ b/internal/merkle/inmemory/tree_test.go @@ -29,15 +29,18 @@ import ( // TODO(pavelkalinnikov): Rewrite this file entirely. +// FIXME +var hx = decodeHexStringOrPanic + var fuzzTestSize = int64(256) // This is the hash of an empty string var emptyTreeHashValue = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // Inputs to the reference tree, which has eight leaves. -var leafInputs = []string{ - "", "00", "10", "2021", "3031", "40414243", - "5051525354555657", "606162636465666768696a6b6c6d6e6f", +var leafInputs = [][]byte{ + hx(""), hx("00"), hx("10"), hx("2021"), hx("3031"), hx("40414243"), + hx("5051525354555657"), hx("606162636465666768696a6b6c6d6e6f"), } // Incremental roots from building the reference tree from inputs leaf-by-leaf. @@ -359,24 +362,19 @@ func TestBuildTreeBuildOneAtATime(t *testing.T) { // Add to the tree, checking after each leaf for l := uint64(0); l < 8; l++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) + mt = mt.AppendData(leafInputs[l]) validateTree(mt, l, t) } } func TestBuildTreeBuildAllAtOnce(t *testing.T) { mt := makeEmptyTree() - - for l := 0; l < 3; l++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) - } + mt = mt.AppendData(leafInputs[:3]...) // Check the intermediate state validateTree(mt, 2, t) - for l := 3; l < 8; l++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) - } + mt = mt.AppendData(leafInputs[3:8]...) // Check the final state validateTree(mt, 7, t) @@ -386,9 +384,7 @@ func TestBuildTreeBuildTwoChunks(t *testing.T) { mt := makeEmptyTree() // Add to the tree, checking after each leaf - for l := 0; l < 8; l++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[l])) - } + mt = mt.AppendData(leafInputs[:8]...) validateTree(mt, 7, t) } @@ -409,13 +405,8 @@ func TestDownToPowerOfTwoSanity(t *testing.T) { } func TestReferenceMerklePathSanity(t *testing.T) { - var data [][]byte - mt := makeEmptyTree() - - for s := 0; s < 8; s++ { - data = append(data, decodeHexStringOrPanic(leafInputs[s])) - } + data := append([][]byte{}, leafInputs[:8]...) for _, path := range testPaths { referencePath, err := referenceMerklePath(data[:path.snapshot], path.leaf, mt.hasher) @@ -557,10 +548,7 @@ func TestMerkleTreeConsistencyFuzz(t *testing.T) { func TestMerkleTreePathBuildOnce(t *testing.T) { // First tree: build in one go. mt := makeEmptyTree() - - for i := 0; i < 8; i++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) - } + mt = mt.AppendData(leafInputs[:8]...) if size := mt.Size(); size != 8 { t.Fatalf("8 leaves added but tree size is %d", size) @@ -600,15 +588,12 @@ func TestMerkleTreePathBuildIncrementally(t *testing.T) { // Second tree: build incrementally. // First tree: build in one go. mt := makeEmptyTree() - - for i := 0; i < 8; i++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) - } + mt = mt.AppendData(leafInputs[:8]...) mt2 := makeEmptyTree() for i := uint64(0); i < 8; i++ { - mt2 = mt2.AppendData(decodeHexStringOrPanic(leafInputs[i])) + mt2 = mt2.AppendData(leafInputs[i]) for j := uint64(0); j < i+1; j++ { p1, err := mt.InclusionProof(j, i+1) @@ -643,10 +628,7 @@ func TestMerkleTreePathBuildIncrementally(t *testing.T) { func TestProofConsistencyTestVectors(t *testing.T) { mt := makeEmptyTree() - - for i := 0; i < 8; i++ { - mt = mt.AppendData(decodeHexStringOrPanic(leafInputs[i])) - } + mt = mt.AppendData(leafInputs[:8]...) if size := mt.Size(); size != 8 { t.Fatalf("8 leaves added but tree size is %d", size) From 914ee9138e1f2c5834f2425c241e2e5dc0946fb2 Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 13 Apr 2022 15:13:50 -0400 Subject: [PATCH 3/5] Fix usages --- integration/log.go | 4 ++-- merkle/logverifier/log_verifier_test.go | 18 ++++++++---------- server/proof_fetcher_test.go | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/integration/log.go b/integration/log.go index bc9a651a49..7a5d460135 100644 --- a/integration/log.go +++ b/integration/log.go @@ -526,7 +526,7 @@ func buildMemoryMerkleTree(leafMap map[int64]*trillian.LogLeaf, params TestParam if err := cr.Append(hasher.HashLeaf(leafMap[l].LeafValue), nil); err != nil { return nil, err } - merkleTree.AppendData(leafMap[l].LeafValue) + merkleTree = merkleTree.AppendData(leafMap[l].LeafValue) } // If the two reference results disagree there's no point in continuing the @@ -543,7 +543,7 @@ func buildMemoryMerkleTree(leafMap map[int64]*trillian.LogLeaf, params TestParam return nil, fmt.Errorf("different root hash results from merkle tree building: %v and %v", got, want) } - return merkleTree, nil + return &merkleTree, nil } func getLatestSignedLogRoot(client trillian.TrillianLogClient, params TestParameters) (*trillian.GetLatestSignedLogRootResponse, error) { diff --git a/merkle/logverifier/log_verifier_test.go b/merkle/logverifier/log_verifier_test.go index 50f28f70e5..ab5fa2f2bb 100644 --- a/merkle/logverifier/log_verifier_test.go +++ b/merkle/logverifier/log_verifier_test.go @@ -334,7 +334,7 @@ func TestVerifyInclusionProofGenerated(t *testing.T) { tree, v := createTree(0) for _, size := range sizes { - growTree(tree, size) + tree = growTree(tree, size) root := tree.Hash() for i := int64(0); i < size; i++ { t.Run(fmt.Sprintf("size:%d:index:%d", size, i), func(t *testing.T) { @@ -435,7 +435,7 @@ func TestPrefixHashFromInclusionProofGenerated(t *testing.T) { tree, v := createTree(0) for _, size := range sizes { - growTree(tree, size) + tree = growTree(tree, size) root := tree.Hash() for i := int64(1); i <= size; i++ { @@ -532,21 +532,19 @@ func shortHash(hash []byte) string { return fmt.Sprintf("%x...", hash[:4]) } -func createTree(size int64) (*inmemory.Tree, LogVerifier) { +func createTree(size int64) (inmemory.Tree, LogVerifier) { tree := inmemory.New(rfc6962.DefaultHasher) - growTree(tree, size) - return tree, New(rfc6962.DefaultHasher) + return growTree(tree, size), New(rfc6962.DefaultHasher) } -func growTree(tree *inmemory.Tree, upTo int64) { +func growTree(tree inmemory.Tree, upTo int64) inmemory.Tree { for i := int64(tree.Size()); i < upTo; i++ { - data := []byte(fmt.Sprintf("data:%d", i)) - tree.AppendData(data) + tree = tree.AppendData([]byte(fmt.Sprintf("data:%d", i))) } + return tree } -func getLeafAndProof(tree *inmemory.Tree, index int64) ([]byte, [][]byte) { - // Note: inmemory.MerkleTree counts leaves from 1. +func getLeafAndProof(tree inmemory.Tree, index int64) ([]byte, [][]byte) { proof, err := tree.InclusionProof(uint64(index), tree.Size()) if err != nil { panic(err) diff --git a/server/proof_fetcher_test.go b/server/proof_fetcher_test.go index 2bc514095f..e9720a8db8 100644 --- a/server/proof_fetcher_test.go +++ b/server/proof_fetcher_test.go @@ -208,11 +208,11 @@ func expandLeaves(n, m uint64) []string { return leaves } -func treeAtSize(n uint64) *inmemory.Tree { +func treeAtSize(n uint64) inmemory.Tree { leaves := expandLeaves(0, n-1) mt := inmemory.New(rfc6962.DefaultHasher) for _, leaf := range leaves { - mt.AppendData([]byte(leaf)) + mt = mt.AppendData([]byte(leaf)) } return mt } From 59220f5cf3fb90161529829b357ecbba37e50397 Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 20 Apr 2022 09:06:17 -0400 Subject: [PATCH 4/5] Add more comments --- internal/merkle/inmemory/tree.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/internal/merkle/inmemory/tree.go b/internal/merkle/inmemory/tree.go index ee1a415d3d..ccc517da62 100644 --- a/internal/merkle/inmemory/tree.go +++ b/internal/merkle/inmemory/tree.go @@ -22,6 +22,16 @@ import ( ) // Tree implements an append-only Merkle tree. For testing. +// +// This type is immutable, but, if Append* methods are not used carefully, the +// hashes can be corrupted. The semantics of memory reuse and reallocation is +// similar to that of the append built-in for Go slices: overlapping Trees can +// share memory. +// +// It is recommended to reuse old versions of Tree only for read operations, or +// making sure that the newer Trees are no longer used before writing to an +// older Tree. One scenario when rolling back to an older Tree can be useful is +// implementing transaction semantics, rollbacks, and snapshots. type Tree struct { hasher merkle.LogHasher size uint64 @@ -33,7 +43,10 @@ func New(hasher merkle.LogHasher) Tree { return Tree{hasher: hasher} } -// AppendData adds the leaf hashes of the given entries to the end of the tree. +// AppendData returns a new Tree which is the result of appending the hashes of +// the given entries and the dependent Merkle tree nodes to the current tree. +// +// See Append method comment for details on safety. func (t Tree) AppendData(entries ...[]byte) Tree { for _, entry := range entries { t.appendImpl(t.hasher.HashLeaf(entry)) @@ -41,7 +54,13 @@ func (t Tree) AppendData(entries ...[]byte) Tree { return t } -// Append adds the given leaf hashes to the end of the tree. +// Append returns a new Tree which is the result of appending the given leaf +// hashes and the dependent Merkle tree nodes to the current tree. +// +// The new Tree likely shares data with the old Tree, but in such a way that +// both objects are valid. It is safe to reuse / roll back to the older Tree +// objects, but Append should be called on them with caution because it may +// corrupt hashes in the newer Tree objects. func (t Tree) Append(hashes ...[]byte) Tree { for _, hash := range hashes { t.appendImpl(hash) From 1d06ac3b67aa2a6c0c58400c651fd97b7a424b7c Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 20 Apr 2022 09:35:38 -0400 Subject: [PATCH 5/5] Remove hex string decoder alias --- internal/merkle/inmemory/tree_test.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/merkle/inmemory/tree_test.go b/internal/merkle/inmemory/tree_test.go index 3941c9e338..bb91062bfe 100644 --- a/internal/merkle/inmemory/tree_test.go +++ b/internal/merkle/inmemory/tree_test.go @@ -29,9 +29,6 @@ import ( // TODO(pavelkalinnikov): Rewrite this file entirely. -// FIXME -var hx = decodeHexStringOrPanic - var fuzzTestSize = int64(256) // This is the hash of an empty string @@ -127,12 +124,12 @@ var testProofs = []proofTestVector{ }}, } -func decodeHexStringOrPanic(hs string) []byte { +// hx decodes a hex string or panics. +func hx(hs string) []byte { data, err := hex.DecodeString(hs) if err != nil { panic(fmt.Errorf("failed to decode test data: %s", hs)) } - return data } @@ -420,7 +417,7 @@ func TestReferenceMerklePathSanity(t *testing.T) { } for i := 0; i < len(path.testVector); i++ { - if !bytes.Equal(referencePath[i], decodeHexStringOrPanic(path.testVector[i])) { + if !bytes.Equal(referencePath[i], hx(path.testVector[i])) { t.Errorf("Path mismatch: %s, %s", hex.EncodeToString(referencePath[i]), path.testVector[i]) } @@ -555,7 +552,7 @@ func TestMerkleTreePathBuildOnce(t *testing.T) { } hash := mt.Hash() - if got, want := hash, decodeHexStringOrPanic(rootsAtSize[7]); !bytes.Equal(got, want) { + if got, want := hash, hx(rootsAtSize[7]); !bytes.Equal(got, want) { t.Fatalf("Got unexpected root hash: %x %x", got, want) } @@ -577,7 +574,7 @@ func TestMerkleTreePathBuildOnce(t *testing.T) { } for j := range p2 { - if got, want := p1[j], decodeHexStringOrPanic(testPaths[i].testVector[j]); !bytes.Equal(got, want) { + if got, want := p1[j], hx(testPaths[i].testVector[j]); !bytes.Equal(got, want) { t.Errorf("Path mismatch: got: %v want: %v", got, want) } } @@ -635,7 +632,7 @@ func TestProofConsistencyTestVectors(t *testing.T) { } hash := mt.Hash() - if got, want := hash, decodeHexStringOrPanic(rootsAtSize[7]); !bytes.Equal(got, want) { + if got, want := hash, hx(rootsAtSize[7]); !bytes.Equal(got, want) { t.Fatalf("Got unexpected root hash: %x %x", got, want) } @@ -653,7 +650,7 @@ func TestProofConsistencyTestVectors(t *testing.T) { } for j := 0; j < len(p2); j++ { - if got, want := p1[j], decodeHexStringOrPanic(testProofs[i].proof[j]); !bytes.Equal(got, want) { + if got, want := p1[j], hx(testProofs[i].proof[j]); !bytes.Equal(got, want) { t.Errorf("Path mismatch: got: %v want: %v", got, want) } }