From b574a50a5a24bd3a9c32a03cc4dc74fb37aef355 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Fri, 5 Aug 2022 10:40:21 -0400 Subject: [PATCH 01/26] Add Makefile with test_all property --- .gitignore | 1 + Makefile | 18 ++++++++++++++++++ README.md | 6 ++++++ 3 files changed, 25 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..722d5e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..294b0d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.SILENT: + +help: + printf "Available targets\n\n" + awk '/^[a-zA-Z\-\_0-9]+:/ { \ + helpMessage = match(lastLine, /^## (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \ + printf "%-30s %s\n", helpCommand, helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.PHONY: test_all +## Run all the unit tests +test_all: + go test -v -count=1 ./... \ No newline at end of file diff --git a/README.md b/README.md index e8afcc2..b06cf01 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ A Go library that implements a Sparse Merkle tree for a key-value map. The tree [![codecov](https://codecov.io/gh/celestiaorg/smt/branch/master/graph/badge.svg?token=U3GGEDSA94)](https://codecov.io/gh/celestiaorg/smt) [![GoDoc](https://godoc.org/github.com/celestiaorg/smt?status.svg)](https://godoc.org/github.com/celestiaorg/smt) +## Installation + +```bash +go get github.com/celestiaorg/smt@master +``` + ## Example ```go From cef5d99780cc7be2cc2cea82d0065be665769fa2 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 10:09:12 -0700 Subject: [PATCH 02/26] General comment updates before adding treehasher test --- Makefile | 7 +++- README.md | 59 +++++++++++++++++------------ smt.go | 103 ++++++++++++++++++++++++++++---------------------- treehasher.go | 16 ++++---- 4 files changed, 107 insertions(+), 78 deletions(-) diff --git a/Makefile b/Makefile index 294b0d0..d9a3290 100644 --- a/Makefile +++ b/Makefile @@ -15,4 +15,9 @@ help: .PHONY: test_all ## Run all the unit tests test_all: - go test -v -count=1 ./... \ No newline at end of file + go test -v -count=1 ./... + +.PHONY: test_smt +## Run all the ^TestSparseMerkleTree unit tests +test_smt: + go test -v -count=1 -run TestSparseMerkleTree ./... \ No newline at end of file diff --git a/README.md b/README.md index b06cf01..d26c435 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# smt +# Sparse Merkle Tree (smt) -A Go library that implements a Sparse Merkle tree for a key-value map. The tree implements the same optimisations specified in the [Libra whitepaper][libra whitepaper], to reduce the number of hash operations required per tree operation to O(k) where k is the number of non-empty elements in the tree. +A Go library that implements a Sparse Merkle Tree for a key-value map. The tree implements the same optimisations specified in the [Jellyfish Merkle Tree whitepaper][jmt whitepaper] originally designed for the [Libra blockchain][libra whitepaper]. It reduces the number of hash operations required per tree operation to `O(k)` where `k` is the number of non-empty elements in the tree. [![Tests](https://github.com/celestiaorg/smt/actions/workflows/test.yml/badge.svg)](https://github.com/celestiaorg/smt/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/celestiaorg/smt/branch/master/graph/badge.svg?token=U3GGEDSA94)](https://codecov.io/gh/celestiaorg/smt) @@ -18,33 +18,44 @@ go get github.com/celestiaorg/smt@master package main import ( - "crypto/sha256" - "fmt" + "crypto/sha256" + "fmt" - "github.com/celestiaorg/smt" + "github.com/celestiaorg/smt" ) func main() { - // Initialise two new key-value store to store the nodes and values of the tree - nodeStore := smt.NewSimpleMap() - valueStore := smt.NewSimpleMap() - // Initialise the tree - 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.") - } + // Initialise 2 new key-value store to stores the nodes and values of the tree + nodeStore := smt.NewSimpleMap() // Mapping from hash -> data + valueStore := smt.NewSimpleMap() // Mapping from path -> value + + // 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 +- [ ] Create types for `sideNodes`, `root`, etc... + [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 diff --git a/smt.go b/smt.go index 5dcf5b1..7a9fa16 100644 --- a/smt.go +++ b/smt.go @@ -11,23 +11,26 @@ const ( right = 1 ) -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 } -// 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 { @@ -39,66 +42,69 @@ func NewSparseMerkleTree(nodes, values MapStore, hasher hash.Hash, options ...Op 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. +// ASK(reviewer): Should this function be exposed at all? +// 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. +// `Get`` 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. +// `Has` 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 } -// Update sets a new value for a key in the tree, and sets and returns the new root of the tree. +// `Update` sets a new value for a key in the tree, 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()) if err != nil { @@ -108,12 +114,8 @@ func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { 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. +// ASK(reviewer): Should this function be exposed at all? +// `UpdateForRoot` sets a new value for a key in the tree given a specific root, and returns the new root. func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte) ([]byte, error) { path := smt.th.path(key) sideNodes, pathNodes, oldLeafData, _, err := smt.sideNodesForRoot(path, root, false) @@ -132,7 +134,6 @@ func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte if err := smt.values.Delete(path); err != nil { return nil, err } - } else { // Insert or update operation. newRoot, err = smt.updateWithSideNodes(path, value, sideNodes, pathNodes, oldLeafData) @@ -140,12 +141,19 @@ func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte return newRoot, err } -// DeleteForRoot deletes a value from tree at a specific root. It returns the new root of the tree. +// `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) +} + +// `DeleteForRoot` deletes a value from tree at a specific root. It returns the new root of the tree. +// ASK(reviewer): Why is this function exported? func (smt *SparseMerkleTree) DeleteForRoot(key, root []byte) ([]byte, error) { return smt.UpdateForRoot(key, defaultValue, root) } func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte, 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 @@ -306,20 +314,22 @@ 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. +// Get all the sibling nodes (i.e. sideNodes) for a given path from a given root. +// Returns: +// - Array of sibling nodes +// - Array of path nodes +// - The leaf data; note that if the leaf is a placeholder, the leaf data is nil. +// - The sibling data; ASK(reviewer): should this not be an array? func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSiblingData bool) ([][]byte, [][]byte, []byte, []byte, 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 } @@ -328,17 +338,18 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli 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. 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++ { 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. if getBitAtFromMSB(path, i) == right { sideNode = leftNode nodeHash = rightNode diff --git a/treehasher.go b/treehasher.go index eda798e..a80e570 100644 --- a/treehasher.go +++ b/treehasher.go @@ -5,12 +5,14 @@ import ( "hash" ) -var leafPrefix = []byte{0} -var nodePrefix = []byte{1} +var ( + leafPrefix = []byte{0} + nodePrefix = []byte{1} +) type treeHasher struct { hasher hash.Hash - zeroValue []byte + zeroValue []byte // ASK(reviewer): rename to `emptyNode`? } func newTreeHasher(hasher hash.Hash) *treeHasher { @@ -20,6 +22,10 @@ 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 { th.hasher.Write(data) sum := th.hasher.Sum(nil) @@ -27,10 +33,6 @@ func (th *treeHasher) digest(data []byte) []byte { 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 = append(value, leafPrefix...) From 0e2da020c6ff08f6f333254259988c5f589463c6 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 10:43:22 -0700 Subject: [PATCH 03/26] Added TestTreeHasherPath --- Makefile | 7 ++++++- go.mod | 2 ++ go.sum | 14 ++++++++++++++ treehasher.go | 1 + treehasher_test.go | 19 +++++++++++++++++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 go.sum create mode 100644 treehasher_test.go diff --git a/Makefile b/Makefile index d9a3290..9ad0bac 100644 --- a/Makefile +++ b/Makefile @@ -20,4 +20,9 @@ test_all: .PHONY: test_smt ## Run all the ^TestSparseMerkleTree unit tests test_smt: - go test -v -count=1 -run TestSparseMerkleTree ./... \ No newline at end of file + go test -v -count=1 -run TestSparseMerkleTree ./... + +.PHONY: test_th +## Run all the ^TestTreeHasher unit tests +test_th: + go test -v -count=1 -run TestTreeHasher ./... \ No newline at end of file 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/treehasher.go b/treehasher.go index a80e570..03b4afd 100644 --- a/treehasher.go +++ b/treehasher.go @@ -43,6 +43,7 @@ func (th *treeHasher) digestLeaf(path []byte, leafData []byte) ([]byte, []byte) sum := th.hasher.Sum(nil) th.hasher.Reset() + // return th.digest(value), value return sum, value } diff --git a/treehasher_test.go b/treehasher_test.go new file mode 100644 index 0000000..e9c5c1d --- /dev/null +++ b/treehasher_test.go @@ -0,0 +1,19 @@ +package smt + +import ( + "crypto/sha256" + "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)) +} From fed5f94d34d55c6bc699edd809b2fc33af9796a5 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 10:44:07 -0700 Subject: [PATCH 04/26] Slight simplifcation to digestLeaf --- treehasher.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/treehasher.go b/treehasher.go index 03b4afd..d3250c7 100644 --- a/treehasher.go +++ b/treehasher.go @@ -39,12 +39,7 @@ func (th *treeHasher) digestLeaf(path []byte, leafData []byte) ([]byte, []byte) value = append(value, path...) value = append(value, leafData...) - th.hasher.Write(value) - sum := th.hasher.Sum(nil) - th.hasher.Reset() - - // return th.digest(value), value - return sum, value + return th.digest(value), value } func (th *treeHasher) parseLeaf(data []byte) ([]byte, []byte) { From 915a4e910dce8b1e16bc61aef327c47f09664d0c Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:25:29 -0700 Subject: [PATCH 05/26] Added tests and updated code for treeHasher --- treehasher.go | 31 +++++++++++++-------------- treehasher_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 16 deletions(-) diff --git a/treehasher.go b/treehasher.go index d3250c7..1845bef 100644 --- a/treehasher.go +++ b/treehasher.go @@ -5,9 +5,11 @@ import ( "hash" ) +// TODO: Need to document the difference between `data`, `value`, `hash`, and `path`.` + var ( - leafPrefix = []byte{0} - nodePrefix = []byte{1} + 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 treeHasher struct { @@ -27,44 +29,41 @@ func (th *treeHasher) path(key []byte) []byte { } 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) digestLeaf(path []byte, leafData []byte) ([]byte, []byte) { - value := make([]byte, 0, len(leafPrefix)+len(path)+len(leafData)) +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...) + value = append(value, data...) 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) { + return value[len(leafPrefix) : th.pathSize()+len(leafPrefix)], value[len(leafPrefix)+th.pathSize():] } 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) (path, data []byte) { + return value[len(nodePrefix) : th.pathSize()+len(nodePrefix)], value[len(nodePrefix)+th.pathSize():] } func (th *treeHasher) pathSize() int { diff --git a/treehasher_test.go b/treehasher_test.go index e9c5c1d..dc9e021 100644 --- a/treehasher_test.go +++ b/treehasher_test.go @@ -1,7 +1,9 @@ package smt import ( + "bytes" "crypto/sha256" + "crypto/sha512" "encoding/hex" "testing" @@ -17,3 +19,53 @@ func TestTreeHasherPath(t *testing.T) { 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) +} From 0d8e5f1d4ecf6dc656f20d752d53bc8108155793 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:31:33 -0700 Subject: [PATCH 06/26] Do not expose updateForRoot function --- smt.go | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/smt.go b/smt.go index 7a9fa16..4fc0587 100644 --- a/smt.go +++ b/smt.go @@ -58,20 +58,19 @@ func (smt *SparseMerkleTree) Root() []byte { return smt.root } -// ASK(reviewer): Should this function be exposed at all? // Sets the root of the tree. -func (smt *SparseMerkleTree) SetRoot(root []byte) { +func (smt *SparseMerkleTree) SetRoot(root []byte) { // ASK(reviewer): Should this function be exposed at all? 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) +// DISCUSS: 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() @@ -98,15 +97,15 @@ func (smt *SparseMerkleTree) Get(key []byte) ([]byte, 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 returns the new root of the tree. +// Sets a new value for a key in the tree, 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()) + newRoot, err := smt.updateForRoot(key, value, smt.Root()) if err != nil { return nil, err } @@ -114,9 +113,8 @@ func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { return newRoot, nil } -// ASK(reviewer): Should this function be exposed at all? -// `UpdateForRoot` sets a new value for a key in the tree given a specific root, and returns the new root. -func (smt *SparseMerkleTree) UpdateForRoot(key []byte, value []byte, root []byte) ([]byte, error) { +// `updateForRoot` sets a new value for a key in the tree given a specific root, and returns the new root. +func (smt *SparseMerkleTree) updateForRoot(key []byte, value []byte, root []byte) ([]byte, error) { path := smt.th.path(key) sideNodes, pathNodes, oldLeafData, _, err := smt.sideNodesForRoot(path, root, false) if err != nil { @@ -149,7 +147,7 @@ func (smt *SparseMerkleTree) Delete(key []byte) ([]byte, error) { // `DeleteForRoot` deletes a value from tree at a specific root. It returns the new root of the tree. // ASK(reviewer): Why is this function exported? func (smt *SparseMerkleTree) DeleteForRoot(key, root []byte) ([]byte, error) { - return smt.UpdateForRoot(key, defaultValue, root) + return smt.updateForRoot(key, defaultValue, root) } func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte, pathNodes [][]byte, oldLeafData []byte) ([]byte, error) { From 9cbd11d989ccb0a5b1aaa1444e9ba186a403fd04 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:32:07 -0700 Subject: [PATCH 07/26] Do not expose setRoot function --- smt.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smt.go b/smt.go index 4fc0587..e2ee35d 100644 --- a/smt.go +++ b/smt.go @@ -37,7 +37,7 @@ func NewSparseMerkleTree(nodes, values MapStore, hasher hash.Hash, options ...Op option(&smt) } - smt.SetRoot(smt.th.placeholder()) + smt.setRoot(smt.th.placeholder()) return &smt } @@ -59,7 +59,7 @@ func (smt *SparseMerkleTree) Root() []byte { } // Sets the root of the tree. -func (smt *SparseMerkleTree) SetRoot(root []byte) { // ASK(reviewer): Should this function be exposed at all? +func (smt *SparseMerkleTree) setRoot(root []byte) { smt.root = root } @@ -109,7 +109,7 @@ func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { if err != nil { return nil, err } - smt.SetRoot(newRoot) + smt.setRoot(newRoot) return newRoot, nil } From 0e0808b777d8db1392eee0e5c7567e191ac45563 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:37:08 -0700 Subject: [PATCH 08/26] Do not expose deleteForRoot function --- smt.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/smt.go b/smt.go index e2ee35d..b2f65ad 100644 --- a/smt.go +++ b/smt.go @@ -104,7 +104,7 @@ func (smt *SparseMerkleTree) Has(key []byte) (bool, error) { } // Sets a new value for a key in the tree, and returns the new root of the tree. -func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { +func (smt *SparseMerkleTree) Update(key, value []byte) ([]byte, error) { newRoot, err := smt.updateForRoot(key, value, smt.Root()) if err != nil { return nil, err @@ -113,8 +113,8 @@ func (smt *SparseMerkleTree) Update(key []byte, value []byte) ([]byte, error) { return newRoot, nil } -// `updateForRoot` sets a new value for a key in the tree given 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) if err != nil { @@ -139,14 +139,14 @@ func (smt *SparseMerkleTree) updateForRoot(key []byte, value []byte, root []byte return newRoot, err } -// `Delete`` deletes a value from tree. It returns the new root of the tree. +// 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) } -// `DeleteForRoot` deletes a value from tree at a specific root. It returns the new root of the tree. +// `deleteForRoot` deletes a value from tree at a specific root. It returns the new root of the tree. // ASK(reviewer): Why is this function exported? -func (smt *SparseMerkleTree) DeleteForRoot(key, root []byte) ([]byte, error) { +func (smt *SparseMerkleTree) deleteForRoot(key, root []byte) ([]byte, error) { return smt.updateForRoot(key, defaultValue, root) } From 1ce3acc6bd6a2aa8ed332eb35e1d1663ef0206d6 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:38:46 -0700 Subject: [PATCH 09/26] Delete deleteForRoot altogether --- smt.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/smt.go b/smt.go index b2f65ad..21f7472 100644 --- a/smt.go +++ b/smt.go @@ -144,12 +144,6 @@ func (smt *SparseMerkleTree) Delete(key []byte) ([]byte, error) { return smt.Update(key, defaultValue) } -// `deleteForRoot` deletes a value from tree at a specific root. It returns the new root of the tree. -// ASK(reviewer): Why is this function exported? -func (smt *SparseMerkleTree) deleteForRoot(key, root []byte) ([]byte, error) { - return smt.updateForRoot(key, defaultValue, root) -} - func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes [][]byte, 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()) { From 3ea32eb31c1c7ff0c2f963d16e2c5cd4cfb545d7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 11:58:18 -0700 Subject: [PATCH 10/26] Added questions to deleteWithSideNodes --- smt.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/smt.go b/smt.go index 21f7472..8f883e5 100644 --- a/smt.go +++ b/smt.go @@ -144,17 +144,18 @@ 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 { From dcb4743b4cf1e475d35180cde1ba72baa8e12625 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 12:33:47 -0700 Subject: [PATCH 11/26] Minor optimization to countCommonPrefix --- smt.go | 6 +++--- treehasher.go | 3 ++- utils.go | 15 ++++++--------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/smt.go b/smt.go index 8f883e5..5e2e7a0 100644 --- a/smt.go +++ b/smt.go @@ -214,17 +214,17 @@ func (smt *SparseMerkleTree) deleteWithSideNodes(path []byte, sideNodes, pathNod 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. diff --git a/treehasher.go b/treehasher.go index 1845bef..dad0e44 100644 --- a/treehasher.go +++ b/treehasher.go @@ -6,7 +6,8 @@ import ( ) // TODO: Need to document the difference between `data`, `value`, `hash`, and `path`.` - +// - It seems that `data` is simply `valueHash` determined using the `digest` function. +// 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 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 { From 9fb2fdb257cd00801498e43d3487aab6c33886b7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 12:51:55 -0700 Subject: [PATCH 12/26] Specified return types for sideNodesForRoot --- smt.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/smt.go b/smt.go index 5e2e7a0..a3a8b93 100644 --- a/smt.go +++ b/smt.go @@ -8,7 +8,7 @@ import ( ) const ( - right = 1 + right = 1 // ASK(reviewer): Are we only dealing with binary trees? ) var ( @@ -63,9 +63,9 @@ func (smt *SparseMerkleTree) setRoot(root []byte) { smt.root = root } -// DISCUSS: 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) +// 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 } @@ -237,15 +237,16 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, 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 } @@ -259,10 +260,12 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, 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 { @@ -315,12 +318,12 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, // - Array of path nodes // - The leaf data; note that if the leaf is a placeholder, the leaf data is nil. // - The sibling data; ASK(reviewer): should this not be an array? -func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSiblingData bool) ([][]byte, [][]byte, []byte, []byte, error) { +func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte, getSiblingData bool) (sideNodes, pathNodes [][]byte, nodeData, siblingData []byte, err error) { // Side nodes for the path. Nodes are inserted in reverse order, then the // slice is reversed at the end. 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) + 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()) { @@ -339,7 +342,6 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli var nodeHash []byte var sideNode []byte - var siblingData []byte for i := 0; i < smtDepth; i++ { leftNode, rightNode := smt.th.parseNode(currentData) @@ -376,7 +378,11 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path []byte, root []byte, getSibli return nil, nil, nil, nil, err } } - return reverseByteSlices(sideNodes), reverseByteSlices(pathNodes), currentData, siblingData, nil + + sideNodes = reverseByteSlices(sideNodes) + pathNodes = reverseByteSlices(pathNodes) + + return sideNodes, pathNodes, currentData, siblingData, nil } // Prove generates a Merkle proof for a key against the current root. From d2f0789111ffeefde25e1b63ccccfebd18a177ba Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 12:55:08 -0700 Subject: [PATCH 13/26] Remove getSiblingData from interface in sideNodesForRoot --- bulk_test.go | 2 +- smt.go | 31 +++++++++++++------------------ 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/bulk_test.go b/bulk_test.go index 9442c11..21355f2 100644 --- a/bulk_test.go +++ b/bulk_test.go @@ -119,7 +119,7 @@ func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv *map[string]string) { largestCommonPrefix = commonPrefix } } - sideNodes, _, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root(), false) + sideNodes, _, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root()) if err != nil { t.Errorf("error: %v", err) } diff --git a/smt.go b/smt.go index a3a8b93..a573323 100644 --- a/smt.go +++ b/smt.go @@ -116,7 +116,7 @@ func (smt *SparseMerkleTree) Update(key, value []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 } @@ -312,13 +312,8 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, return currentHash, nil } -// Get all the sibling nodes (i.e. sideNodes) for a given path from a given root. -// Returns: -// - Array of sibling nodes -// - Array of path nodes -// - The leaf data; note that if the leaf is a placeholder, the leaf data is nil. -// - The sibling data; ASK(reviewer): should this not be an array? -func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte, getSiblingData bool) (sideNodes, pathNodes [][]byte, nodeData, siblingData []byte, err 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. smtDepth := smt.depth() @@ -341,7 +336,6 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte, getSiblingData } var nodeHash []byte - var sideNode []byte for i := 0; i < smtDepth; i++ { leftNode, rightNode := smt.th.parseNode(currentData) @@ -372,17 +366,10 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte, getSiblingData } } - if getSiblingData { - siblingData, err = smt.nodes.Get(sideNode) - if err != nil { - return nil, nil, nil, nil, err - } - } - sideNodes = reverseByteSlices(sideNodes) pathNodes = reverseByteSlices(pathNodes) - return sideNodes, pathNodes, currentData, siblingData, nil + return sideNodes, pathNodes, currentData, sideNode, nil } // Prove generates a Merkle proof for a key against the current root. @@ -419,11 +406,19 @@ func (smt *SparseMerkleTree) ProveUpdatableForRoot(key []byte, root []byte) (Spa func (smt *SparseMerkleTree) doProveForRoot(key []byte, 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 { From 3c5608f7b8f736b37ca18b1e48bb0eb3945465e1 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 13:02:01 -0700 Subject: [PATCH 14/26] Finished reading (but don't fully understand) sideNodesForRoot and updateWithSideNodes --- smt.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/smt.go b/smt.go index a573323..c3f6869 100644 --- a/smt.go +++ b/smt.go @@ -282,7 +282,7 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, 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. @@ -299,12 +299,13 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, } 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 } @@ -330,17 +331,18 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pat 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 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 - for i := 0; i < smtDepth; i++ { leftNode, rightNode := smt.th.parseNode(currentData) // Get sideNode depending on whether the path bit is on or off. + // ASK(reviewer): This part is not documented well if getBitAtFromMSB(path, i) == right { sideNode = leftNode nodeHash = rightNode @@ -351,8 +353,8 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pat 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 } @@ -360,8 +362,9 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pat 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 } } From f1bd40b8dc1027fb737bd47a1fa879e4664934a7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 13:08:43 -0700 Subject: [PATCH 15/26] Finished reading through smt.go --- smt.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/smt.go b/smt.go index c3f6869..2b8ea2f 100644 --- a/smt.go +++ b/smt.go @@ -381,8 +381,7 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pat // 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. @@ -391,23 +390,22 @@ 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, sideNode, err := smt.sideNodesForRoot(path, root) if err != nil { @@ -447,13 +445,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. @@ -462,6 +459,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) } From 093f1fb477ae336cf82f665e4c28ad835352726c Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 13:09:55 -0700 Subject: [PATCH 16/26] Added personal checklist --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index d26c435..b05c13b 100644 --- a/README.md +++ b/README.md @@ -59,3 +59,30 @@ Run `make` to see all the options available [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 +- [ ] ├── mapstore.go +- [ ] ├── mapstore_test.go +- [ ] ├── 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 From b0bd12803f5d97e6129d97caaca55c3f9ae38ac2 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 13:26:18 -0700 Subject: [PATCH 17/26] Very minor cleanup to MapStore --- Makefile | 7 ++++++- README.md | 7 ++++--- mapstore.go | 9 ++++----- mapstore_test.go | 14 ++++++++------ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 9ad0bac..c306d79 100644 --- a/Makefile +++ b/Makefile @@ -25,4 +25,9 @@ test_smt: .PHONY: test_th ## Run all the ^TestTreeHasher unit tests test_th: - go test -v -count=1 -run TestTreeHasher ./... \ No newline at end of file + go test -v -count=1 -run TestTreeHasher ./... + +.PHONY: test_ms +## Run all the ^TestMapStore unit tests +test_ms: + go test -v -count=1 -run TestMapStore ./... \ No newline at end of file diff --git a/README.md b/README.md index b05c13b..f2b4d92 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,9 @@ Run `make` to see all the options available ## General Improvements / TODOs -- [ ] Use the `require` test module to simplify unit tests +- [ ] 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 [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 @@ -75,8 +76,8 @@ Run `make` to see all the options available - [ ] │   └── fuzz.go - [x] ├── go.mod - [x] ├── go.sum -- [ ] ├── mapstore.go -- [ ] ├── mapstore_test.go +- [x] ├── mapstore.go +- [x] ├── mapstore_test.go - [ ] ├── options.go - [ ] ├── oss-fuzz-build.sh - [ ] ├── proofs.go 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") } From fdb7138bab1bf6ce927c56b67b0ff39cb0c89dce Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 1 Oct 2022 14:43:59 -0700 Subject: [PATCH 18/26] Don't pass around a pointer to maps in the tests --- README.md | 1 + bulk_test.go | 11 ++++++----- smt_test.go | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f2b4d92..e1edb67 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Run `make` to see all the options available - [ ] 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 [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 diff --git a/bulk_test.go b/bulk_test.go index 21355f2..0491ca0 100644 --- a/bulk_test.go +++ b/bulk_test.go @@ -21,7 +21,7 @@ func TestSparseMerkleTree(t *testing.T) { } // Test all tree operations in bulk, with specified ratio probabilities of insert, update and delete. -func bulkOperations(t *testing.T, operations int, insert int, update int, delete int) { +func bulkOperations(t *testing.T, operations, insert, update, delete int) (*SparseMerkleTree, *SimpleMap, *SimpleMap, map[string]string) { smn, smv := NewSimpleMap(), NewSimpleMap() smt := NewSparseMerkleTree(smn, smv, sha256.New()) @@ -74,12 +74,13 @@ func bulkOperations(t *testing.T, operations int, insert int, update int, delete } } - bulkCheckAll(t, smt, &kv) + bulkCheckAll(t, smt, kv) } + return smt, smn, smv, kv } -func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv *map[string]string) { - for k, v := range *kv { +func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv map[string]string) { + for k, v := range kv { value, err := smt.Get([]byte(k)) if err != nil { t.Errorf("error: %v", err) @@ -110,7 +111,7 @@ func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv *map[string]string) { // Check that the key is at the correct height in the tree. largestCommonPrefix := 0 - for k2, v2 := range *kv { + for k2, v2 := range kv { if v2 == "" { continue } 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 From e5af7278b105e70a0853a97cbf0a985f0e747c77 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 2 Oct 2022 14:55:32 -0700 Subject: [PATCH 19/26] Add questions and improve readability in smt.go --- smt.go | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/smt.go b/smt.go index 2b8ea2f..0a7ff10 100644 --- a/smt.go +++ b/smt.go @@ -20,9 +20,10 @@ var ( ) 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 } // `Creates a new Sparse Merkle tree on an empty MapStore @@ -121,20 +122,19 @@ func (smt *SparseMerkleTree) updateForRoot(key, value, root []byte) ([]byte, err 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 - } - } else { - // Insert or update operation. - newRoot, err = smt.updateWithSideNodes(path, value, sideNodes, pathNodes, oldLeafData) + // Insert or update operation. + if !bytes.Equal(value, defaultValue) { + return 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 } @@ -339,10 +339,15 @@ func (smt *SparseMerkleTree) sideNodesForRoot(path, root []byte) (sideNodes, pat var nodeHash []byte 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. - // ASK(reviewer): This part is not documented well + // parseNodewer): This part is not documented well if getBitAtFromMSB(path, i) == right { sideNode = leftNode nodeHash = rightNode From 88ec5fa157189ff6e72aea7b6752bee85692a0c6 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 2 Oct 2022 14:55:50 -0700 Subject: [PATCH 20/26] Add questions and improve readability in treehasher.go --- treehasher.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/treehasher.go b/treehasher.go index dad0e44..7d8e81f 100644 --- a/treehasher.go +++ b/treehasher.go @@ -5,9 +5,19 @@ import ( "hash" ) -// TODO: Need to document the difference between `data`, `value`, `hash`, and `path`.` -// - It seems that `data` is simply `valueHash` determined using the `digest` function. -// +// 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 From 8a1793bc03f774ed89410884979689734aa01729 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 2 Oct 2022 14:56:23 -0700 Subject: [PATCH 21/26] Add TODOs in README --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1edb67..8d5ca53 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ import ( func main() { // Initialise 2 new key-value store to stores the nodes and values of the tree - nodeStore := smt.NewSimpleMap() // Mapping from hash -> data - valueStore := smt.NewSimpleMap() // Mapping from path -> value + nodeStore := smt.NewSimpleMap() // Mapping from hash -> 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()) @@ -58,6 +58,9 @@ Run `make` to see all the options available - [ ] 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 From 77c9425024e56829968ad2c600c1dadd9db2fff7 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 2 Oct 2022 15:55:55 -0700 Subject: [PATCH 22/26] Commit before improving readability of updateWithSideNodes --- smt.go | 5 ++++- treehasher.go | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/smt.go b/smt.go index 0a7ff10..590d630 100644 --- a/smt.go +++ b/smt.go @@ -4,6 +4,7 @@ package smt import ( "bytes" "errors" + "fmt" "hash" ) @@ -237,8 +238,10 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, actualPath, oldValueHash = smt.th.parseLeaf(oldLeafData) commonPrefixCount = countCommonPrefix(path, actualPath) } + fmt.Println("OLSH commonPrefixCount", commonPrefixCount) // 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() { + fmt.Println("OLSH 1") // TODO: Need to understand / visualize the business logic here too if getBitAtFromMSB(path, commonPrefixCount) == right { currentHash, currentData = smt.th.digestNode(pathNodes[0], currentData) @@ -249,7 +252,6 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, 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 @@ -277,6 +279,7 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, // Note: i-offsetOfSideNodes is the index into sideNodes[] offsetOfSideNodes := smt.depth() - len(sideNodes) + fmt.Println("OLSH 3", offsetOfSideNodes) for i := 0; i < smt.depth(); i++ { var sideNode []byte diff --git a/treehasher.go b/treehasher.go index 7d8e81f..ba05f64 100644 --- a/treehasher.go +++ b/treehasher.go @@ -47,6 +47,12 @@ func (th *treeHasher) digest(data []byte) []byte { return sum } +// 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...) From ee24777ec161f39649fd78c8526bd3da34f92537 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 23 Oct 2022 13:48:52 -0700 Subject: [PATCH 23/26] INterim commit before dsplot --- bits.csv | Bin 0 -> 96 bytes bulk_test.go | 48 +- olsh_test.go | 232 ++++ treehasher.go | 16 +- visualize.ipynb | 2772 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3046 insertions(+), 22 deletions(-) create mode 100644 bits.csv create mode 100644 olsh_test.go create mode 100644 visualize.ipynb diff --git a/bits.csv b/bits.csv new file mode 100644 index 0000000000000000000000000000000000000000..59aad0145360fdf4aa054ccd9d83869db6c16547 GIT binary patch literal 96 zcmZ=~U|`TO;PM1hhFq=?+Q5Lz3&b)sG%z&aa)dFtoM9}W0v9B<8<=gt 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()) - 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/olsh_test.go b/olsh_test.go new file mode 100644 index 0000000..d0ef805 --- /dev/null +++ b/olsh_test.go @@ -0,0 +1,232 @@ +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 TestOlshansky(t *testing.T) { + smt, smn, smv, kv := bulkOperations(t, 20, 10, 5, 5) + fmt.Println("OLSH", len(smn.m), len(smv.m), len(kv), smt.Root()) + /* + // Loop over nodes map + for hash, node := range smn.m { + // smt.th.digest() + // fmt.Println(hash, " | ", node) + // value, err := smt.Get([]byte(hash)) + // fmt.Println("OLSH", value, err) + fmt.Println("OLSH1", hex.EncodeToString([]byte(hash))) + path, _ := smt.th.parseLeaf([]byte(node)) + // path2, _ := smt.th.parseNode([]byte(node)) + fmt.Println("OLSH11", hex.EncodeToString([]byte(path))) + fmt.Println("OLSH1", hex.EncodeToString(smt.th.digest([]byte(hash)))) + // break + } + fmt.Println() + + // Full matches from OLSH1 -> OLSH11 + // Full matches from OLSH2 -> OLSH11) + // Full matches from OLSH33 -> OLSH22 + // Full matches from OLSH333 -> OLSH2 (and in turn OLSH11 + + // Loop over values map + for path, value := range smv.m { + // fmt.Println(path, " | ", value) + // value, err := smt.Get([]byte(path)) + // fmt.Println("OLSH", value, err) + fmt.Println("OLSH2", hex.EncodeToString([]byte(path))) + fmt.Println("OLSH22", hex.EncodeToString([]byte(value))) + fmt.Println("OLSH222", hex.EncodeToString(smt.th.digest([]byte(value)))) + // break + } + fmt.Println() + + // Loop over kv map + var key string + for key, value := range kv { + // fmt.Println(key, " | ", value) + // value, err := smt.Get([]byte(key)) + // fmt.Println("OLSH", value, err) + fmt.Println("OLSH3", hex.EncodeToString([]byte(key))) + fmt.Println("OLSH33", hex.EncodeToString([]byte(value))) + fmt.Println("OLSH333", hex.EncodeToString(smt.th.path([]byte(key)))) + + // break + } + + sideNodes, pathNodes, nodeData, sideNode, err := smt.sideNodesForRoot(smt.th.path([]byte(key)), smt.root) + require.NoError(t, err) + fmt.Println("----------------") + fmt.Print("OLSH", len(sideNodes), len(pathNodes), "-------", len(nodeData), len(sideNode)) + */ + var key string + for key, value := range kv { + fmt.Println(key, " | ", value) + break + } + sideNodes, pathNodes, nodeData, sideNode, err := smt.sideNodesForRoot(smt.th.path([]byte(key)), smt.root) + require.NoError(t, err) + fmt.Println("OLSH", smt.depth(), len(sideNodes), len(pathNodes), "-------", len(nodeData), len(sideNode)) + + // for + + // value + // left + // right + // leaf? + // node? +} + +func TestOlshansky2(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) + } + fmt.Println("OLSH KV", kv) + + // var key string + for key, value := range kv { + path := smt.th.path([]byte(key)) + + sideNodes, pathNodes, nodeData, _, err := smt.sideNodesForRoot(path, smt.root) + if smt.th.isLeaf([]byte(nodeData)) { + // input: (key, value) + + // path = smt.th.digest([]byte(key)) + // valueHash := smt.th.digest(value) + // currentHash, currentData := smt.th.digestLeaf(path, valueHash) + + // smt.nodes.Set(currentHash, currentData) + // smt.values.Set(path, value) + + // value = append(value, leafPrefix...) + // value = append(value, path...) + // value = append(value, data...) + // return th.digest(value), value + + // fmt.Println("LEAF: ", value) + // leafPath, leafData := smt.th.parseLeaf(nodeData) + // value1, err := smt.Get(leafData) + // require.NoError(t, err) + // value2, err := smt.Get(leafPath) + // require.NoError(t, err) + // value3, err := smt.Get([]byte(key)) + // require.NoError(t, err) + // fmt.Println("Leaf data: ", value, string(value3), leafData, leafPath) + // fmt.Println("Leaf data: ", value) + + } else { + // fmt.Println("NODE: ", value) + } + + // How do I map from node to value? + + var sb strings.Builder + fmt.Print(fmt.Sprintf("OLSH bits (%s): ", value)) + largestCommonPrefixLen := getLargestCommonPrefix(t, smt, kv, key) + for i := 0; i < largestCommonPrefixLen; i++ { + bit := getBitAtFromMSB(path, i) + fmt.Print(bit, " ") + sb.WriteString(strconv.Itoa(bit)) + } + fmt.Println("\n\tLargest common prefix:", largestCommonPrefixLen) + bits[key] = sb.String() + + sideNodeReadable := make([]string, len(sideNodes)) + for i, sideNode := range sideNodes { + sideNodeReadable[i] = getLeafValue(t, smt, sideNode) + } + fmt.Println("\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, ", "), "]") + + require.NoError(t, err) + fmt.Println("---", smt.depth(), len(sideNodes), len(pathNodes)) + + // path := smt.th.path(key) + // value, err := smt.values.Get(path) + + // fmt.Println("isLeaf: ", smt.th.isLeaf(sideNode), value) + // nodePath, nodeData := smt.th.parseNode(nodeData) + // fmt.Println(nodeData) + + // smt.Get([]byte(d1) + // fmt.Println(key, " | ", value, " | ", d1, " | ") + } + fmt.Println("WTF ", bits) + 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) + fmt.Println("HOLLA") + csvWriter := csv.NewWriter(csvFile) + for key, bits := range bits { + err = csvWriter.Write([]string{string(key), bits}) + require.NoError(t, err) + } + csvWriter.Flush() + csvFile.Close() +} + +// currentHash, currentData = smt.th.digestNode(sideNode, currentData) +// } else { +// currentHash, currentData = smt.th.digestNode(currentData, sideNode) +// } + +// if err := smt.nodes.Set(currentHash, currentData); err != nil { +// return nil, err +// } +// currentData = currentHash +// } + +// if err := smt.values.Set(path, value); err != nil { diff --git a/treehasher.go b/treehasher.go index ba05f64..2567101 100644 --- a/treehasher.go +++ b/treehasher.go @@ -23,6 +23,12 @@ var ( 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 // ASK(reviewer): rename to `emptyNode`? @@ -63,7 +69,9 @@ func (th *treeHasher) digestLeaf(path, data []byte) (hash, value []byte) { } func (th *treeHasher) parseLeaf(value []byte) (path, data []byte) { - return value[len(leafPrefix) : th.pathSize()+len(leafPrefix)], value[len(leafPrefix)+th.pathSize():] + path = value[len(leafPrefix) : th.pathSize()+len(leafPrefix)] + data = value[len(leafPrefix)+th.pathSize():] + return } func (th *treeHasher) isLeaf(data []byte) bool { @@ -79,8 +87,10 @@ func (th *treeHasher) digestNode(leftData, rightData []byte) (hash, value []byte return th.digest(value), value } -func (th *treeHasher) parseNode(value []byte) (path, data []byte) { - return value[len(nodePrefix) : th.pathSize()+len(nodePrefix)], value[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/visualize.ipynb b/visualize.ipynb new file mode 100644 index 0000000..d0d0116 --- /dev/null +++ b/visualize.ipynb @@ -0,0 +1,2772 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def make_annotations(pos, text, labels, font_size=10, font_color='rgb(250,250,250)'):\n", + " L=len(pos)\n", + " if len(text)!=L:\n", + " raise ValueError('The lists pos and text must have the same len')\n", + " annotations = []\n", + " for k in range(L):\n", + " annotations.append(\n", + " dict(\n", + " text=labels[k], # or replace labels with a different list for the text within the circle\n", + " x=pos[k][0], y=2*M-position[k][1],\n", + " xref='x1', yref='y1',\n", + " font=dict(color=font_color, size=font_size),\n", + " showarrow=False)\n", + " )\n", + " return annotations" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "hoverinfo": "none", + "line": { + "color": "rgb(210,210,210)", + "width": 1 + }, + "mode": "lines", + "type": "scatter", + "x": [ + -3.333333333333333, + -3.333333333333333, + null, + -3.333333333333333, + 0, + null, + -3.333333333333333, + -5.333333333333333, + null, + -3.333333333333333, + -1.333333333333333, + null, + 0, + 0.666666666666667, + null, + 0, + 2.666666666666667, + null, + -5.333333333333333, + -6.333333333333333, + null, + -5.333333333333333, + -4.333333333333333, + null, + -1.333333333333333, + -2.333333333333333, + null, + -1.333333333333333, + -0.33333333333333304, + null, + 0.666666666666667, + 0.16666666666666696, + null, + 0.666666666666667, + 1.166666666666667, + null, + 2.666666666666667, + 2.166666666666667, + null, + 2.666666666666667, + 3.166666666666667, + null, + -6.333333333333333, + -6.833333333333333, + null, + -6.333333333333333, + -5.833333333333333, + null, + -4.333333333333333, + -4.833333333333333, + null, + -4.333333333333333, + -3.833333333333333, + null, + -2.333333333333333, + -2.833333333333333, + null, + -2.333333333333333, + -1.833333333333333, + null, + -0.33333333333333304, + -0.833333333333333, + null, + -0.33333333333333304, + 0.16666666666666696, + null, + 0.16666666666666696, + -0.33333333333333304, + null, + 0.16666666666666696, + 0.666666666666667, + null + ], + "y": [ + 9, + 8, + null, + 9, + 10, + null, + 8, + 7, + null, + 8, + 7, + null, + 10, + 9, + null, + 10, + 9, + null, + 7, + 6, + null, + 7, + 6, + null, + 7, + 6, + null, + 7, + 6, + null, + 9, + 8, + null, + 9, + 8, + null, + 9, + 8, + null, + 9, + 8, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 6, + 5, + null, + 8, + 7, + null, + 8, + 7, + null + ] + }, + { + "hoverinfo": "text", + "marker": { + "color": "#6175c1", + "line": { + "color": "rgb(50,50,50)", + "width": 1 + }, + "size": 18, + "symbol": "circle-dot" + }, + "mode": "markers", + "name": "bla", + "opacity": 0.8, + "text": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + "21", + "22", + "23", + "24" + ], + "type": "scatter", + "x": [ + -3.333333333333333, + -3.333333333333333, + 0, + -5.333333333333333, + -1.333333333333333, + 0.666666666666667, + 2.666666666666667, + -6.333333333333333, + -4.333333333333333, + -2.333333333333333, + -0.33333333333333304, + 0.16666666666666696, + 1.166666666666667, + 2.166666666666667, + 3.166666666666667, + -6.833333333333333, + -5.833333333333333, + -4.833333333333333, + -3.833333333333333, + -2.833333333333333, + -1.833333333333333, + -0.833333333333333, + 0.16666666666666696, + -0.33333333333333304, + 0.666666666666667 + ], + "y": [ + 9, + 8, + 10, + 7, + 7, + 9, + 9, + 6, + 6, + 6, + 6, + 8, + 8, + 8, + 8, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 7, + 7 + ] + } + ], + "layout": { + "annotations": [ + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "0", + "x": -3.333333333333333, + "xref": "x", + "y": 9, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "1", + "x": -3.333333333333333, + "xref": "x", + "y": 8, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "2", + "x": 0, + "xref": "x", + "y": 10, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "3", + "x": -5.333333333333333, + "xref": "x", + "y": 7, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "4", + "x": -1.333333333333333, + "xref": "x", + "y": 7, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "5", + "x": 0.666666666666667, + "xref": "x", + "y": 9, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "6", + "x": 2.666666666666667, + "xref": "x", + "y": 9, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "7", + "x": -6.333333333333333, + "xref": "x", + "y": 6, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "8", + "x": -4.333333333333333, + "xref": "x", + "y": 6, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "9", + "x": -2.333333333333333, + "xref": "x", + "y": 6, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "10", + "x": -0.33333333333333304, + "xref": "x", + "y": 6, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "11", + "x": 0.16666666666666696, + "xref": "x", + "y": 8, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "12", + "x": 1.166666666666667, + "xref": "x", + "y": 8, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "13", + "x": 2.166666666666667, + "xref": "x", + "y": 8, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "14", + "x": 3.166666666666667, + "xref": "x", + "y": 8, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "15", + "x": -6.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "16", + "x": -5.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "17", + "x": -4.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "18", + "x": -3.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "19", + "x": -2.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "20", + "x": -1.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "21", + "x": -0.833333333333333, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "22", + "x": 0.16666666666666696, + "xref": "x", + "y": 5, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "23", + "x": -0.33333333333333304, + "xref": "x", + "y": 7, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "24", + "x": 0.666666666666667, + "xref": "x", + "y": 7, + "yref": "y" + } + ], + "font": { + "size": 12 + }, + "hovermode": "closest", + "margin": { + "b": 85, + "l": 40, + "r": 40, + "t": 100 + }, + "plot_bgcolor": "rgb(248,248,248)", + "showlegend": false, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Tree with Reingold-Tilford Layout" + }, + "xaxis": { + "showgrid": false, + "showline": false, + "showticklabels": false, + "zeroline": false + }, + "yaxis": { + "showgrid": false, + "showline": false, + "showticklabels": false, + "zeroline": false + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import plotly.graph_objects as go\n", + "\n", + "import igraph\n", + "from igraph import Graph, EdgeSeq\n", + "nr_vertices = 25\n", + "v_label = list(map(str, range(nr_vertices)))\n", + "G = Graph.Tree(nr_vertices, 2) # 2 stands for children number\n", + "lay = G.layout('rt')\n", + "\n", + "position = {k: lay[k] for k in range(nr_vertices)}\n", + "Y = [lay[k][1] for k in range(nr_vertices)]\n", + "M = max(Y)\n", + "\n", + "es = EdgeSeq(G) # sequence of edges\n", + "E = [e.tuple for e in G.es] # list of edges\n", + "\n", + "L = len(position)\n", + "Xn = [position[k][0] for k in range(L)]\n", + "Yn = [2*M-position[k][1] for k in range(L)]\n", + "Xe = []\n", + "Ye = []\n", + "for edge in E:\n", + " Xe+=[position[edge[0]][0],position[edge[1]][0], None]\n", + " Ye+=[2*M-position[edge[0]][1],2*M-position[edge[1]][1], None]\n", + "\n", + "labels = v_label\n", + "\n", + "fig = go.Figure()\n", + "fig.add_trace(go.Scatter(x=Xe,\n", + " y=Ye,\n", + " mode='lines',\n", + " line=dict(color='rgb(210,210,210)', width=1),\n", + " hoverinfo='none'\n", + " ))\n", + "fig.add_trace(go.Scatter(x=Xn,\n", + " y=Yn,\n", + " mode='markers',\n", + " name='bla',\n", + " marker=dict(symbol='circle-dot',\n", + " size=18,\n", + " color='#6175c1', #'#DB4551',\n", + " line=dict(color='rgb(50,50,50)', width=1)\n", + " ),\n", + " text=labels,\n", + " hoverinfo='text',\n", + " opacity=0.8\n", + " ))\n", + "axis = dict(showline=False, # hide axis line, grid, ticklabels and title\n", + " zeroline=False,\n", + " showgrid=False,\n", + " showticklabels=False,\n", + " )\n", + "\n", + "fig.update_layout(title= 'Tree with Reingold-Tilford Layout',\n", + " annotations=make_annotations(position, v_label, v_label),\n", + " font_size=12,\n", + " showlegend=False,\n", + " xaxis=axis,\n", + " yaxis=axis,\n", + " margin=dict(l=40, r=40, b=85, t=100),\n", + " hovermode='closest',\n", + " plot_bgcolor='rgb(248,248,248)'\n", + " )\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'G': '0', 'I': '01', 'F': '10', 'H': '10', 'E': '0100', 'B': '0100', 'A': '1101', 'J': '11010', 'C': '110100', 'D': '110100'}\n", + "G 0\n", + "I 01\n", + "F 10\n", + "H 10\n", + "E 0100\n", + "B 0100\n", + "A 1101\n", + "J 11010\n", + "C 110100\n", + "D 110100\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(k, v, edge_map=defaultdict[str, str]):\n", + " if k \n", + " build_edges[]\n", + "\n", + " pass\n", + "\n", + "bits = read_bits_from_go_test(\"bits.csv\")\n", + "\n", + "print(bits)\n", + "for k, v in bits.items():\n", + " print(k, v)\n", + "\n", + " root\n", + "\n", + "0 \n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "hoverinfo": "none", + "line": { + "color": "rgb(210,210,210)", + "width": 1 + }, + "mode": "lines", + "type": "scatter", + "x": [ + -4, + -2.5, + null, + -4, + -3, + null, + -4, + -2, + null, + -4, + -1, + null, + -2.5, + -3, + null, + -2.5, + -2, + null, + -2.5, + -1, + null, + -2, + -1, + null + ], + "y": [ + 2, + 3, + null, + 2, + 2, + null, + 2, + 2, + null, + 2, + 2, + null, + 3, + 2, + null, + 3, + 2, + null, + 3, + 2, + null, + 2, + 2, + null + ] + }, + { + "hoverinfo": "text", + "marker": { + "color": "#6175c1", + "line": { + "color": "rgb(50,50,50)", + "width": 1 + }, + "size": 18, + "symbol": "circle-dot" + }, + "mode": "markers", + "name": "bla", + "opacity": 0.8, + "text": [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ], + "type": "scatter", + "x": [ + -4, + -2.5, + -3, + -2, + -1, + -1.5, + -0.5, + 0.5, + 1.5, + 2.5 + ], + "y": [ + 2, + 3, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3 + ] + } + ], + "layout": { + "annotations": [ + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "0", + "x": -4, + "xref": "x", + "y": 2, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "1", + "x": -2.5, + "xref": "x", + "y": 3, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "2", + "x": -3, + "xref": "x", + "y": 2, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "3", + "x": -2, + "xref": "x", + "y": 2, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "4", + "x": -1, + "xref": "x", + "y": 2, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "5", + "x": -1.5, + "xref": "x", + "y": 3, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "6", + "x": -0.5, + "xref": "x", + "y": 3, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "7", + "x": 0.5, + "xref": "x", + "y": 3, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "8", + "x": 1.5, + "xref": "x", + "y": 3, + "yref": "y" + }, + { + "font": { + "color": "rgb(250,250,250)", + "size": 10 + }, + "showarrow": false, + "text": "9", + "x": 2.5, + "xref": "x", + "y": 3, + "yref": "y" + } + ], + "font": { + "size": 12 + }, + "hovermode": "closest", + "margin": { + "b": 0, + "l": 0, + "r": 40, + "t": 40 + }, + "plot_bgcolor": "rgb(248,248,248)", + "showlegend": true, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Jellyfish Merkle Tree" + }, + "xaxis": { + "showgrid": false, + "showline": false, + "showticklabels": false, + "zeroline": false + }, + "yaxis": { + "showgrid": false, + "showline": false, + "showticklabels": false, + "zeroline": false + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import igraph as ig\n", + "import plotly.graph_objects as go\n", + "\n", + "\n", + "edges = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)]\n", + "nr_vertices = len(bits)\n", + "v_label = list(map(str, range(nr_vertices)))\n", + "\n", + "G = ig.Graph(nr_vertices, edges)\n", + "lay = G.layout('rt')\n", + "\n", + "\n", + "position = {k: lay[k] for k in range(nr_vertices)}\n", + "Y = [lay[k][1] for k in range(nr_vertices)]\n", + "M = max(Y)\n", + "\n", + "# es = EdgeSeq(G) # sequence of edges\n", + "# E = [e.tuple for e in G.es] # list of edges\n", + "\n", + "L = len(position)\n", + "Xn = [position[k][0] for k in range(L)]\n", + "Yn = [2*M-position[k][1] for k in range(L)]\n", + "Xe = []\n", + "Ye = []\n", + "for edge in edges:\n", + " Xe+=[position[edge[0]][0],position[edge[1]][0], None]\n", + " Ye+=[2*M-position[edge[0]][1],2*M-position[edge[1]][1], None]\n", + "\n", + "\n", + "\n", + "fig = go.Figure()\n", + "fig.add_trace(go.Scatter(x=Xe,\n", + " y=Ye,\n", + " mode='lines',\n", + " line=dict(color='rgb(210,210,210)', width=1),\n", + " hoverinfo='none'\n", + " ))\n", + "fig.add_trace(go.Scatter(x=Xn,\n", + " y=Yn,\n", + " mode='markers',\n", + " name='bla',\n", + " marker=dict(symbol='circle-dot',\n", + " size=18,\n", + " color='#6175c1', #'#DB4551',\n", + " line=dict(color='rgb(50,50,50)', width=1)\n", + " ),\n", + " text=v_label,\n", + " hoverinfo='text',\n", + " opacity=0.8\n", + " ))\n", + "\n", + "axis = dict(showline=False, # hide axis line, grid, ticklabels and title\n", + " zeroline=False,\n", + " showgrid=False,\n", + " showticklabels=False,\n", + " )\n", + "\n", + "fig.update_layout(title= 'Jellyfish Merkle Tree',\n", + " annotations=make_annotations(position, v_label, v_label),\n", + " font_size=12,\n", + " showlegend=True,\n", + " xaxis=axis,\n", + " yaxis=axis,\n", + " margin=dict(l=0, r=40, b=0, t=40),\n", + " hovermode='closest',\n", + " plot_bgcolor='rgb(248,248,248)'\n", + " )\n", + "fig.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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.6" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "b0fa6594d8f4cbf19f97940f81e996739fb7646882a419484c72d19e05852a7e" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From ef6cebd7c89362c757e7dc004bfe57966294e47d Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 23 Oct 2022 18:33:33 -0700 Subject: [PATCH 24/26] Got some sort of visualization going --- README.md | 2 +- bits.csv | Bin 96 -> 96 bytes olsh_test.go | 144 +-- visualize.ipynb | 2726 +---------------------------------------------- 4 files changed, 59 insertions(+), 2813 deletions(-) diff --git a/README.md b/README.md index 8d5ca53..d9e0be3 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Run `make` to see all the options available - [x] ├── go.sum - [x] ├── mapstore.go - [x] ├── mapstore_test.go -- [ ] ├── options.go +- [x] ├── options.go - [ ] ├── oss-fuzz-build.sh - [ ] ├── proofs.go - [ ] ├── proofs_test.go diff --git a/bits.csv b/bits.csv index 59aad0145360fdf4aa054ccd9d83869db6c16547..6d780fefa380ae47a0ca89f3ed80463078775fe2 100644 GIT binary patch literal 96 zcmZ=^U|`TOG&C?YFyL|mvB4}?ILi$zZ@}da7UA-Ma=pMbNE??UjLGE*R%6KJ3=;vG F2>>!=3#9-6 literal 96 zcmZ=~U|`TO;PM1hhFq=?+Q5Lz3&b)sG%z&aa)dFtoM9}W0v9B<8<=gt OLSH11 - // Full matches from OLSH2 -> OLSH11) - // Full matches from OLSH33 -> OLSH22 - // Full matches from OLSH333 -> OLSH2 (and in turn OLSH11 - - // Loop over values map - for path, value := range smv.m { - // fmt.Println(path, " | ", value) - // value, err := smt.Get([]byte(path)) - // fmt.Println("OLSH", value, err) - fmt.Println("OLSH2", hex.EncodeToString([]byte(path))) - fmt.Println("OLSH22", hex.EncodeToString([]byte(value))) - fmt.Println("OLSH222", hex.EncodeToString(smt.th.digest([]byte(value)))) - // break - } - fmt.Println() - - // Loop over kv map - var key string - for key, value := range kv { - // fmt.Println(key, " | ", value) - // value, err := smt.Get([]byte(key)) - // fmt.Println("OLSH", value, err) - fmt.Println("OLSH3", hex.EncodeToString([]byte(key))) - fmt.Println("OLSH33", hex.EncodeToString([]byte(value))) - fmt.Println("OLSH333", hex.EncodeToString(smt.th.path([]byte(key)))) - - // break - } - - sideNodes, pathNodes, nodeData, sideNode, err := smt.sideNodesForRoot(smt.th.path([]byte(key)), smt.root) - require.NoError(t, err) - fmt.Println("----------------") - fmt.Print("OLSH", len(sideNodes), len(pathNodes), "-------", len(nodeData), len(sideNode)) - */ - var key string - for key, value := range kv { - fmt.Println(key, " | ", value) - break - } - sideNodes, pathNodes, nodeData, sideNode, err := smt.sideNodesForRoot(smt.th.path([]byte(key)), smt.root) - require.NoError(t, err) - fmt.Println("OLSH", smt.depth(), len(sideNodes), len(pathNodes), "-------", len(nodeData), len(sideNode)) - - // for - - // value - // left - // right - // leaf? - // node? -} - -func TestOlshansky2(t *testing.T) { +func TestVisualizationHelper(t *testing.T) { smn, smv := NewSimpleMap(), NewSimpleMap() smt := NewSparseMerkleTree(smn, smv, sha256.New()) kv := make(map[string]string) @@ -103,82 +31,46 @@ func TestOlshansky2(t *testing.T) { smt.Update(bs, bs) kv[string(bs)] = string(bs) } - fmt.Println("OLSH KV", kv) // var key string for key, value := range kv { path := smt.th.path([]byte(key)) sideNodes, pathNodes, nodeData, _, err := smt.sideNodesForRoot(path, smt.root) - if smt.th.isLeaf([]byte(nodeData)) { - // input: (key, value) - - // path = smt.th.digest([]byte(key)) - // valueHash := smt.th.digest(value) - // currentHash, currentData := smt.th.digestLeaf(path, valueHash) - - // smt.nodes.Set(currentHash, currentData) - // smt.values.Set(path, value) - - // value = append(value, leafPrefix...) - // value = append(value, path...) - // value = append(value, data...) - // return th.digest(value), value - - // fmt.Println("LEAF: ", value) - // leafPath, leafData := smt.th.parseLeaf(nodeData) - // value1, err := smt.Get(leafData) - // require.NoError(t, err) - // value2, err := smt.Get(leafPath) - // require.NoError(t, err) - // value3, err := smt.Get([]byte(key)) - // require.NoError(t, err) - // fmt.Println("Leaf data: ", value, string(value3), leafData, leafPath) - // fmt.Println("Leaf data: ", value) + 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("NODE: ", value) + fmt.Println(fmt.Sprintf("Not Leaf: %s (%d side nodes, %d path nodes)", value, len(sideNodes), len(pathNodes))) } - // How do I map from node to value? + largestCommonPrefixLen := getLargestCommonPrefix(t, smt, kv, key) + fmt.Print("\tLargest common prefix: ", largestCommonPrefixLen) var sb strings.Builder - fmt.Print(fmt.Sprintf("OLSH bits (%s): ", value)) - largestCommonPrefixLen := getLargestCommonPrefix(t, smt, kv, key) + fmt.Print("\n\tCommon Bits : ") for i := 0; i < largestCommonPrefixLen; i++ { bit := getBitAtFromMSB(path, i) fmt.Print(bit, " ") sb.WriteString(strconv.Itoa(bit)) } - fmt.Println("\n\tLargest common prefix:", largestCommonPrefixLen) 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("\tSIDE NODES: [", strings.Join(sideNodeReadable, ", "), "]") + 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, ", "), "]") - - require.NoError(t, err) - fmt.Println("---", smt.depth(), len(sideNodes), len(pathNodes)) - - // path := smt.th.path(key) - // value, err := smt.values.Get(path) - - // fmt.Println("isLeaf: ", smt.th.isLeaf(sideNode), value) - // nodePath, nodeData := smt.th.parseNode(nodeData) - // fmt.Println(nodeData) - - // smt.Get([]byte(d1) - // fmt.Println(key, " | ", value, " | ", d1, " | ") } - fmt.Println("WTF ", bits) writeToCsv(t, bits) } @@ -208,7 +100,6 @@ func getLeafValue(t *testing.T, smt *SparseMerkleTree, nodeHash []byte) string { func writeToCsv(t *testing.T, bits map[string]string) { csvFile, err := os.Create("bits.csv") require.NoError(t, err) - fmt.Println("HOLLA") csvWriter := csv.NewWriter(csvFile) for key, bits := range bits { err = csvWriter.Write([]string{string(key), bits}) @@ -217,16 +108,3 @@ func writeToCsv(t *testing.T, bits map[string]string) { csvWriter.Flush() csvFile.Close() } - -// currentHash, currentData = smt.th.digestNode(sideNode, currentData) -// } else { -// currentHash, currentData = smt.th.digestNode(currentData, sideNode) -// } - -// if err := smt.nodes.Set(currentHash, currentData); err != nil { -// return nil, err -// } -// currentData = currentHash -// } - -// if err := smt.values.Set(path, value); err != nil { diff --git a/visualize.ipynb b/visualize.ipynb index d0d0116..c98205e 100644 --- a/visualize.ipynb +++ b/visualize.ipynb @@ -2,1534 +2,26 @@ "cells": [ { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "def make_annotations(pos, text, labels, font_size=10, font_color='rgb(250,250,250)'):\n", - " L=len(pos)\n", - " if len(text)!=L:\n", - " raise ValueError('The lists pos and text must have the same len')\n", - " annotations = []\n", - " for k in range(L):\n", - " annotations.append(\n", - " dict(\n", - " text=labels[k], # or replace labels with a different list for the text within the circle\n", - " x=pos[k][0], y=2*M-position[k][1],\n", - " xref='x1', yref='y1',\n", - " font=dict(color=font_color, size=font_size),\n", - " showarrow=False)\n", - " )\n", - " return annotations" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "hoverinfo": "none", - "line": { - "color": "rgb(210,210,210)", - "width": 1 - }, - "mode": "lines", - "type": "scatter", - "x": [ - -3.333333333333333, - -3.333333333333333, - null, - -3.333333333333333, - 0, - null, - -3.333333333333333, - -5.333333333333333, - null, - -3.333333333333333, - -1.333333333333333, - null, - 0, - 0.666666666666667, - null, - 0, - 2.666666666666667, - null, - -5.333333333333333, - -6.333333333333333, - null, - -5.333333333333333, - -4.333333333333333, - null, - -1.333333333333333, - -2.333333333333333, - null, - -1.333333333333333, - -0.33333333333333304, - null, - 0.666666666666667, - 0.16666666666666696, - null, - 0.666666666666667, - 1.166666666666667, - null, - 2.666666666666667, - 2.166666666666667, - null, - 2.666666666666667, - 3.166666666666667, - null, - -6.333333333333333, - -6.833333333333333, - null, - -6.333333333333333, - -5.833333333333333, - null, - -4.333333333333333, - -4.833333333333333, - null, - -4.333333333333333, - -3.833333333333333, - null, - -2.333333333333333, - -2.833333333333333, - null, - -2.333333333333333, - -1.833333333333333, - null, - -0.33333333333333304, - -0.833333333333333, - null, - -0.33333333333333304, - 0.16666666666666696, - null, - 0.16666666666666696, - -0.33333333333333304, - null, - 0.16666666666666696, - 0.666666666666667, - null - ], - "y": [ - 9, - 8, - null, - 9, - 10, - null, - 8, - 7, - null, - 8, - 7, - null, - 10, - 9, - null, - 10, - 9, - null, - 7, - 6, - null, - 7, - 6, - null, - 7, - 6, - null, - 7, - 6, - null, - 9, - 8, - null, - 9, - 8, - null, - 9, - 8, - null, - 9, - 8, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 6, - 5, - null, - 8, - 7, - null, - 8, - 7, - null - ] - }, - { - "hoverinfo": "text", - "marker": { - "color": "#6175c1", - "line": { - "color": "rgb(50,50,50)", - "width": 1 - }, - "size": 18, - "symbol": "circle-dot" - }, - "mode": "markers", - "name": "bla", - "opacity": 0.8, - "text": [ - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - "21", - "22", - "23", - "24" - ], - "type": "scatter", - "x": [ - -3.333333333333333, - -3.333333333333333, - 0, - -5.333333333333333, - -1.333333333333333, - 0.666666666666667, - 2.666666666666667, - -6.333333333333333, - -4.333333333333333, - -2.333333333333333, - -0.33333333333333304, - 0.16666666666666696, - 1.166666666666667, - 2.166666666666667, - 3.166666666666667, - -6.833333333333333, - -5.833333333333333, - -4.833333333333333, - -3.833333333333333, - -2.833333333333333, - -1.833333333333333, - -0.833333333333333, - 0.16666666666666696, - -0.33333333333333304, - 0.666666666666667 - ], - "y": [ - 9, - 8, - 10, - 7, - 7, - 9, - 9, - 6, - 6, - 6, - 6, - 8, - 8, - 8, - 8, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 5, - 7, - 7 - ] - } - ], - "layout": { - "annotations": [ - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "0", - "x": -3.333333333333333, - "xref": "x", - "y": 9, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "1", - "x": -3.333333333333333, - "xref": "x", - "y": 8, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "2", - "x": 0, - "xref": "x", - "y": 10, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "3", - "x": -5.333333333333333, - "xref": "x", - "y": 7, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "4", - "x": -1.333333333333333, - "xref": "x", - "y": 7, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "5", - "x": 0.666666666666667, - "xref": "x", - "y": 9, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "6", - "x": 2.666666666666667, - "xref": "x", - "y": 9, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "7", - "x": -6.333333333333333, - "xref": "x", - "y": 6, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "8", - "x": -4.333333333333333, - "xref": "x", - "y": 6, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "9", - "x": -2.333333333333333, - "xref": "x", - "y": 6, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "10", - "x": -0.33333333333333304, - "xref": "x", - "y": 6, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "11", - "x": 0.16666666666666696, - "xref": "x", - "y": 8, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "12", - "x": 1.166666666666667, - "xref": "x", - "y": 8, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "13", - "x": 2.166666666666667, - "xref": "x", - "y": 8, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "14", - "x": 3.166666666666667, - "xref": "x", - "y": 8, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "15", - "x": -6.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "16", - "x": -5.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "17", - "x": -4.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "18", - "x": -3.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "19", - "x": -2.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "20", - "x": -1.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "21", - "x": -0.833333333333333, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "22", - "x": 0.16666666666666696, - "xref": "x", - "y": 5, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "23", - "x": -0.33333333333333304, - "xref": "x", - "y": 7, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "24", - "x": 0.666666666666667, - "xref": "x", - "y": 7, - "yref": "y" - } - ], - "font": { - "size": 12 - }, - "hovermode": "closest", - "margin": { - "b": 85, - "l": 40, - "r": 40, - "t": 100 - }, - "plot_bgcolor": "rgb(248,248,248)", - "showlegend": false, - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Tree with Reingold-Tilford Layout" - }, - "xaxis": { - "showgrid": false, - "showline": false, - "showticklabels": false, - "zeroline": false - }, - "yaxis": { - "showgrid": false, - "showline": false, - "showticklabels": false, - "zeroline": false - } - } - } - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import plotly.graph_objects as go\n", - "\n", - "import igraph\n", - "from igraph import Graph, EdgeSeq\n", - "nr_vertices = 25\n", - "v_label = list(map(str, range(nr_vertices)))\n", - "G = Graph.Tree(nr_vertices, 2) # 2 stands for children number\n", - "lay = G.layout('rt')\n", - "\n", - "position = {k: lay[k] for k in range(nr_vertices)}\n", - "Y = [lay[k][1] for k in range(nr_vertices)]\n", - "M = max(Y)\n", - "\n", - "es = EdgeSeq(G) # sequence of edges\n", - "E = [e.tuple for e in G.es] # list of edges\n", - "\n", - "L = len(position)\n", - "Xn = [position[k][0] for k in range(L)]\n", - "Yn = [2*M-position[k][1] for k in range(L)]\n", - "Xe = []\n", - "Ye = []\n", - "for edge in E:\n", - " Xe+=[position[edge[0]][0],position[edge[1]][0], None]\n", - " Ye+=[2*M-position[edge[0]][1],2*M-position[edge[1]][1], None]\n", - "\n", - "labels = v_label\n", - "\n", - "fig = go.Figure()\n", - "fig.add_trace(go.Scatter(x=Xe,\n", - " y=Ye,\n", - " mode='lines',\n", - " line=dict(color='rgb(210,210,210)', width=1),\n", - " hoverinfo='none'\n", - " ))\n", - "fig.add_trace(go.Scatter(x=Xn,\n", - " y=Yn,\n", - " mode='markers',\n", - " name='bla',\n", - " marker=dict(symbol='circle-dot',\n", - " size=18,\n", - " color='#6175c1', #'#DB4551',\n", - " line=dict(color='rgb(50,50,50)', width=1)\n", - " ),\n", - " text=labels,\n", - " hoverinfo='text',\n", - " opacity=0.8\n", - " ))\n", - "axis = dict(showline=False, # hide axis line, grid, ticklabels and title\n", - " zeroline=False,\n", - " showgrid=False,\n", - " showticklabels=False,\n", - " )\n", - "\n", - "fig.update_layout(title= 'Tree with Reingold-Tilford Layout',\n", - " annotations=make_annotations(position, v_label, v_label),\n", - " font_size=12,\n", - " showlegend=False,\n", - " xaxis=axis,\n", - " yaxis=axis,\n", - " margin=dict(l=40, r=40, b=85, t=100),\n", - " hovermode='closest',\n", - " plot_bgcolor='rgb(248,248,248)'\n", - " )\n", - "fig.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 19, + "execution_count": 121, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'G': '0', 'I': '01', 'F': '10', 'H': '10', 'E': '0100', 'B': '0100', 'A': '1101', 'J': '11010', 'C': '110100', 'D': '110100'}\n", "G 0\n", "I 01\n", "F 10\n", "H 10\n", - "E 0100\n", "B 0100\n", + "E 0100\n", "A 1101\n", "J 11010\n", + "D 110100\n", "C 110100\n", - "D 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" ] } ], @@ -1547,1199 +39,75 @@ " bits = {k: v for k, v in sorted(bits.items(), key=lambda item: len(item[1]))}\n", " return bits\n", "\n", - "def build_edges(k, v, edge_map=defaultdict[str, str]):\n", - " if k \n", - " build_edges[]\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", - " pass\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", - "print(bits)\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", - " root\n", - "\n", - "0 \n" + "print(edge_map)\n", + "print(edges)\n", + "print(edges_label)" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 122, "metadata": {}, "outputs": [ { "data": { - "application/vnd.plotly.v1+json": { - "config": { - "plotlyServerURL": "https://plot.ly" - }, - "data": [ - { - "hoverinfo": "none", - "line": { - "color": "rgb(210,210,210)", - "width": 1 - }, - "mode": "lines", - "type": "scatter", - "x": [ - -4, - -2.5, - null, - -4, - -3, - null, - -4, - -2, - null, - -4, - -1, - null, - -2.5, - -3, - null, - -2.5, - -2, - null, - -2.5, - -1, - null, - -2, - -1, - null - ], - "y": [ - 2, - 3, - null, - 2, - 2, - null, - 2, - 2, - null, - 2, - 2, - null, - 3, - 2, - null, - 3, - 2, - null, - 3, - 2, - null, - 2, - 2, - null - ] - }, - { - "hoverinfo": "text", - "marker": { - "color": "#6175c1", - "line": { - "color": "rgb(50,50,50)", - "width": 1 - }, - "size": 18, - "symbol": "circle-dot" - }, - "mode": "markers", - "name": "bla", - "opacity": 0.8, - "text": [ - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9" - ], - "type": "scatter", - "x": [ - -4, - -2.5, - -3, - -2, - -1, - -1.5, - -0.5, - 0.5, - 1.5, - 2.5 - ], - "y": [ - 2, - 3, - 2, - 2, - 2, - 3, - 3, - 3, - 3, - 3 - ] - } - ], - "layout": { - "annotations": [ - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "0", - "x": -4, - "xref": "x", - "y": 2, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "1", - "x": -2.5, - "xref": "x", - "y": 3, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "2", - "x": -3, - "xref": "x", - "y": 2, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "3", - "x": -2, - "xref": "x", - "y": 2, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "4", - "x": -1, - "xref": "x", - "y": 2, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "5", - "x": -1.5, - "xref": "x", - "y": 3, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "6", - "x": -0.5, - "xref": "x", - "y": 3, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "7", - "x": 0.5, - "xref": "x", - "y": 3, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "8", - "x": 1.5, - "xref": "x", - "y": 3, - "yref": "y" - }, - { - "font": { - "color": "rgb(250,250,250)", - "size": 10 - }, - "showarrow": false, - "text": "9", - "x": 2.5, - "xref": "x", - "y": 3, - "yref": "y" - } - ], - "font": { - "size": 12 - }, - "hovermode": "closest", - "margin": { - "b": 0, - "l": 0, - "r": 40, - "t": 40 - }, - "plot_bgcolor": "rgb(248,248,248)", - "showlegend": true, - "template": { - "data": { - "bar": [ - { - "error_x": { - "color": "#2a3f5f" - }, - "error_y": { - "color": "#2a3f5f" - }, - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "bar" - } - ], - "barpolar": [ - { - "marker": { - "line": { - "color": "#E5ECF6", - "width": 0.5 - }, - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "barpolar" - } - ], - "carpet": [ - { - "aaxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "baxis": { - "endlinecolor": "#2a3f5f", - "gridcolor": "white", - "linecolor": "white", - "minorgridcolor": "white", - "startlinecolor": "#2a3f5f" - }, - "type": "carpet" - } - ], - "choropleth": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "choropleth" - } - ], - "contour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "contour" - } - ], - "contourcarpet": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "contourcarpet" - } - ], - "heatmap": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmap" - } - ], - "heatmapgl": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "heatmapgl" - } - ], - "histogram": [ - { - "marker": { - "pattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - } - }, - "type": "histogram" - } - ], - "histogram2d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2d" - } - ], - "histogram2dcontour": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "histogram2dcontour" - } - ], - "mesh3d": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "type": "mesh3d" - } - ], - "parcoords": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "parcoords" - } - ], - "pie": [ - { - "automargin": true, - "type": "pie" - } - ], - "scatter": [ - { - "fillpattern": { - "fillmode": "overlay", - "size": 10, - "solidity": 0.2 - }, - "type": "scatter" - } - ], - "scatter3d": [ - { - "line": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatter3d" - } - ], - "scattercarpet": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattercarpet" - } - ], - "scattergeo": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergeo" - } - ], - "scattergl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattergl" - } - ], - "scattermapbox": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scattermapbox" - } - ], - "scatterpolar": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolar" - } - ], - "scatterpolargl": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterpolargl" - } - ], - "scatterternary": [ - { - "marker": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "type": "scatterternary" - } - ], - "surface": [ - { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - }, - "colorscale": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "type": "surface" - } - ], - "table": [ - { - "cells": { - "fill": { - "color": "#EBF0F8" - }, - "line": { - "color": "white" - } - }, - "header": { - "fill": { - "color": "#C8D4E3" - }, - "line": { - "color": "white" - } - }, - "type": "table" - } - ] - }, - "layout": { - "annotationdefaults": { - "arrowcolor": "#2a3f5f", - "arrowhead": 0, - "arrowwidth": 1 - }, - "autotypenumbers": "strict", - "coloraxis": { - "colorbar": { - "outlinewidth": 0, - "ticks": "" - } - }, - "colorscale": { - "diverging": [ - [ - 0, - "#8e0152" - ], - [ - 0.1, - "#c51b7d" - ], - [ - 0.2, - "#de77ae" - ], - [ - 0.3, - "#f1b6da" - ], - [ - 0.4, - "#fde0ef" - ], - [ - 0.5, - "#f7f7f7" - ], - [ - 0.6, - "#e6f5d0" - ], - [ - 0.7, - "#b8e186" - ], - [ - 0.8, - "#7fbc41" - ], - [ - 0.9, - "#4d9221" - ], - [ - 1, - "#276419" - ] - ], - "sequential": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ], - "sequentialminus": [ - [ - 0, - "#0d0887" - ], - [ - 0.1111111111111111, - "#46039f" - ], - [ - 0.2222222222222222, - "#7201a8" - ], - [ - 0.3333333333333333, - "#9c179e" - ], - [ - 0.4444444444444444, - "#bd3786" - ], - [ - 0.5555555555555556, - "#d8576b" - ], - [ - 0.6666666666666666, - "#ed7953" - ], - [ - 0.7777777777777778, - "#fb9f3a" - ], - [ - 0.8888888888888888, - "#fdca26" - ], - [ - 1, - "#f0f921" - ] - ] - }, - "colorway": [ - "#636efa", - "#EF553B", - "#00cc96", - "#ab63fa", - "#FFA15A", - "#19d3f3", - "#FF6692", - "#B6E880", - "#FF97FF", - "#FECB52" - ], - "font": { - "color": "#2a3f5f" - }, - "geo": { - "bgcolor": "white", - "lakecolor": "white", - "landcolor": "#E5ECF6", - "showlakes": true, - "showland": true, - "subunitcolor": "white" - }, - "hoverlabel": { - "align": "left" - }, - "hovermode": "closest", - "mapbox": { - "style": "light" - }, - "paper_bgcolor": "white", - "plot_bgcolor": "#E5ECF6", - "polar": { - "angularaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "radialaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "scene": { - "xaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "yaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - }, - "zaxis": { - "backgroundcolor": "#E5ECF6", - "gridcolor": "white", - "gridwidth": 2, - "linecolor": "white", - "showbackground": true, - "ticks": "", - "zerolinecolor": "white" - } - }, - "shapedefaults": { - "line": { - "color": "#2a3f5f" - } - }, - "ternary": { - "aaxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "baxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - }, - "bgcolor": "#E5ECF6", - "caxis": { - "gridcolor": "white", - "linecolor": "white", - "ticks": "" - } - }, - "title": { - "x": 0.05 - }, - "xaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - }, - "yaxis": { - "automargin": true, - "gridcolor": "white", - "linecolor": "white", - "ticks": "", - "title": { - "standoff": 15 - }, - "zerolinecolor": "white", - "zerolinewidth": 2 - } - } - }, - "title": { - "text": "Jellyfish Merkle Tree" - }, - "xaxis": { - "showgrid": false, - "showline": false, - "showticklabels": false, - "zeroline": false - }, - "yaxis": { - "showgrid": false, - "showline": false, - "showticklabels": false, - "zeroline": false - } - } - } + "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 igraph as ig\n", - "import plotly.graph_objects as go\n", + "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", - "edges = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (3, 4)]\n", - "nr_vertices = len(bits)\n", - "v_label = list(map(str, range(nr_vertices)))\n", + "color_map = ['yellow']\n", + "G.add_node('root')\n", "\n", - "G = ig.Graph(nr_vertices, edges)\n", - "lay = G.layout('rt')\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", - "position = {k: lay[k] for k in range(nr_vertices)}\n", - "Y = [lay[k][1] for k in range(nr_vertices)]\n", - "M = max(Y)\n", + "for e in edges:\n", + " G.add_edge(*e)\n", "\n", - "# es = EdgeSeq(G) # sequence of edges\n", - "# E = [e.tuple for e in G.es] # list of edges\n", - "\n", - "L = len(position)\n", - "Xn = [position[k][0] for k in range(L)]\n", - "Yn = [2*M-position[k][1] for k in range(L)]\n", - "Xe = []\n", - "Ye = []\n", - "for edge in edges:\n", - " Xe+=[position[edge[0]][0],position[edge[1]][0], None]\n", - " Ye+=[2*M-position[edge[0]][1],2*M-position[edge[1]][1], None]\n", - "\n", - "\n", - "\n", - "fig = go.Figure()\n", - "fig.add_trace(go.Scatter(x=Xe,\n", - " y=Ye,\n", - " mode='lines',\n", - " line=dict(color='rgb(210,210,210)', width=1),\n", - " hoverinfo='none'\n", - " ))\n", - "fig.add_trace(go.Scatter(x=Xn,\n", - " y=Yn,\n", - " mode='markers',\n", - " name='bla',\n", - " marker=dict(symbol='circle-dot',\n", - " size=18,\n", - " color='#6175c1', #'#DB4551',\n", - " line=dict(color='rgb(50,50,50)', width=1)\n", - " ),\n", - " text=v_label,\n", - " hoverinfo='text',\n", - " opacity=0.8\n", - " ))\n", - "\n", - "axis = dict(showline=False, # hide axis line, grid, ticklabels and title\n", - " zeroline=False,\n", - " showgrid=False,\n", - " showticklabels=False,\n", - " )\n", - "\n", - "fig.update_layout(title= 'Jellyfish Merkle Tree',\n", - " annotations=make_annotations(position, v_label, v_label),\n", - " font_size=12,\n", - " showlegend=True,\n", - " xaxis=axis,\n", - " yaxis=axis,\n", - " margin=dict(l=0, r=40, b=0, t=40),\n", - " hovermode='closest',\n", - " plot_bgcolor='rgb(248,248,248)'\n", - " )\n", - "fig.show()" + "# 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)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { From a8c621aed473b3dcc2e38dcdff84639118a05333 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sun, 23 Oct 2022 18:42:30 -0700 Subject: [PATCH 25/26] Remove OLSH logs --- bits.csv | Bin 96 -> 96 bytes smt.go | 4 ---- 2 files changed, 4 deletions(-) diff --git a/bits.csv b/bits.csv index 6d780fefa380ae47a0ca89f3ed80463078775fe2..06434580130f212200d06c5fce4fd473e297ed11 100644 GIT binary patch literal 96 zcmZ=~U|`TO;PM1hhFq=?+Q5Lz4a71u;Bo}hh6aXQ9#9c4n1}(F6NqVG2vqJ27c}5< HL1F^{80QP6 literal 96 zcmZ=^U|`TOG&C?YFyL|mvB4}?ILi$zZ@}da7UA-Ma=pMbNE??UjLGE*R%6KJ3=;vG F2>>!=3#9-6 diff --git a/smt.go b/smt.go index 590d630..6b5d967 100644 --- a/smt.go +++ b/smt.go @@ -4,7 +4,6 @@ package smt import ( "bytes" "errors" - "fmt" "hash" ) @@ -238,10 +237,8 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, actualPath, oldValueHash = smt.th.parseLeaf(oldLeafData) commonPrefixCount = countCommonPrefix(path, actualPath) } - fmt.Println("OLSH commonPrefixCount", commonPrefixCount) // 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() { - fmt.Println("OLSH 1") // TODO: Need to understand / visualize the business logic here too if getBitAtFromMSB(path, commonPrefixCount) == right { currentHash, currentData = smt.th.digestNode(pathNodes[0], currentData) @@ -279,7 +276,6 @@ func (smt *SparseMerkleTree) updateWithSideNodes(path, value []byte, sideNodes, // Note: i-offsetOfSideNodes is the index into sideNodes[] offsetOfSideNodes := smt.depth() - len(sideNodes) - fmt.Println("OLSH 3", offsetOfSideNodes) for i := 0; i < smt.depth(); i++ { var sideNode []byte From 47658768e50bf46875ab2753512d4afb4acb98d2 Mon Sep 17 00:00:00 2001 From: Daniel Olshansky Date: Sat, 25 Feb 2023 12:19:35 -0800 Subject: [PATCH 26/26] Some old changes --- .DS_Store | Bin 0 -> 6148 bytes bits.csv | Bin 96 -> 96 bytes visualize.ipynb | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .DS_Store 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?