Skip to content

Commit

Permalink
feat(examples): add merkle tree package (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
albttx authored May 24, 2023
1 parent 9c34aba commit 2469cbd
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 0 deletions.
20 changes: 20 additions & 0 deletions examples/gno.land/p/demo/merkle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# p/demo/merkle

This package implement a merkle tree that is complient with [merkletreejs](https://github.com/merkletreejs/merkletreejs)

## [merkletreejs](https://github.com/merkletreejs/merkletreejs)

```javascript
const { MerkleTree } = require("merkletreejs");
const SHA256 = require("crypto-js/sha256");

let leaves = [];
for (let i = 0; i < 10; i++) {
leaves.push(SHA256(`node_${i}`));
}

const tree = new MerkleTree(leaves, SHA256);
const root = tree.getRoot().toString("hex");

console.log(root); // cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21
```
132 changes: 132 additions & 0 deletions examples/gno.land/p/demo/merkle/merkle.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package merkle

import (
"bytes"
"crypto/sha256"
"encoding/hex"
"errors"
)

type Hashable interface {
Bytes() []byte
}

type nodes []Node

type Node struct {
hash []byte

position uint8
}

func (n Node) Hash() string {
return hex.EncodeToString(n.hash[:])
}

type Tree struct {
layers []nodes
}

// Root return the merkle root of the tree
func (t *Tree) Root() string {
for _, l := range t.layers {
if len(l) == 1 {
return l[0].Hash()
}
}
return ""
}

// NewTree create a new Merkle Tree
func NewTree(data []Hashable) *Tree {
tree := &Tree{}

leaves := make([]Node, len(data))

for i, d := range data {
hash := sha256.Sum256(d.Bytes())
leaves[i] = Node{hash: hash[:]}
}

tree.layers = []nodes{nodes(leaves)}

var buff bytes.Buffer
for len(leaves) > 1 {
level := make([]Node, 0, len(leaves)/2+1)
for i := 0; i < len(leaves); i += 2 {
buff.Reset()

if i < len(leaves)-1 {
buff.Write(leaves[i].hash)
buff.Write(leaves[i+1].hash)
hash := sha256.Sum256(buff.Bytes())
level = append(level, Node{
hash: hash[:],
})
} else {
level = append(level, leaves[i])
}
}
leaves = level
tree.layers = append(tree.layers, level)
}
return tree
}

// Proof return a MerkleProof
func (t *Tree) Proof(data Hashable) ([]Node, error) {
targetHash := sha256.Sum256(data.Bytes())
targetIndex := -1

for i, layer := range t.layers[0] {
if bytes.Equal(targetHash[:], layer.hash) {
targetIndex = i
break
}
}

if targetIndex == -1 {
return nil, errors.New("target not found")
}

proofs := make([]Node, 0, len(t.layers))

for _, layer := range t.layers {
var pairIndex int

if targetIndex%2 == 0 {
pairIndex = targetIndex + 1
} else {
pairIndex = targetIndex - 1
}
if pairIndex < len(layer) {
proofs = append(proofs, Node{
hash: layer[pairIndex].hash,
position: uint8(targetIndex) % 2,
})
}
targetIndex /= 2
}
return proofs, nil
}

// Verify if a merkle proof is valid
func (t *Tree) Verify(leaf Hashable, proofs []Node) bool {
return Verify(t.Root(), leaf, proofs)
}

// Verify if a merkle proof is valid
func Verify(root string, leaf Hashable, proofs []Node) bool {
hash := sha256.Sum256(leaf.Bytes())

for i := 0; i < len(proofs); i += 1 {
var h []byte
if proofs[i].position == 0 {
h = append(hash[:], proofs[i].hash...)
} else {
h = append(proofs[i].hash, hash[:]...)
}
hash = sha256.Sum256(h)
}
return hex.EncodeToString(hash[:]) == root
}
70 changes: 70 additions & 0 deletions examples/gno.land/p/demo/merkle/merkle_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package merkle

import (
"fmt"
"testing"
"time"
)

type testData struct {
content string
}

func (d testData) Bytes() []byte {
return []byte(d.content)
}

func TestMerkleTree(t *testing.T) {
tests := []struct {
size int
expected string
}{
{
size: 1,
expected: "cf9f824bce7f5bc63d557b23591f58577f53fe29f974a615bdddbd0140f912f4",
},
{
size: 3,
expected: "1a4a5f0fa267244bf9f74a63fdf2a87eed5e97e4bd104a9e94728c8fb5442177",
},
{
size: 10,
expected: "cd8a40502b0b92bf58e7432a5abb2d8b60121cf2b7966d6ebaf103f907a1bc21",
},
{
size: 1000,
expected: "fa533d2efdf12be26bc410dfa42936ac63361324e35e9b1ff54d422a1dd2388b",
},
}

for _, test := range tests {
var leaves []Hashable
for i := 0; i < test.size; i++ {
leaves = append(leaves, testData{fmt.Sprintf("node_%d", i)})
}

tree := NewTree(leaves)

if tree == nil {
t.Error("Merkle tree creation failed")
}

root := tree.Root()

if root != test.expected {
t.Fatalf("merkle.Tree.Root(), expected: %s; got: %s", test.expected, root)
}

for _, leaf := range leaves {
proofs, err := tree.Proof(leaf)
if err != nil {
t.Fatal("failed to proof leaf: %v, on tree: %v", leaf, test)
}

ok := Verify(root, leaf, proofs)
if !ok {
t.Fatal("failed to verify leaf: %v, on tree: %v", leaf, tree)
}
}
}
}
2 changes: 2 additions & 0 deletions gnovm/pkg/gnolang/precompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ var stdlibWhitelist = []string{
"context",
"crypto/md5",
"crypto/sha1",
"crypto/sha256",
"encoding/json",
"encoding/base64",
"encoding/binary",
"encoding/hex",
"encoding/xml",
"errors",
"flag",
Expand Down

0 comments on commit 2469cbd

Please sign in to comment.