forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request cosmos#105 from tendermint/bucky/merkle
Bring in merkle and tmhash from tmlibs
- Loading branch information
Showing
10 changed files
with
587 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
## Simple Merkle Tree | ||
|
||
For smaller static data structures that don't require immutable snapshots or mutability; | ||
for instance the transactions and validation signatures of a block can be hashed using this simple merkle tree logic. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
Package merkle computes a deterministic minimal height Merkle tree hash. | ||
If the number of items is not a power of two, some leaves | ||
will be at different levels. Tries to keep both sides of | ||
the tree the same size, but the left may be one greater. | ||
Use this for short deterministic trees, such as the validator list. | ||
For larger datasets, use IAVLTree. | ||
Be aware that the current implementation by itself does not prevent | ||
second pre-image attacks. Hence, use this library with caution. | ||
Otherwise you might run into similar issues as, e.g., in early Bitcoin: | ||
https://bitcointalk.org/?topic=102395 | ||
* | ||
/ \ | ||
/ \ | ||
/ \ | ||
/ \ | ||
* * | ||
/ \ / \ | ||
/ \ / \ | ||
/ \ / \ | ||
* * * h6 | ||
/ \ / \ / \ | ||
h0 h1 h2 h3 h4 h5 | ||
TODO(ismail): add 2nd pre-image protection or clarify further on how we use this and why this secure. | ||
*/ | ||
package merkle |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package merkle | ||
|
||
import ( | ||
"github.com/tendermint/go-crypto/tmhash" | ||
cmn "github.com/tendermint/tmlibs/common" | ||
) | ||
|
||
// Merkle tree from a map. | ||
// Leaves are `hash(key) | hash(value)`. | ||
// Leaves are sorted before Merkle hashing. | ||
type simpleMap struct { | ||
kvs cmn.KVPairs | ||
sorted bool | ||
} | ||
|
||
func newSimpleMap() *simpleMap { | ||
return &simpleMap{ | ||
kvs: nil, | ||
sorted: false, | ||
} | ||
} | ||
|
||
// Set hashes the key and value and appends it to the kv pairs. | ||
func (sm *simpleMap) Set(key string, value Hasher) { | ||
sm.sorted = false | ||
|
||
// Hash the key to blind it... why not? | ||
khash := tmhash.Sum([]byte(key)) | ||
|
||
// And the value is hashed too, so you can | ||
// check for equality with a cached value (say) | ||
// and make a determination to fetch or not. | ||
vhash := value.Hash() | ||
|
||
sm.kvs = append(sm.kvs, cmn.KVPair{ | ||
Key: khash, | ||
Value: vhash, | ||
}) | ||
} | ||
|
||
// Hash Merkle root hash of items sorted by key | ||
// (UNSTABLE: and by value too if duplicate key). | ||
func (sm *simpleMap) Hash() []byte { | ||
sm.Sort() | ||
return hashKVPairs(sm.kvs) | ||
} | ||
|
||
func (sm *simpleMap) Sort() { | ||
if sm.sorted { | ||
return | ||
} | ||
sm.kvs.Sort() | ||
sm.sorted = true | ||
} | ||
|
||
// Returns a copy of sorted KVPairs. | ||
// NOTE these contain the hashed key and value. | ||
func (sm *simpleMap) KVPairs() cmn.KVPairs { | ||
sm.Sort() | ||
kvs := make(cmn.KVPairs, len(sm.kvs)) | ||
copy(kvs, sm.kvs) | ||
return kvs | ||
} | ||
|
||
//---------------------------------------- | ||
|
||
// A local extension to KVPair that can be hashed. | ||
// Key and value are length prefixed and concatenated, | ||
// then hashed. | ||
type kvPair cmn.KVPair | ||
|
||
func (kv kvPair) Hash() []byte { | ||
hasher := tmhash.New() | ||
err := encodeByteSlice(hasher, kv.Key) | ||
if err != nil { | ||
panic(err) | ||
} | ||
err = encodeByteSlice(hasher, kv.Value) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return hasher.Sum(nil) | ||
} | ||
|
||
func hashKVPairs(kvs cmn.KVPairs) []byte { | ||
kvsH := make([]Hasher, len(kvs)) | ||
for i, kvp := range kvs { | ||
kvsH[i] = kvPair(kvp) | ||
} | ||
return SimpleHashFromHashers(kvsH) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package merkle | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/tendermint/go-crypto/tmhash" | ||
) | ||
|
||
type strHasher string | ||
|
||
func (str strHasher) Hash() []byte { | ||
return tmhash.Sum([]byte(str)) | ||
} | ||
|
||
func TestSimpleMap(t *testing.T) { | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key1", strHasher("value1")) | ||
assert.Equal(t, "3dafc06a52039d029be57c75c9d16356a4256ef4", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key1", strHasher("value2")) | ||
assert.Equal(t, "03eb5cfdff646bc4e80fec844e72fd248a1c6b2c", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key1", strHasher("value1")) | ||
db.Set("key2", strHasher("value2")) | ||
assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key2", strHasher("value2")) // NOTE: out of order | ||
db.Set("key1", strHasher("value1")) | ||
assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key1", strHasher("value1")) | ||
db.Set("key2", strHasher("value2")) | ||
db.Set("key3", strHasher("value3")) | ||
assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
{ | ||
db := newSimpleMap() | ||
db.Set("key2", strHasher("value2")) // NOTE: out of order | ||
db.Set("key1", strHasher("value1")) | ||
db.Set("key3", strHasher("value3")) | ||
assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package merkle | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
) | ||
|
||
// SimpleProof represents a simple merkle proof. | ||
type SimpleProof struct { | ||
Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child. | ||
} | ||
|
||
// SimpleProofsFromHashers computes inclusion proof for given items. | ||
// proofs[0] is the proof for items[0]. | ||
func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleProof) { | ||
trails, rootSPN := trailsFromHashers(items) | ||
rootHash = rootSPN.Hash | ||
proofs = make([]*SimpleProof, len(items)) | ||
for i, trail := range trails { | ||
proofs[i] = &SimpleProof{ | ||
Aunts: trail.FlattenAunts(), | ||
} | ||
} | ||
return | ||
} | ||
|
||
// SimpleProofsFromMap generates proofs from a map. The keys/values of the map will be used as the keys/values | ||
// in the underlying key-value pairs. | ||
// The keys are sorted before the proofs are computed. | ||
func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*SimpleProof) { | ||
sm := newSimpleMap() | ||
for k, v := range m { | ||
sm.Set(k, v) | ||
} | ||
sm.Sort() | ||
kvs := sm.kvs | ||
kvsH := make([]Hasher, 0, len(kvs)) | ||
for _, kvp := range kvs { | ||
kvsH = append(kvsH, kvPair(kvp)) | ||
} | ||
return SimpleProofsFromHashers(kvsH) | ||
} | ||
|
||
// Verify that leafHash is a leaf hash of the simple-merkle-tree | ||
// which hashes to rootHash. | ||
func (sp *SimpleProof) Verify(index int, total int, leafHash []byte, rootHash []byte) bool { | ||
computedHash := computeHashFromAunts(index, total, leafHash, sp.Aunts) | ||
return computedHash != nil && bytes.Equal(computedHash, rootHash) | ||
} | ||
|
||
// String implements the stringer interface for SimpleProof. | ||
// It is a wrapper around StringIndented. | ||
func (sp *SimpleProof) String() string { | ||
return sp.StringIndented("") | ||
} | ||
|
||
// StringIndented generates a canonical string representation of a SimpleProof. | ||
func (sp *SimpleProof) StringIndented(indent string) string { | ||
return fmt.Sprintf(`SimpleProof{ | ||
%s Aunts: %X | ||
%s}`, | ||
indent, sp.Aunts, | ||
indent) | ||
} | ||
|
||
// Use the leafHash and innerHashes to get the root merkle hash. | ||
// If the length of the innerHashes slice isn't exactly correct, the result is nil. | ||
// Recursive impl. | ||
func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][]byte) []byte { | ||
if index >= total || index < 0 || total <= 0 { | ||
return nil | ||
} | ||
switch total { | ||
case 0: | ||
panic("Cannot call computeHashFromAunts() with 0 total") | ||
case 1: | ||
if len(innerHashes) != 0 { | ||
return nil | ||
} | ||
return leafHash | ||
default: | ||
if len(innerHashes) == 0 { | ||
return nil | ||
} | ||
numLeft := (total + 1) / 2 | ||
if index < numLeft { | ||
leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) | ||
if leftHash == nil { | ||
return nil | ||
} | ||
return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) | ||
} | ||
rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) | ||
if rightHash == nil { | ||
return nil | ||
} | ||
return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) | ||
} | ||
} | ||
|
||
// SimpleProofNode is a helper structure to construct merkle proof. | ||
// The node and the tree is thrown away afterwards. | ||
// Exactly one of node.Left and node.Right is nil, unless node is the root, in which case both are nil. | ||
// node.Parent.Hash = hash(node.Hash, node.Right.Hash) or | ||
// hash(node.Left.Hash, node.Hash), depending on whether node is a left/right child. | ||
type SimpleProofNode struct { | ||
Hash []byte | ||
Parent *SimpleProofNode | ||
Left *SimpleProofNode // Left sibling (only one of Left,Right is set) | ||
Right *SimpleProofNode // Right sibling (only one of Left,Right is set) | ||
} | ||
|
||
// FlattenAunts will return the inner hashes for the item corresponding to the leaf, | ||
// starting from a leaf SimpleProofNode. | ||
func (spn *SimpleProofNode) FlattenAunts() [][]byte { | ||
// Nonrecursive impl. | ||
innerHashes := [][]byte{} | ||
for spn != nil { | ||
if spn.Left != nil { | ||
innerHashes = append(innerHashes, spn.Left.Hash) | ||
} else if spn.Right != nil { | ||
innerHashes = append(innerHashes, spn.Right.Hash) | ||
} else { | ||
break | ||
} | ||
spn = spn.Parent | ||
} | ||
return innerHashes | ||
} | ||
|
||
// trails[0].Hash is the leaf hash for items[0]. | ||
// trails[i].Parent.Parent....Parent == root for all i. | ||
func trailsFromHashers(items []Hasher) (trails []*SimpleProofNode, root *SimpleProofNode) { | ||
// Recursive impl. | ||
switch len(items) { | ||
case 0: | ||
return nil, nil | ||
case 1: | ||
trail := &SimpleProofNode{items[0].Hash(), nil, nil, nil} | ||
return []*SimpleProofNode{trail}, trail | ||
default: | ||
lefts, leftRoot := trailsFromHashers(items[:(len(items)+1)/2]) | ||
rights, rightRoot := trailsFromHashers(items[(len(items)+1)/2:]) | ||
rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash) | ||
root := &SimpleProofNode{rootHash, nil, nil, nil} | ||
leftRoot.Parent = root | ||
leftRoot.Right = rightRoot | ||
rightRoot.Parent = root | ||
rightRoot.Left = leftRoot | ||
return append(lefts, rights...), root | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package merkle | ||
|
||
import ( | ||
"github.com/tendermint/go-crypto/tmhash" | ||
) | ||
|
||
// SimpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right). | ||
func SimpleHashFromTwoHashes(left, right []byte) []byte { | ||
var hasher = tmhash.New() | ||
err := encodeByteSlice(hasher, left) | ||
if err != nil { | ||
panic(err) | ||
} | ||
err = encodeByteSlice(hasher, right) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return hasher.Sum(nil) | ||
} | ||
|
||
// SimpleHashFromHashers computes a Merkle tree from items that can be hashed. | ||
func SimpleHashFromHashers(items []Hasher) []byte { | ||
hashes := make([][]byte, len(items)) | ||
for i, item := range items { | ||
hash := item.Hash() | ||
hashes[i] = hash | ||
} | ||
return simpleHashFromHashes(hashes) | ||
} | ||
|
||
// SimpleHashFromMap computes a Merkle tree from sorted map. | ||
// Like calling SimpleHashFromHashers with | ||
// `item = []byte(Hash(key) | Hash(value))`, | ||
// sorted by `item`. | ||
func SimpleHashFromMap(m map[string]Hasher) []byte { | ||
sm := newSimpleMap() | ||
for k, v := range m { | ||
sm.Set(k, v) | ||
} | ||
return sm.Hash() | ||
} | ||
|
||
//---------------------------------------------------------------- | ||
|
||
// Expects hashes! | ||
func simpleHashFromHashes(hashes [][]byte) []byte { | ||
// Recursive impl. | ||
switch len(hashes) { | ||
case 0: | ||
return nil | ||
case 1: | ||
return hashes[0] | ||
default: | ||
left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) | ||
right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) | ||
return SimpleHashFromTwoHashes(left, right) | ||
} | ||
} |
Oops, something went wrong.