From c52eedf3957a349f69a0d55b049615098d41ad35 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 25 Feb 2023 12:25:55 -0800 Subject: [PATCH] Olsh exploration --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 1 + Makefile | 33 ++++++ README.md | 97 +++++++++++++----- bits.csv | Bin 0 -> 96 bytes bulk_test.go | 57 ++++++----- go.mod | 2 + go.sum | 14 +++ mapstore.go | 9 +- mapstore_test.go | 14 +-- olsh_test.go | 110 ++++++++++++++++++++ smt.go | 244 +++++++++++++++++++++++---------------------- smt_test.go | 3 +- treehasher.go | 76 +++++++++----- treehasher_test.go | 71 +++++++++++++ utils.go | 15 ++- visualize.ipynb | 140 ++++++++++++++++++++++++++ 17 files changed, 675 insertions(+), 211 deletions(-) create mode 100644 .DS_Store create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 bits.csv create mode 100644 go.sum create mode 100644 olsh_test.go create mode 100644 treehasher_test.go create mode 100644 visualize.ipynb diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d818df9a3e24264621a5c4e46a73045cf192662c GIT binary patch literal 6148 zcmeHKJxfC|6g|<0EecZn0LQ13ljuKqN*$g20ZLnKtG2ITE9msx930$SUHuAkQWkjnjQ#5|=h$BerLTsAPdIz0ZL|h5Lq|{`DDWE z8gJE{yaQ&TeQ9ed9FJcP7KLJU)np&lLI8(ZVsB zC}9g_^4g)_XJ5)l6Kx#&ET!ygbuRbk)2m0T-_7+*{ZeiS?&^26I)CyW(FCd zDN{-`rOIA0lqqL_Z1WO}nL$$yWiKDf&aCVWMd{f&eyr1>5`)191%d*;0;A? data; + valueStore := smt.NewSimpleMap() // Mapping from node_path -> node_value; a path can be retrieved using the digest of the key + + // Initialise the smt + tree := smt.NewSparseMerkleTree(nodeStore, valueStore, sha256.New()) + + // Update the key "foo" with the value "bar" + _, _ = tree.Update([]byte("foo"), []byte("bar")) + + // Generate a Merkle proof for foo=bar + proof, _ := tree.Prove([]byte("foo")) + root := tree.Root() // We also need the current tree root for the proof + + // Verify the Merkle proof for foo=bar + if smt.VerifyProof(proof, root, []byte("foo"), []byte("bar"), sha256.New()) { + fmt.Println("Proof verification succeeded.") + } else { + fmt.Println("Proof verification failed.") + } } ``` +## Development + +Run `make` to see all the options available + +## General Improvements / TODOs + +- [ ] Use the `require` test module to simplify unit tests; can be done with a single clever regex find+replace +- [ ] Create types for `sideNodes`, `root`, etc... +- [ ] Add an interface for `SparseMerkleProof` so we can return nils and not access vars directly +- [ ] Add an interface for `SparseMerkleTree` so it's clear how we should interact with it +- [ ] If we create an interface for `TreeHasher`, we can embed it in `SparseMerkleTree` and then avoid the need to write things like `smt.th.path(...)` everywhere and use `smt.path(...)` directly. +- [ ] Consider splitting `smt.go` into `smt_ops.go` and `smt_proofs.go` +- [ ] Functions like `sideNodesForRoot` and `updateWithSideNodes` need to be split into smaller more compartmentalized functions + [libra whitepaper]: https://diem-developers-components.netlify.app/papers/the-diem-blockchain/2020-05-26.pdf +[jmt whitepaper]: https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf + +### [Delete me later] personal checklist + +- [x] ├── LICENSE +- [x] ├── Makefile +- [x] ├── README.md +- [ ] ├── bench_test.go +- [ ] ├── bulk_test.go +- [ ] ├── deepsubtree.go +- [ ] ├── deepsubtree_test.go +- [ ] ├── fuzz +- [ ] │   ├── delete +- [ ] │   │   └── fuzz.go +- [ ] │   └── fuzz.go +- [x] ├── go.mod +- [x] ├── go.sum +- [x] ├── mapstore.go +- [x] ├── mapstore_test.go +- [x] ├── options.go +- [ ] ├── oss-fuzz-build.sh +- [ ] ├── proofs.go +- [ ] ├── proofs_test.go +- [x] ├── smt.go +- [ ] ├── smt_test.go +- [x] ├── treehasher.go +- [x] ├── treehasher_test.go +- [x] └── utils.go diff --git a/bits.csv b/bits.csv new file mode 100644 index 0000000000000000000000000000000000000000..5d1bcfb3c6ad2c89d7786bf8be1ca4d2226a2468 GIT binary patch literal 96 zcmZ=}U|`TOG&C?YFyL|ru?@Jq;6hxkU=fg largestCommonPrefix { - largestCommonPrefix = commonPrefix - } + largestCommonPrefix := getLargestCommonPrefix(t, smt, kv, k) + numSideNodes := getNumSideNodes(t, smt, kv, k) + if (numSideNodes != largestCommonPrefix+1) && numSideNodes != 0 && largestCommonPrefix != 0 { + t.Error("leaf is at unexpected height") } - sideNodes, _, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root(), false) - if err != nil { - t.Errorf("error: %v", err) + } +} + +func getNumSideNodes(t *testing.T, smt *SparseMerkleTree, kv map[string]string, key string) (numSideNodes int) { + path := smt.th.path([]byte(key)) + sideNodes, _, _, _, err := smt.sideNodesForRoot(path, smt.Root()) + require.NoError(t, err) + for _, v := range sideNodes { + if v != nil { + numSideNodes++ } - numSideNodes := 0 - for _, v := range sideNodes { - if v != nil { - numSideNodes++ - } + } + return +} + +func getLargestCommonPrefix(_ *testing.T, smt *SparseMerkleTree, kv map[string]string, key string) (largestCommonPrefix int) { + path := smt.th.path([]byte(key)) + for k, v := range kv { + if v == "" { + continue } - if numSideNodes != largestCommonPrefix+1 && (numSideNodes != 0 && largestCommonPrefix != 0) { - t.Error("leaf is at unexpected height") + commonPrefix := countCommonPrefix(path, smt.th.path([]byte(k))) + if commonPrefix != smt.depth() && commonPrefix > largestCommonPrefix { + largestCommonPrefix = commonPrefix } } + return } diff --git a/go.mod b/go.mod index 5a78e23..880d3bb 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/celestiaorg/smt go 1.14 + +require github.com/stretchr/testify v1.8.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b410979 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mapstore.go b/mapstore.go index 5fe381b..4356b6b 100644 --- a/mapstore.go +++ b/mapstore.go @@ -6,9 +6,9 @@ import ( // MapStore is a key-value store. type MapStore interface { - Get(key []byte) ([]byte, error) // Get gets the value for a key. - Set(key []byte, value []byte) error // Set updates the value for a key. - Delete(key []byte) error // Delete deletes a key. + Get(key []byte) ([]byte, error) // Get gets the value for a key. + Set(key, value []byte) error // Set updates the value for a key. + Delete(key []byte) error // Delete deletes a key. } // InvalidKeyError is thrown when a key that does not exist is being accessed. @@ -48,8 +48,7 @@ func (sm *SimpleMap) Set(key []byte, value []byte) error { // Delete deletes a key. func (sm *SimpleMap) Delete(key []byte) error { - _, ok := sm.m[string(key)] - if ok { + if _, ok := sm.m[string(key)]; ok { delete(sm.m, string(key)) return nil } diff --git a/mapstore_test.go b/mapstore_test.go index ea945cc..46359d6 100644 --- a/mapstore_test.go +++ b/mapstore_test.go @@ -6,26 +6,28 @@ import ( "testing" ) -func TestSimpleMap(t *testing.T) { +func TestMapStoreSimpleMap(t *testing.T) { sm := NewSimpleMap() h := sha256.New() + var value []byte var err error h.Write([]byte("test")) + key := h.Sum(nil) // Tests for Get. - _, err = sm.Get(h.Sum(nil)) + _, err = sm.Get(key) if err == nil { t.Error("did not return an error when getting a non-existent key") } // Tests for Put. - err = sm.Set(h.Sum(nil), []byte("hello")) + err = sm.Set(key, []byte("hello")) if err != nil { t.Error("updating a key returned an error") } - value, err = sm.Get(h.Sum(nil)) + value, err = sm.Get(key) if err != nil { t.Error("getting a key returned an error") } @@ -34,11 +36,11 @@ func TestSimpleMap(t *testing.T) { } // Tests for Del. - err = sm.Delete(h.Sum(nil)) + err = sm.Delete(key) if err != nil { t.Error("deleting a key returned an error") } - _, err = sm.Get(h.Sum(nil)) + _, err = sm.Get(key) if err == nil { t.Error("failed to delete key") } diff --git a/olsh_test.go b/olsh_test.go new file mode 100644 index 0000000..cae2ca9 --- /dev/null +++ b/olsh_test.go @@ -0,0 +1,110 @@ +package smt + +// https://igraph.org/python/versions/latest/tutorials/quickstart/quickstart.html +// https://developers.diem.com/papers/jellyfish-merkle-tree/2021-01-14.pdf + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "encoding/csv" + "fmt" + "os" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestVisualizationHelper(t *testing.T) { + smn, smv := NewSimpleMap(), NewSimpleMap() + smt := NewSparseMerkleTree(smn, smv, sha256.New()) + kv := make(map[string]string) + bits := make(map[string]string) + + // Populate SMT + for i := 0; i < 10; i++ { + bs := make([]byte, 4) + binary.LittleEndian.PutUint32(bs, uint32(i+65)) + fmt.Println(bs, string(bs)) + smt.Update(bs, bs) + kv[string(bs)] = string(bs) + } + + // var key string + for key, value := range kv { + path := smt.th.path([]byte(key)) + + sideNodes, pathNodes, nodeData, _, err := smt.sideNodesForRoot(path, smt.root) + require.NoError(t, err) + + if smt.th.isLeaf([]byte(nodeData)) { + fmt.Println(fmt.Sprintf("Leaf: %s (%d side nodes, %d path nodes)", value, len(sideNodes), len(pathNodes))) + } else { + fmt.Println(fmt.Sprintf("Not Leaf: %s (%d side nodes, %d path nodes)", value, len(sideNodes), len(pathNodes))) + } + + largestCommonPrefixLen := getLargestCommonPrefix(t, smt, kv, key) + fmt.Print("\tLargest common prefix: ", largestCommonPrefixLen) + + var sb strings.Builder + fmt.Print("\n\tCommon Bits : ") + for i := 0; i < largestCommonPrefixLen; i++ { + bit := getBitAtFromMSB(path, i) + fmt.Print(bit, " ") + sb.WriteString(strconv.Itoa(bit)) + } + bits[key] = sb.String() + + // fmt.Print(fmt.Sprintf("bits (%s): ", value)) + + sideNodeReadable := make([]string, len(sideNodes)) + for i, sideNode := range sideNodes { + sideNodeReadable[i] = getLeafValue(t, smt, sideNode) + } + fmt.Println("\n\tSIDE NODES: [", strings.Join(sideNodeReadable, ", "), "]") + + pathNodeReadable := make([]string, len(pathNodes)) + for i, pathNode := range pathNodes { + pathNodeReadable[i] = getLeafValue(t, smt, pathNode) + } + fmt.Println("\tPATH NODES: [", strings.Join(pathNodeReadable, ", "), "]") + } + writeToCsv(t, bits) +} + +func getLeafValue(t *testing.T, smt *SparseMerkleTree, nodeHash []byte) string { + if bytes.Equal(nodeHash, smt.th.placeholder()) { + return "ZERO" + } + + hashOrData, err := smt.nodes.Get(nodeHash) + require.NoError(t, err) + + if smt.th.isLeaf(hashOrData) { + leafPath, _ := smt.th.parseLeaf(hashOrData) + leafUserValue, err := smt.values.Get(leafPath) + require.NoError(t, err) + return string(leafUserValue) + } + + // Node is not a leaf, but is a digest of its children + // left, right := smt.th.parseNode(hashOrData) + // l := getLeafValue(t, smt, left) + // r := getLeafValue(t, smt, right) + // return fmt.Sprintf("(%s %s)", l, r) + return "NODE" +} + +func writeToCsv(t *testing.T, bits map[string]string) { + csvFile, err := os.Create("bits.csv") + require.NoError(t, err) + csvWriter := csv.NewWriter(csvFile) + for key, bits := range bits { + err = csvWriter.Write([]string{string(key), bits}) + require.NoError(t, err) + } + csvWriter.Flush() + csvFile.Close() +} diff --git a/smt.go b/smt.go index 5dcf5b1..6b5d967 100644 --- a/smt.go +++ b/smt.go @@ -8,153 +8,154 @@ import ( ) const ( - right = 1 + right = 1 // ASK(reviewer): Are we only dealing with binary trees? ) -var defaultValue = []byte{} +var ( + // ASK(reviewer): rename to `emptySubtreeNode`? + defaultValue = make([]byte, 0) -var errKeyAlreadyEmpty = errors.New("key already empty") + // errors + errKeyAlreadyEmpty = errors.New("key already empty") +) -// SparseMerkleTree is a Sparse Merkle tree. type SparseMerkleTree struct { - th treeHasher - nodes, values MapStore - root []byte + th treeHasher + nodes MapStore // mapping from `hash` -> `data` + values MapStore // mapping from `path` -> `value` + root []byte } -// NewSparseMerkleTree creates a new Sparse Merkle tree on an empty MapStore. +// `Creates a new Sparse Merkle tree on an empty MapStore func NewSparseMerkleTree(nodes, values MapStore, hasher hash.Hash, options ...Option) *SparseMerkleTree { smt := SparseMerkleTree{ th: *newTreeHasher(hasher), - nodes: nodes, - values: values, + nodes: nodes, // ASK(reviewer): there is no validation that this is empty. Should we check? + values: values, // ASK(reviewer): there is no validation that this is empty. Should we check? } for _, option := range options { option(&smt) } - smt.SetRoot(smt.th.placeholder()) + smt.setRoot(smt.th.placeholder()) return &smt } -// ImportSparseMerkleTree imports a Sparse Merkle tree from a non-empty MapStore. +// `Imports a Sparse Merkle tree from a non-empty MapStore. func ImportSparseMerkleTree(nodes, values MapStore, hasher hash.Hash, root []byte) *SparseMerkleTree { smt := SparseMerkleTree{ th: *newTreeHasher(hasher), - nodes: nodes, - values: values, + nodes: nodes, // ASK(reviewer): No validation that this corresponds to the root provided + values: values, // ASK(reviewer): No validation that this corresponds to the root provided root: root, } return &smt } -// Root gets the root of the tree. +// Returns the root of the tree. func (smt *SparseMerkleTree) Root() []byte { return smt.root } -// SetRoot sets the root of the tree. -func (smt *SparseMerkleTree) SetRoot(root []byte) { +// Sets the root of the tree. +func (smt *SparseMerkleTree) setRoot(root []byte) { smt.root = root } +// ASK(reviewer): Why is the depth of the tree the size of the hash in bits? +// Per JMT, it should be depending on the `k-ary` of the tree and the size of the hash. +// E.g. if we are using a 256 bit hasher, the MAX depth is logk(256) func (smt *SparseMerkleTree) depth() int { return smt.th.pathSize() * 8 } -// Get gets the value of a key from the tree. +// Gets the value of a key from the tree. func (smt *SparseMerkleTree) Get(key []byte) ([]byte, error) { // Get tree's root root := smt.Root() + // If the tree is empty, return the default value if bytes.Equal(root, smt.th.placeholder()) { - // The tree is empty, return the default value. return defaultValue, nil } + // Retrieve the value based on the path path := smt.th.path(key) value, err := smt.values.Get(path) + if err == nil { + return value, nil + } - if err != nil { - var invalidKeyError *InvalidKeyError - - if errors.As(err, &invalidKeyError) { - // If key isn't found, return default value - return defaultValue, nil - } else { - // Otherwise percolate up any other error - return nil, err - } + // If key isn't found, return default value + var invalidKeyError *InvalidKeyError + if errors.As(err, &invalidKeyError) { + return defaultValue, nil } - return value, nil + + // Otherwise percolate up any other error + return nil, err } -// Has returns true if the value at the given key is non-default, false -// otherwise. +// Returns true if the value at the given key is non-default, false otherwise. func (smt *SparseMerkleTree) Has(key []byte) (bool, error) { - val, err := smt.Get(key) - return !bytes.Equal(defaultValue, val), err + value, err := smt.Get(key) + return !bytes.Equal(defaultValue, value), err } -// Update sets a new value for a key in the tree, and sets and returns the new root of the tree. -func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { - newRoot, err := smt.UpdateForRoot(key, value, smt.Root()) +// Sets a new value for a key in the tree, and returns the new root of the tree. +func (smt *SparseMerkleTree) Update(key, value []byte) ([]byte, error) { + newRoot, err := smt.updateForRoot(key, value, smt.Root()) if err != nil { return nil, err } - smt.SetRoot(newRoot) + smt.setRoot(newRoot) return newRoot, nil } -// Delete deletes a value from tree. It returns the new root of the tree. -func (smt *SparseMerkleTree) Delete(key []byte) ([]byte, error) { - return smt.Update(key, defaultValue) -} - -// UpdateForRoot sets a new value for a key in the tree at a specific root, and returns the new root. -func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte) ([]byte, error) { +// Internal helper for `Update` +func (smt *SparseMerkleTree) updateForRoot(key, value, root []byte) ([]byte, error) { path := smt.th.path(key) - sideNodes, pathNodes, oldLeafData, _, err := smt.sideNodesForRoot(path, root, false) + sideNodes, pathNodes, oldLeafData, _, err := smt.sideNodesForRoot(path, root) if err != nil { return nil, err } - var newRoot []byte - if bytes.Equal(value, defaultValue) { - // Delete operation. - newRoot, err = smt.deleteWithSideNodes(path, sideNodes, pathNodes, oldLeafData) - if errors.Is(err, errKeyAlreadyEmpty) { - // This key is already empty; return the old root. - return root, nil - } - if err := smt.values.Delete(path); err != nil { - return nil, err - } + // Insert or update operation. + if !bytes.Equal(value, defaultValue) { + return smt.updateWithSideNodes(path, value, sideNodes, pathNodes, oldLeafData) + } - } else { - // Insert or update operation. - newRoot, err = smt.updateWithSideNodes(path, value, sideNodes, pathNodes, oldLeafData) + // Delete operation. + newRoot, err := smt.deleteWithSideNodes(path, sideNodes, pathNodes, oldLeafData) + if errors.Is(err, errKeyAlreadyEmpty) { + // This key is already empty; return the old root. + return root, nil + } + if err := smt.values.Delete(path); err != nil { + return nil, err } return newRoot, err } -// DeleteForRoot deletes a value from tree at a specific root. It returns the new root of the tree. -func (smt *SparseMerkleTree) DeleteForRoot(key, root []byte) ([]byte, error) { - return smt.UpdateForRoot(key, defaultValue, root) +// Deletes the key-value mapping from the tree and returns the new root +func (smt *SparseMerkleTree) Delete(key []byte) ([]byte, error) { + return smt.Update(key, defaultValue) } -func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte, pathNodes [][]byte, oldLeafData []byte) ([]byte, error) { +func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes, pathNodes [][]byte, oldLeafData []byte) ([]byte, error) { + // Checking if the first node of the path (i.e. the root) is a placeholder if bytes.Equal(pathNodes[0], smt.th.placeholder()) { - // This key is already empty as it is a placeholder; return an error. return nil, errKeyAlreadyEmpty } - actualPath, _ := smt.th.parseLeaf(oldLeafData) - if !bytes.Equal(path, actualPath) { - // This key is already empty as a different key was found its place; return an error. + + oldPath, _ := smt.th.parseLeaf(oldLeafData) + if !bytes.Equal(path, oldPath) { + // The node path to the old leaf does not exist, which means the key is already empty return nil, errKeyAlreadyEmpty } + // All nodes above the deleted leaf are now orphaned for _, node := range pathNodes { if err := smt.nodes.Delete(node); err != nil { @@ -174,7 +175,7 @@ func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte if smt.th.isLeaf(sideNodeValue) { // This is the leaf sibling that needs to be bubbled up the tree. currentHash = sideNode - currentData = sideNode + currentData = sideNode // ASK(REVIEWER): Why are we settings hash and data to the same value? continue } else { // This is the node sibling that needs to be left in its place. @@ -193,6 +194,8 @@ func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte nonPlaceholderReached = true } + // Determine if `currentData` is currently a left or right child. + // ASK(REVIEWER): Unclear how getting this specific bit determines the orientation of the tree if getBitAtFromMSB(path, len(sideNodes)-1-i) == right { currentHash, currentData = smt.th.digestNode(sideNode, currentData) } else { @@ -201,7 +204,7 @@ func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte if err := smt.nodes.Set(currentHash, currentData); err != nil { return nil, err } - currentData = currentHash + currentData = currentHash // ASK(REVIEWER): Unclear why we're doing this } if currentHash == nil { @@ -211,17 +214,17 @@ func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte return currentHash, nil } -func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, sideNodes [][]byte, pathNodes [][]byte, oldLeafData []byte) ([]byte, error) { +func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, pathNodes [][]byte, oldLeafData []byte) ([]byte, error) { valueHash := smt.th.digest(value) currentHash, currentData := smt.th.digestLeaf(path, valueHash) if err := smt.nodes.Set(currentHash, currentData); err != nil { return nil, err } - currentData = currentHash + currentData = currentHash // ASK(REVIEWER): Again, trying to understand this logic // If the leaf node that sibling nodes lead to has a different actual path // than the leaf node being updated, we need to create an intermediate node - // with this leaf node and the new leaf node as children. + // with this leaf node and the new leaf nodes as children. // // First, get the number of bits that the paths of the two leaf nodes share // in common as a prefix. @@ -234,18 +237,18 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side actualPath, oldValueHash = smt.th.parseLeaf(oldLeafData) commonPrefixCount = countCommonPrefix(path, actualPath) } + // ASK(reviewer): I don't fully understand why the # of bits is the max depth - depends on the k-ary of the tree if commonPrefixCount != smt.depth() { + // TODO: Need to understand / visualize the business logic here too if getBitAtFromMSB(path, commonPrefixCount) == right { currentHash, currentData = smt.th.digestNode(pathNodes[0], currentData) } else { currentHash, currentData = smt.th.digestNode(currentData, pathNodes[0]) } - err := smt.nodes.Set(currentHash, currentData) - if err != nil { + if err := smt.nodes.Set(currentHash, currentData); err != nil { return nil, err } - currentData = currentHash } else if oldValueHash != nil { // Short-circuit if the same value is being set @@ -256,10 +259,12 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side if err := smt.nodes.Delete(pathNodes[0]); err != nil { return nil, err } + // TODO: Need to understand / visualize the different between path & pathNodes if err := smt.values.Delete(path); err != nil { return nil, err } } + // All remaining path nodes are orphaned for i := 1; i < len(pathNodes); i++ { if err := smt.nodes.Delete(pathNodes[i]); err != nil { @@ -276,7 +281,7 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side if i-offsetOfSideNodes < 0 || sideNodes[i-offsetOfSideNodes] == nil { if commonPrefixCount != smt.depth() && commonPrefixCount > smt.depth()-1-i { - // If there are no sidenodes at this height, but the number of + // If there are no sideNodes at this height, but the number of // bits that the paths of the two leaf nodes share in common is // greater than this depth, then we need to build up the tree // to this depth with placeholder values at siblings. @@ -293,12 +298,13 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side } else { currentHash, currentData = smt.th.digestNode(currentData, sideNode) } - err := smt.nodes.Set(currentHash, currentData) - if err != nil { + + if err := smt.nodes.Set(currentHash, currentData); err != nil { return nil, err } currentData = currentHash } + if err := smt.values.Set(path, value); err != nil { return nil, err } @@ -306,20 +312,17 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path []byte, value []byte, side return currentHash, nil } -// Get all the sibling nodes (sidenodes) for a given path from a given root. -// Returns an array of sibling nodes, the leaf hash found at that path, the -// leaf data, and the sibling data. -// -// If the leaf is a placeholder, the leaf data is nil. -func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSiblingData bool) ([][]byte, [][]byte, []byte, []byte, error) { +// Get all the side nodes (a.k.a. sibling nodes) for a given path from a given root. +func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pathNodes [][]byte, nodeData, sideNode []byte, err error) { // Side nodes for the path. Nodes are inserted in reverse order, then the // slice is reversed at the end. - sideNodes := make([][]byte, 0, smt.depth()) - pathNodes := make([][]byte, 0, smt.depth()+1) + smtDepth := smt.depth() + sideNodes = make([][]byte, 0, smtDepth) // ASK(reviewer): this should be a function of the k-ary of the tree, not the depth + pathNodes = make([][]byte, 0, smtDepth) pathNodes = append(pathNodes, root) if bytes.Equal(root, smt.th.placeholder()) { - // If the root is a placeholder, there are no sidenodes to return. + // If the root is a placeholder, there are no sideNodes to return. // Let the "actual path" be the input path. return sideNodes, pathNodes, nil, nil, nil } @@ -327,18 +330,23 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli currentData, err := smt.nodes.Get(root) if err != nil { return nil, nil, nil, nil, err - } else if smt.th.isLeaf(currentData) { - // If the root is a leaf, there are also no sidenodes to return. + } + // If the root is a leaf, there are no sideNodes to return. + if smt.th.isLeaf(currentData) { return sideNodes, pathNodes, currentData, nil, nil } var nodeHash []byte - var sideNode []byte - var siblingData []byte - for i := 0; i < smt.depth(); i++ { + for i := 0; i < smtDepth; i++ { + // ASK(reviewer): This part is really confusing. + // 1. `parseNode` returns (path, data) + // 2. We interpret this return value at (leftNode, rightNode) + // 3. We use these two values as (sideNode, nodeHash) + // 4. We append these values to (sideNode, pathNode) leftNode, rightNode := smt.th.parseNode(currentData) - // Get sidenode depending on whether the path bit is on or off. + // Get sideNode depending on whether the path bit is on or off. + // parseNodewer): This part is not documented well if getBitAtFromMSB(path, i) == right { sideNode = leftNode nodeHash = rightNode @@ -349,8 +357,8 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli sideNodes = append(sideNodes, sideNode) pathNodes = append(pathNodes, nodeHash) + // If the node is a placeholder, we've reached the end. if bytes.Equal(nodeHash, smt.th.placeholder()) { - // If the node is a placeholder, we've reached the end. currentData = nil break } @@ -358,19 +366,17 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli currentData, err = smt.nodes.Get(nodeHash) if err != nil { return nil, nil, nil, nil, err - } else if smt.th.isLeaf(currentData) { - // If the node is a leaf, we've reached the end. + } + // If the node is a leaf, we've reached the end. + if smt.th.isLeaf(currentData) { break } } - if getSiblingData { - siblingData, err = smt.nodes.Get(sideNode) - if err != nil { - return nil, nil, nil, nil, err - } - } - return reverseByteSlices(sideNodes), reverseByteSlices(pathNodes), currentData, siblingData, nil + sideNodes = reverseByteSlices(sideNodes) + pathNodes = reverseByteSlices(pathNodes) + + return sideNodes, pathNodes, currentData, sideNode, nil } // Prove generates a Merkle proof for a key against the current root. @@ -379,8 +385,7 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli // the leaf may be updated (e.g. in a state transition fraud proof). For // updatable proofs, see ProveUpdatable. func (smt *SparseMerkleTree) Prove(key []byte) (SparseMerkleProof, error) { - proof, err := smt.ProveForRoot(key, smt.Root()) - return proof, err + return smt.ProveForRoot(key, smt.Root()) } // ProveForRoot generates a Merkle proof for a key, against a specific node. @@ -389,29 +394,36 @@ func (smt *SparseMerkleTree) Prove(key []byte) (SparseMerkleProof, error) { // This proof can be used for read-only applications, but should not be used if // the leaf may be updated (e.g. in a state transition fraud proof). For // updatable proofs, see ProveUpdatableForRoot. -func (smt *SparseMerkleTree) ProveForRoot(key []byte, root []byte) (SparseMerkleProof, error) { +func (smt *SparseMerkleTree) ProveForRoot(key, root []byte) (SparseMerkleProof, error) { return smt.doProveForRoot(key, root, false) } // ProveUpdatable generates an updatable Merkle proof for a key against the current root. func (smt *SparseMerkleTree) ProveUpdatable(key []byte) (SparseMerkleProof, error) { - proof, err := smt.ProveUpdatableForRoot(key, smt.Root()) - return proof, err + return smt.ProveUpdatableForRoot(key, smt.Root()) } // ProveUpdatableForRoot generates an updatable Merkle proof for a key, against a specific node. // This is primarily useful for generating Merkle proofs for subtrees. -func (smt *SparseMerkleTree) ProveUpdatableForRoot(key []byte, root []byte) (SparseMerkleProof, error) { +func (smt *SparseMerkleTree) ProveUpdatableForRoot(key, root []byte) (SparseMerkleProof, error) { return smt.doProveForRoot(key, root, true) } -func (smt *SparseMerkleTree) doProveForRoot(key []byte, root []byte, isUpdatable bool) (SparseMerkleProof, error) { +func (smt *SparseMerkleTree) doProveForRoot(key, root []byte, isUpdatable bool) (SparseMerkleProof, error) { path := smt.th.path(key) - sideNodes, pathNodes, leafData, siblingData, err := smt.sideNodesForRoot(path, root, isUpdatable) + sideNodes, pathNodes, leafData, sideNode, err := smt.sideNodesForRoot(path, root) if err != nil { return SparseMerkleProof{}, err } + var siblingData []byte + if isUpdatable { + siblingData, err = smt.nodes.Get(sideNode) + if err != nil { + return SparseMerkleProof{}, err + } + } + var nonEmptySideNodes [][]byte for _, v := range sideNodes { if v != nil { @@ -437,13 +449,12 @@ func (smt *SparseMerkleTree) doProveForRoot(key []byte, root []byte, isUpdatable SiblingData: siblingData, } - return proof, err + return proof, nil } // ProveCompact generates a compacted Merkle proof for a key against the current root. func (smt *SparseMerkleTree) ProveCompact(key []byte) (SparseCompactMerkleProof, error) { - proof, err := smt.ProveCompactForRoot(key, smt.Root()) - return proof, err + return smt.ProveCompactForRoot(key, smt.Root()) } // ProveCompactForRoot generates a compacted Merkle proof for a key, at a specific root. @@ -452,6 +463,5 @@ func (smt *SparseMerkleTree) ProveCompactForRoot(key []byte, root []byte) (Spars if err != nil { return SparseCompactMerkleProof{}, err } - compactedProof, err := CompactProof(proof, smt.th.hasher) - return compactedProof, err + return CompactProof(proof, smt.th.hasher) } diff --git a/smt_test.go b/smt_test.go index 43f4b39..9b285bd 100644 --- a/smt_test.go +++ b/smt_test.go @@ -12,6 +12,7 @@ import ( func TestSparseMerkleTreeUpdateBasic(t *testing.T) { smn, smv := NewSimpleMap(), NewSimpleMap() smt := NewSparseMerkleTree(smn, smv, sha256.New()) + var value []byte var has bool var err error @@ -467,7 +468,7 @@ func (h *dummyHasher) BlockSize() int { return h.Size() } -func TestOrphanRemoval(t *testing.T) { +func TestSparseMerkleTreeOrphanRemoval(t *testing.T) { var smn, smv *SimpleMap var smt *SparseMerkleTree var err error diff --git a/treehasher.go b/treehasher.go index eda798e..2567101 100644 --- a/treehasher.go +++ b/treehasher.go @@ -5,12 +5,33 @@ import ( "hash" ) -var leafPrefix = []byte{0} -var nodePrefix = []byte{1} +// ASK(reviewer): Help clarify terminology: +// - Terms used through the codebase: `data`, `value`, `hash`, `path`, `digest` +// - Current understanding: +// - `data` is simply `valueHash` determined using the `digest` function. +// - `hash`` is synonymous to `digest` +// - Leaf `value`: (prefix, path, userDataDigest) +// - Node `value`: (prefix, leftData, rightData) + +// From the whitepaper: +// Leaf node is a node that stores user value at the bottom of the tree. Besides the data, it also +// contains the key used for querying the tree of the node and the digest of the data. The nibble +// path field of a leaf node key must be a prefix of its key. + +var ( + leafPrefix = []byte{0} // prefix used for the path of each leaf in the three + nodePrefix = []byte{1} // prefix used for the path of each node in the three +) + +// type Node struct { +// isLeaf bool +// hash []byte +// data []byte +// } type treeHasher struct { hasher hash.Hash - zeroValue []byte + zeroValue []byte // ASK(reviewer): rename to `emptyNode`? } func newTreeHasher(hasher hash.Hash) *treeHasher { @@ -20,53 +41,56 @@ func newTreeHasher(hasher hash.Hash) *treeHasher { return &th } +func (th *treeHasher) path(key []byte) []byte { + return th.digest(key) +} + func (th *treeHasher) digest(data []byte) []byte { + // TODO: Need to add a lock inside of `treeHasher` to avoid race conditions here. th.hasher.Write(data) sum := th.hasher.Sum(nil) th.hasher.Reset() return sum } -func (th *treeHasher) path(key []byte) []byte { - return th.digest(key) -} - -func (th *treeHasher) digestLeaf(path []byte, leafData []byte) ([]byte, []byte) { - value := make([]byte, 0, len(leafPrefix)+len(path)+len(leafData)) +// value: (prefix, path, userDataDigest) +// Alternative interpretations of return value: +// (hash, value) +// (hash(value), value) +// (valueHash, value) +// (valueDigest, value) +func (th *treeHasher) digestLeaf(path, data []byte) (hash, value []byte) { + value = make([]byte, 0, len(leafPrefix)+len(path)+len(data)) value = append(value, leafPrefix...) value = append(value, path...) - value = append(value, leafData...) - - th.hasher.Write(value) - sum := th.hasher.Sum(nil) - th.hasher.Reset() + value = append(value, data...) - return sum, value + return th.digest(value), value } -func (th *treeHasher) parseLeaf(data []byte) ([]byte, []byte) { - return data[len(leafPrefix) : th.pathSize()+len(leafPrefix)], data[len(leafPrefix)+th.pathSize():] +func (th *treeHasher) parseLeaf(value []byte) (path, data []byte) { + path = value[len(leafPrefix) : th.pathSize()+len(leafPrefix)] + data = value[len(leafPrefix)+th.pathSize():] + return } func (th *treeHasher) isLeaf(data []byte) bool { return bytes.Equal(data[:len(leafPrefix)], leafPrefix) } -func (th *treeHasher) digestNode(leftData []byte, rightData []byte) ([]byte, []byte) { - value := make([]byte, 0, len(nodePrefix)+len(leftData)+len(rightData)) +func (th *treeHasher) digestNode(leftData, rightData []byte) (hash, value []byte) { + value = make([]byte, 0, len(nodePrefix)+len(leftData)+len(rightData)) value = append(value, nodePrefix...) value = append(value, leftData...) value = append(value, rightData...) - th.hasher.Write(value) - sum := th.hasher.Sum(nil) - th.hasher.Reset() - - return sum, value + return th.digest(value), value } -func (th *treeHasher) parseNode(data []byte) ([]byte, []byte) { - return data[len(nodePrefix) : th.pathSize()+len(nodePrefix)], data[len(nodePrefix)+th.pathSize():] +func (th *treeHasher) parseNode(value []byte) (leftData, rightData []byte) { + leftData = value[len(nodePrefix) : th.pathSize()+len(nodePrefix)] + rightData = value[len(nodePrefix)+th.pathSize():] + return } func (th *treeHasher) pathSize() int { diff --git a/treehasher_test.go b/treehasher_test.go new file mode 100644 index 0000000..dc9e021 --- /dev/null +++ b/treehasher_test.go @@ -0,0 +1,71 @@ +package smt + +import ( + "bytes" + "crypto/sha256" + "crypto/sha512" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTreeHasherPath(t *testing.T) { + keyStr := "jellyfish" + expectedPathEncoded := "a0ea5328f032f1557fbc5d6516c59cc85e7c0fa270c43085f9c994ef2915449b" + + th := newTreeHasher(sha256.New()) + digest := th.digest([]byte(keyStr)) + + require.Equal(t, expectedPathEncoded, hex.EncodeToString(digest)) +} + +func TestTreeHasherLeaf(t *testing.T) { + path := []byte("i am a leaf") + data := []byte("i am leaf data") + expectedHashEncoded := "c8b12b6fd50318a81b6bbfd1f0871c76d957930cd6dbec5436f8e8e237575e6b" + expectedValueEncoded := "006920616d2061206c6561666920616d206c6561662064617461" + + th := newTreeHasher(sha256.New()) + hash, value := th.digestLeaf(path, data) + require.Equal(t, []byte{0}, value[:1]) // verify prefix + require.Equal(t, expectedHashEncoded, hex.EncodeToString(hash)) + require.Equal(t, expectedValueEncoded, hex.EncodeToString(value)) +} + +func TestTreeHasherNode(t *testing.T) { + path := []byte("i am a node") + data := []byte("i am node data") + expectedHashEncoded := "6435f669879aee1a3426a7f98b242f9efc84de4e1d1f710cfc953795b9bf3a6d" + expectedValueEncoded := "016920616d2061206e6f64656920616d206e6f64652064617461" + + th := newTreeHasher(sha256.New()) + hash, value := th.digestNode(path, data) + require.Equal(t, []byte{1}, value[:1]) // verify prefix + require.Equal(t, expectedHashEncoded, hex.EncodeToString(hash)) + require.Equal(t, expectedValueEncoded, hex.EncodeToString(value)) +} + +func TestTreeHasherPathSize(t *testing.T) { + //sha256 + th := newTreeHasher(sha256.New()) + require.Equal(t, 32, th.pathSize()) + + //sha512 + th = newTreeHasher(sha512.New()) + require.Equal(t, 64, th.pathSize()) +} + +func TestTreeHasherPathPlaceholder(t *testing.T) { + //sha256 + th := newTreeHasher(sha256.New()) + placeholder := th.placeholder() + require.Len(t, placeholder, 32) + require.Equal(t, bytes.Repeat([]byte{0}, 32), placeholder) + + //sha512 + th = newTreeHasher(sha512.New()) + placeholder = th.placeholder() + require.Len(t, placeholder, 64) + require.Equal(t, bytes.Repeat([]byte{0}, 64), placeholder) +} diff --git a/utils.go b/utils.go index b676b66..7e3cb12 100644 --- a/utils.go +++ b/utils.go @@ -25,21 +25,18 @@ func countSetBits(data []byte) int { return count } -func countCommonPrefix(data1 []byte, data2 []byte) int { - count := 0 - for i := 0; i < len(data1)*8; i++ { - if getBitAtFromMSB(data1, i) == getBitAtFromMSB(data2, i) { - count++ - } else { +func countCommonPrefix(data1, data2 []byte) int { + i := 0 + for i = 0; i < len(data1)*8; i++ { + if getBitAtFromMSB(data1, i) != getBitAtFromMSB(data2, i) { break } } - return count + return i } func emptyBytes(length int) []byte { - b := make([]byte, length) - return b + return make([]byte, length) } func reverseByteSlices(slices [][]byte) [][]byte { diff --git a/visualize.ipynb b/visualize.ipynb new file mode 100644 index 0000000..3372227 --- /dev/null +++ b/visualize.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 121, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "G 0\n", + "I 01\n", + "F 10\n", + "H 10\n", + "B 0100\n", + "E 0100\n", + "A 1101\n", + "J 11010\n", + "D 110100\n", + "C 110100\n", + "{'0': 'internal_0', '01': 'internal_1', '1': 'internal_2', '10': 'internal_3', '010': 'internal_4', '0100': 'internal_5', '11': 'internal_6', '110': 'internal_7', '1101': 'internal_8', '11010': 'internal_9', '110100': 'internal_10'}\n", + "[('root', '0'), ('0', 'G'), ('0', '01'), ('01', 'I'), ('root', '1'), ('1', '10'), ('10', 'F'), ('10', 'H'), ('01', '010'), ('010', '0100'), ('0100', 'B'), ('0100', 'E'), ('1', '11'), ('11', '110'), ('110', '1101'), ('1101', 'A'), ('1101', '11010'), ('11010', 'J'), ('11010', '110100'), ('110100', 'D'), ('110100', 'C')]\n", + "['0', '', '1', '', '1', '0', '', '', '0', '0', '', '', '1', '0', '1', '', '0', '', '0', '', '']\n" + ] + } + ], + "source": [ + "from typing import Dict \n", + "from collections import defaultdict\n", + "\n", + "def read_bits_from_go_test(filename: str) -> Dict[str, str]:\n", + " bits = {}\n", + " with open(filename, 'r') as f:\n", + " for line in f:\n", + " key, path = line.strip().split(',')\n", + " bits[key.rstrip(\"\\x00\")] = path\n", + " bits = {k: v for k, v in sorted(bits.items(), key=lambda item: item[1])}\n", + " bits = {k: v for k, v in sorted(bits.items(), key=lambda item: len(item[1]))}\n", + " return bits\n", + "\n", + "def build_edges(prev_k, curr_k, rem_k, value, edge_map, edges, edges_label):\n", + " if curr_k not in edge_map:\n", + " edge_map[curr_k] = f\"internal_{len(edge_map)}\"\n", + " edges.append((prev_k, curr_k))\n", + " edges_label.append(curr_k[-1])\n", + "\n", + " if len(rem_k) == 0:\n", + " edges.append((curr_k, value))\n", + " edges_label.append('')\n", + " return\n", + "\n", + " build_edges(curr_k, curr_k + rem_k[0], rem_k[1:], value, edge_map, edges, edges_label)\n", + "\n", + "bits = read_bits_from_go_test(\"bits.csv\")\n", + "edge_map = {}\n", + "edges = []\n", + "edges_label = []\n", + "\n", + "for k, v in bits.items():\n", + " print(k, v)\n", + " build_edges(\"root\", v[0], v[1:], k, edge_map, edges, edges_label)\n", + "\n", + "print(edge_map)\n", + "print(edges)\n", + "print(edges_label)" + ] + }, + { + "cell_type": "code", + "execution_count": 122, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGbCAYAAABZBpPkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/av/WaAAAACXBIWXMAAA9hAAAPYQGoP6dpAACD50lEQVR4nO3dd3wT9RvA8c8l6QRK2VD2lr1BZCN7I6DIRhAUQeQHCgiywcEQZSmCIHuJILvsvTey92rZpRToSHK/P64tbelu0rTJ8+bVV9vL5XvPHendc9/7DkVVVRUhhBBCOCydrQMQQgghhG1JMiCEEEI4OEkGhBBCCAcnyYAQQgjh4CQZEEIIIRycJANCCCGEg5NkQAghhHBwkgwIIYQQDk6SASGEEMLBSTIgHNKuXbtQFIVdu3aFL+vWrRv58uVLVHkPHjygbdu2ZMqUCUVRmDp1arTbiA9FUejbt2+i4rC12rVrU7JkyTjXy5cvH926dbN+QEKIeJFkQKRq8+fPR1EUjh07ZtM4BgwYwJYtWxg6dCgLFy6kUaNGNonj5s2bKIqCoiiMGzcu2nU6duyIoiikTZs2maOzvLD//7i+EpvkCeEoDLYOQAh7sGPHDlq2bMmgQYPClxUpUoTXr1/j7Oyc7PG4urqydOlShg8fHmn5y5cvWbt2La6urskekzXUrFmThQsXRlrWs2dPKleuTK9evcKX2UPiI4Q1STIghAU8fPgQT0/PSMt0Op3NLrpNmjRh9erVnD59mjJlyoQvX7t2LcHBwTRq1IgdO3ZYbHsvX74kTZo0FisvvgoUKECBAgUiLfvss88oUKAAnTp1ivF9RqMRs9lsk0RNiJRIHhMIu3Px4kXatm1LxowZcXV1pWLFivz7778JKkNVVfLly0fLli3fei0wMJD06dPTu3fv8GpqVVWZMWNGeLU0RN8u4cqVK7Rp04bs2bPj6upKrly5aN++Pc+fP39rO2vWrKFkyZK4uLhQokQJNm/eHO/4q1atSv78+VmyZEmk5YsXL6ZRo0ZkzJgx2vdt2rSJGjVqkCZNGtKlS0fTpk3577//Iq3TrVs30qZNy7Vr12jSpAnp0qWjY8eOMcbi7e2Nu7s7H3/8MUajMcb1/Pz8+Oqrr8idOzcuLi4UKlSIH3/8EbPZHO/9jk7Yo5NJkyYxdepUChYsiIuLC+fPnwfi/3mxVnxCpARSMyDsyn///Ue1atXImTMnQ4YMIU2aNKxYsYJWrVrx999/07p163iVoygKnTp14qeffuLp06eRLp7r1q3D39+fTp06kTNnThYuXEjnzp2pX78+Xbp0ibHM4OBgGjZsSFBQEP369SN79uzcu3eP9evX4+fnR/r06cPX3bdvH6tXr6ZPnz6kS5eOX3/9lTZt2nD79m0yZcoUr334+OOPWbRoET/88AOKovD48WO8vb1ZuHBhtInFwoUL6dq1Kw0bNuTHH3/k1atXzJo1i+rVq3Py5MlIz92NRiMNGzakevXqTJo0CXd392hjWL9+PW3btuWjjz7izz//RK/XR7veq1evqFWrFvfu3aN3797kyZOHAwcOMHToUHx8fJg6dWq89jk28+bNIzAwkF69euHi4kLGjBnj/XlJjviEsClViFRs3rx5KqAePXpUVVVVff/999VSpUqpgYGB4euYzWb1vffeUwsXLhy+bOfOnSqg7ty5M3xZ165d1bx584b/funSJRVQZ82aFWmbLVq0UPPly6eazebwZYD6xRdfRFov6jZOnjypAurKlStj3SdAdXZ2Vq9evRq+7PTp0yqgTps2Ldb33rhxQwXUiRMnqufOnVMBde/evaqqquqMGTPUtGnTqi9fvlS7du2qpkmTJvx9L168UD09PdVPP/00Unm+vr5q+vTpIy3v2rWrCqhDhgx5a/u1atVSS5Qooaqqqv7999+qk5OT+umnn6omkynSennz5lW7du0a/vvYsWPVNGnSqJcvX4603pAhQ1S9Xq/evn071v2OKE2aNJHKDjsmHh4e6sOHDyOtG9/PiyXjEyIlkscEwm48ffqUHTt28OGHH/LixQseP37M48ePefLkCQ0bNuTKlSvcu3cv3uUVKVKEKlWqsHjx4kjb2LRpU3iL/IQIu/PfsmULr169inXdevXqUbBgwfDfS5cujYeHB9evX4/39kqUKEHp0qVZunQpAEuWLKFly5bR3sVv3boVPz8/Pv744/Dj9vjxY/R6PVWqVGHnzp1vvefzzz+PcdtLly7lo48+onfv3vz+++/odLGfalauXEmNGjXIkCFDpO3Xq1cPk8nEnj174r3fMWnTpg1ZsmQJ/z0hn5fkiE8IW5LHBMJuXL16FVVV+e677/juu++iXefhw4fkzJkz3mV26dKFvn37cuvWLfLmzcvKlSsJCQmh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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import networkx as nx\n", + "from networkx.drawing.nx_agraph import write_dot, graphviz_layout\n", + "import matplotlib.pyplot as plt\n", + "\n", + "G = nx.DiGraph()\n", + "\n", + "color_map = ['yellow']\n", + "G.add_node('root')\n", + "\n", + "for e in edge_map:\n", + " G.add_node(e)\n", + " color_map.append('green')\n", + "\n", + "for k in bits:\n", + " G.add_node(k)\n", + " color_map.append('red')\n", + "\n", + "for e in edges:\n", + " G.add_edge(*e)\n", + "\n", + "# same layout using matplotlib with labels\n", + "plt.title('Jellyfish Merkle Tree')\n", + "pos = graphviz_layout(G, prog='dot')\n", + "nx.draw(G, pos, with_labels=True, arrows=True, node_color=color_map)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.10.6 64-bit", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "b0fa6594d8f4cbf19f97940f81e996739fb7646882a419484c72d19e05852a7e" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}