From 205a82f081c40b0f9a29b01de9926cf28a6a5baf Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 17 Mar 2015 16:45:01 -0400 Subject: [PATCH] .: stub on merkle nodes --- node.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ node_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++ tree.go | 5 ++++ 3 files changed, 153 insertions(+) create mode 100644 node.go create mode 100644 node_test.go create mode 100644 tree.go diff --git a/node.go b/node.go new file mode 100644 index 0000000..943139b --- /dev/null +++ b/node.go @@ -0,0 +1,82 @@ +package merkle + +import ( + "crypto" + _ "crypto/sha1" + "fmt" +) + +var ( + DefaultHash = crypto.SHA1 +) + +func NewNode() *Node { + return &Node{hash: DefaultHash} +} + +// Node is a fundamental part of the tree. +type Node struct { + hash crypto.Hash + checksum []byte + Parent, Left, Right *Node +} + +// If it is a leaf (no children) Node, then the Checksum is of the block of a +// payload. Otherwise, the Checksum is of it's two children's Checksum. +func (n Node) Checksum() ([]byte, error) { + if n.checksum != nil { + return n.checksum, nil + } + if n.Left != nil && n.Right != nil { + + // we'll ask our children for their sum and wait til they return + var ( + lSumChan = make(chan childSumResponse) + rSumChan = make(chan childSumResponse) + ) + go func() { + c, err := n.Left.Checksum() + lSumChan <- childSumResponse{checksum: c, err: err} + }() + go func() { + c, err := n.Right.Checksum() + rSumChan <- childSumResponse{checksum: c, err: err} + }() + + h := n.hash.New() + + // First left + lSum := <-lSumChan + if lSum.err != nil { + return nil, lSum.err + } + if _, err := h.Write(lSum.checksum); err != nil { + return nil, err + } + + // then right + rSum := <-rSumChan + if rSum.err != nil { + return nil, rSum.err + } + if _, err := h.Write(rSum.checksum); err != nil { + return nil, err + } + + return h.Sum(nil), nil + } + return nil, ErrNoChecksumAvailable{node: &n} +} + +type ErrNoChecksumAvailable struct { + node *Node +} + +func (err ErrNoChecksumAvailable) Error() string { + return fmt.Sprintf("no block or children available to derive checksum from: %#v", *err.node) +} + +type childSumResponse struct { + checksum []byte + err error +} diff --git a/node_test.go b/node_test.go new file mode 100644 index 0000000..6655c22 --- /dev/null +++ b/node_test.go @@ -0,0 +1,66 @@ +package merkle + +import ( + "log" + "strings" + "testing" +) + +var words string = `Who were expelled from the academy for crazy & publishing obscene odes on the windows of the skull` + +func TestNodeSums(t *testing.T) { + var ( + nodes []*Node + h = DefaultHash.New() + ) + for _, word := range strings.Split(words, " ") { + h.Reset() + if _, err := h.Write([]byte(word)); err != nil { + t.Errorf("on word %q, encountered %s", word, err) + } + sum := h.Sum(nil) + nodes = append(nodes, &Node{checksum: sum}) + } + + for { + nodes = levelUp(nodes) + if len(nodes) == 1 { + break + } + } + for i := range nodes { + c, err := nodes[i].Checksum() + if err != nil { + t.Error(err) + } + t.Logf("checksum %x", c) + } + if len(nodes) > 0 { + t.Errorf("%d nodes; %d characters", len(nodes), len(words)) + } +} + +func levelUp(nodes []*Node) []*Node { + var ( + newNodes []*Node + last = len(nodes) - 1 + ) + + for i := range nodes { + if i%2 == 0 { + if i == last { + // TODO rebalance the last parent + log.Println("WHOOP") + } + n := NewNode() + n.Left = nodes[i] + n.Left.Parent = n + newNodes = append(newNodes, n) + } else { + n := newNodes[len(newNodes)-1] + n.Right = nodes[i] + n.Right.Parent = n + } + } + return newNodes +} diff --git a/tree.go b/tree.go new file mode 100644 index 0000000..e65b2c5 --- /dev/null +++ b/tree.go @@ -0,0 +1,5 @@ +package merkle + +type Tree struct { + Nodes []Node +}